diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj
index af1b5fe55..3a91799b6 100644
--- a/src/NzbDrone.Api/NzbDrone.Api.csproj
+++ b/src/NzbDrone.Api/NzbDrone.Api.csproj
@@ -101,6 +101,7 @@
+
@@ -202,6 +203,7 @@
+
diff --git a/src/NzbDrone.Api/Queue/QueueActionModule.cs b/src/NzbDrone.Api/Queue/QueueActionModule.cs
new file mode 100644
index 000000000..cba735d74
--- /dev/null
+++ b/src/NzbDrone.Api/Queue/QueueActionModule.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
+ {
+ 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[@"/(?[\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 Import()
+ {
+ var resource = Request.Body.FromJson();
+ var trackedDownload = GetTrackedDownload(resource.Id);
+
+ _completedDownloadService.Import(trackedDownload);
+
+ return resource.AsResponse();
+ }
+
+ private JsonResponse Grab()
+ {
+ var resource = Request.Body.FromJson();
+
+ 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;
+ }
+ }
+}
diff --git a/src/NzbDrone.Api/REST/NotFoundException.cs b/src/NzbDrone.Api/REST/NotFoundException.cs
new file mode 100644
index 000000000..92b4016a9
--- /dev/null
+++ b/src/NzbDrone.Api/REST/NotFoundException.cs
@@ -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)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs
index 116ac2874..324e11ac7 100644
--- a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs
@@ -36,7 +36,7 @@ namespace NzbDrone.Core.Test.Download
.Build()
.ToList();
- var remoteEpisode = new RemoteEpisode
+ var remoteEpisode = new RemoteEpisode
{
Series = new Series(),
Episodes = new List {new Episode {Id = 1}}
@@ -115,7 +115,7 @@ namespace NzbDrone.Core.Test.Download
private void GivenCompletedImport()
{
Mocker.GetMock()
- .Setup(v => v.ProcessFolder(It.IsAny(), It.IsAny()))
+ .Setup(v => v.ProcessFolder(It.IsAny(), It.IsAny(), It.IsAny()))
.Returns(new List
{
new ImportResult(new ImportDecision(new LocalEpisode() { Path = @"C:\TestPath\Droned.S01E01.mkv" }))
@@ -125,7 +125,7 @@ namespace NzbDrone.Core.Test.Download
private void GivenFailedImport()
{
Mocker.GetMock()
- .Setup(v => v.ProcessFolder(It.IsAny(), It.IsAny()))
+ .Setup(v => v.ProcessFolder(It.IsAny(), It.IsAny(), It.IsAny()))
.Returns(new List()
{
new ImportResult(new ImportDecision(new LocalEpisode() { Path = @"C:\TestPath\Droned.S01E01.mkv" }, "Test Failure"))
@@ -135,13 +135,13 @@ namespace NzbDrone.Core.Test.Download
private void VerifyNoImports()
{
Mocker.GetMock()
- .Verify(v => v.ProcessFolder(It.IsAny(), It.IsAny()), Times.Never());
+ .Verify(v => v.ProcessFolder(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never());
}
private void VerifyImports()
{
Mocker.GetMock()
- .Verify(v => v.ProcessFolder(It.IsAny(), It.IsAny()), Times.Once());
+ .Verify(v => v.ProcessFolder(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once());
}
[Test]
@@ -473,7 +473,7 @@ namespace NzbDrone.Core.Test.Download
GivenNoImportedHistory();
Mocker.GetMock()
- .Setup(v => v.ProcessFolder(It.IsAny(), It.IsAny()))
+ .Setup(v => v.ProcessFolder(It.IsAny(), It.IsAny(), It.IsAny()))
.Returns(new List
{
new ImportResult(
@@ -505,7 +505,7 @@ namespace NzbDrone.Core.Test.Download
GivenNoImportedHistory();
Mocker.GetMock()
- .Setup(v => v.ProcessFolder(It.IsAny(), It.IsAny()))
+ .Setup(v => v.ProcessFolder(It.IsAny(), It.IsAny(), It.IsAny()))
.Returns(new List
{
new ImportResult(
@@ -537,7 +537,7 @@ namespace NzbDrone.Core.Test.Download
GivenNoImportedHistory();
Mocker.GetMock()
- .Setup(v => v.ProcessFolder(It.IsAny(), It.IsAny()))
+ .Setup(v => v.ProcessFolder(It.IsAny(), It.IsAny(), It.IsAny()))
.Returns(new List
{
new ImportResult(new ImportDecision(new LocalEpisode() {Path = @"C:\TestPath\Droned.S01E01.mkv"})),
diff --git a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveGrabbedFixture.cs b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveGrabbedFixture.cs
index 800f9a720..89776300a 100644
--- a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveGrabbedFixture.cs
+++ b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveGrabbedFixture.cs
@@ -5,7 +5,9 @@ using Marr.Data;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.DecisionEngine;
+using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Pending;
+using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles;
@@ -102,7 +104,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
{
GivenHeldRelease(_parsedEpisodeInfo.Quality);
- Subject.RemoveGrabbed(new List { _temporarilyRejected });
+ Subject.Handle(new EpisodeGrabbedEvent(_remoteEpisode));
VerifyDelete();
}
@@ -112,7 +114,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
{
GivenHeldRelease(new QualityModel(Quality.SDTV));
- Subject.RemoveGrabbed(new List { _temporarilyRejected });
+ Subject.Handle(new EpisodeGrabbedEvent(_remoteEpisode));
VerifyDelete();
}
@@ -122,7 +124,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
{
GivenHeldRelease(new QualityModel(Quality.Bluray720p));
- Subject.RemoveGrabbed(new List { _temporarilyRejected });
+ Subject.Handle(new EpisodeGrabbedEvent(_remoteEpisode));
VerifyNoDelete();
}
diff --git a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveRejectedFixture.cs b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveRejectedFixture.cs
index c80780c4a..36e520a2d 100644
--- a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveRejectedFixture.cs
+++ b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveRejectedFixture.cs
@@ -5,7 +5,9 @@ using Marr.Data;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.DecisionEngine;
+using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Pending;
+using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles;
@@ -104,7 +106,9 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
{
GivenHeldRelease(_release.Title, _release.Indexer, _release.PublishDate);
- Subject.RemoveRejected(new List { _temporarilyRejected });
+ Subject.Handle(new RssSyncCompleteEvent(new ProcessedDecisions(new List(),
+ new List(),
+ new List { _temporarilyRejected })));
VerifyDelete();
}
@@ -114,7 +118,9 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
{
GivenHeldRelease(_release.Title + "-RP", _release.Indexer, _release.PublishDate);
- Subject.RemoveRejected(new List { _temporarilyRejected });
+ Subject.Handle(new RssSyncCompleteEvent(new ProcessedDecisions(new List(),
+ new List(),
+ new List { _temporarilyRejected })));
VerifyNoDelete();
}
@@ -124,7 +130,9 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
{
GivenHeldRelease(_release.Title, "AnotherIndexer", _release.PublishDate);
- Subject.RemoveRejected(new List { _temporarilyRejected });
+ Subject.Handle(new RssSyncCompleteEvent(new ProcessedDecisions(new List(),
+ new List(),
+ new List { _temporarilyRejected })));
VerifyNoDelete();
}
@@ -134,7 +142,9 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
{
GivenHeldRelease(_release.Title, _release.Indexer, _release.PublishDate.AddHours(1));
- Subject.RemoveRejected(new List { _temporarilyRejected });
+ Subject.Handle(new RssSyncCompleteEvent(new ProcessedDecisions(new List(),
+ new List(),
+ new List { _temporarilyRejected })));
VerifyNoDelete();
}
diff --git a/src/NzbDrone.Core.Test/IndexerSearchTests/EpisodeInfoRefreshedSearchFixture.cs b/src/NzbDrone.Core.Test/IndexerSearchTests/EpisodeInfoRefreshedSearchFixture.cs
index 5a57a6f34..6a60eae6c 100644
--- a/src/NzbDrone.Core.Test/IndexerSearchTests/EpisodeInfoRefreshedSearchFixture.cs
+++ b/src/NzbDrone.Core.Test/IndexerSearchTests/EpisodeInfoRefreshedSearchFixture.cs
@@ -105,7 +105,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
Mocker.GetMock()
.Setup(s => s.ProcessDecisions(It.IsAny>()))
- .Returns(new ProcessedDecisions(new List(), new List()));
+ .Returns(new ProcessedDecisions(new List(), new List(), new List()));
Subject.Handle(new EpisodeInfoRefreshedEvent(_series, _added, _updated));
diff --git a/src/NzbDrone.Core.Test/IndexerSearchTests/SeriesSearchServiceFixture.cs b/src/NzbDrone.Core.Test/IndexerSearchTests/SeriesSearchServiceFixture.cs
index 22ef726a7..3b3f15216 100644
--- a/src/NzbDrone.Core.Test/IndexerSearchTests/SeriesSearchServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/IndexerSearchTests/SeriesSearchServiceFixture.cs
@@ -37,7 +37,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
Mocker.GetMock()
.Setup(s => s.ProcessDecisions(It.IsAny>()))
- .Returns(new ProcessedDecisions(new List(), new List()));
+ .Returns(new ProcessedDecisions(new List(), new List(), new List()));
}
[Test]
diff --git a/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs
index 398a5d704..22244a059 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs
@@ -87,6 +87,8 @@ namespace NzbDrone.Core.Test.MediaFiles
[Test]
public void should_not_import_if_folder_is_a_series_path()
{
+ GivenValidSeries();
+
Mocker.GetMock()
.Setup(s => s.SeriesPathExists(It.IsAny()))
.Returns(true);
@@ -97,8 +99,8 @@ namespace NzbDrone.Core.Test.MediaFiles
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
- Mocker.GetMock()
- .Verify(v => v.GetSeries(It.IsAny()), Times.Never());
+ Mocker.GetMock()
+ .Verify(v => v.GetVideoFiles(It.IsAny(), true), Times.Never());
ExceptionVerification.ExpectedWarns(1);
}
diff --git a/src/NzbDrone.Core/Download/CompletedDownloadService.cs b/src/NzbDrone.Core/Download/CompletedDownloadService.cs
index 33a279d38..9b15bd390 100644
--- a/src/NzbDrone.Core/Download/CompletedDownloadService.cs
+++ b/src/NzbDrone.Core/Download/CompletedDownloadService.cs
@@ -118,13 +118,14 @@ namespace NzbDrone.Core.Download
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);
}
+
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);
}
diff --git a/src/NzbDrone.Core/Download/DownloadClientProvider.cs b/src/NzbDrone.Core/Download/DownloadClientProvider.cs
index f8c931b3e..471a9901c 100644
--- a/src/NzbDrone.Core/Download/DownloadClientProvider.cs
+++ b/src/NzbDrone.Core/Download/DownloadClientProvider.cs
@@ -8,6 +8,7 @@ namespace NzbDrone.Core.Download
{
IDownloadClient GetDownloadClient(DownloadProtocol downloadProtocol);
IEnumerable GetDownloadClients();
+ IDownloadClient Get(int id);
}
public class DownloadClientProvider : IProvideDownloadClient
@@ -28,5 +29,10 @@ namespace NzbDrone.Core.Download
{
return _downloadClientFactory.GetAvailableProviders();
}
+
+ public IDownloadClient Get(int id)
+ {
+ return _downloadClientFactory.GetAvailableProviders().Single(d => d.Definition.Id == id);
+ }
}
}
\ No newline at end of file
diff --git a/src/NzbDrone.Core/Download/DownloadTrackingService.cs b/src/NzbDrone.Core/Download/DownloadTrackingService.cs
index 14aaef7ff..44190560c 100644
--- a/src/NzbDrone.Core/Download/DownloadTrackingService.cs
+++ b/src/NzbDrone.Core/Download/DownloadTrackingService.cs
@@ -19,7 +19,7 @@ namespace NzbDrone.Core.Download
{
TrackedDownload[] GetCompletedDownloads();
TrackedDownload[] GetQueuedDownloads();
-
+ TrackedDownload Find(string trackingId);
void MarkAsFailed(Int32 historyId);
}
@@ -88,6 +88,11 @@ namespace NzbDrone.Core.Download
}, 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);
diff --git a/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs b/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs
index ba0c6a7b6..4963002ca 100644
--- a/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs
+++ b/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs
@@ -4,6 +4,7 @@ using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DecisionEngine;
+using NzbDrone.Core.Indexers;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
@@ -17,15 +18,19 @@ namespace NzbDrone.Core.Download.Pending
public interface IPendingReleaseService
{
void Add(DownloadDecision decision);
- void RemoveGrabbed(List grabbed);
- void RemoveRejected(List rejected);
+
List GetPending();
List GetPendingRemoteEpisodes(int seriesId);
List GetPendingQueue();
+ Queue.Queue FindPendingQueueItem(int queueId);
+ void RemovePendingQueueItem(int queueId);
RemoteEpisode OldestPendingRelease(int seriesId, IEnumerable episodeIds);
}
- public class PendingReleaseService : IPendingReleaseService, IHandle
+ public class PendingReleaseService : IPendingReleaseService,
+ IHandle,
+ IHandle,
+ IHandle
{
private readonly IPendingReleaseRepository _repository;
private readonly ISeriesService _seriesService;
@@ -49,6 +54,7 @@ namespace NzbDrone.Core.Download.Pending
_logger = logger;
}
+
public void Add(DownloadDecision decision)
{
var alreadyPending = GetPendingReleases();
@@ -69,61 +75,6 @@ namespace NzbDrone.Core.Download.Pending
Insert(decision);
}
- public void RemoveGrabbed(List 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 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 GetPending()
{
return _repository.All().Select(p => p.Release).ToList();
@@ -165,6 +116,18 @@ namespace NzbDrone.Core.Download.Pending
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 episodeIds)
{
return GetPendingRemoteEpisodes(seriesId)
@@ -243,9 +206,73 @@ namespace NzbDrone.Core.Download.Pending
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 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)
{
_repository.DeleteBySeriesId(message.Series.Id);
}
+
+ public void Handle(EpisodeGrabbedEvent message)
+ {
+ RemoveGrabbed(message.Episode);
+ }
+
+ public void Handle(RssSyncCompleteEvent message)
+ {
+ RemoveRejected(message.ProcessedDecisions.Rejected);
+ }
}
}
diff --git a/src/NzbDrone.Core/Download/ProcessDownloadDecisions.cs b/src/NzbDrone.Core/Download/ProcessDownloadDecisions.cs
index 8482648c3..b006f6e59 100644
--- a/src/NzbDrone.Core/Download/ProcessDownloadDecisions.cs
+++ b/src/NzbDrone.Core/Download/ProcessDownloadDecisions.cs
@@ -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 GetQualifiedReports(IEnumerable decisions)
diff --git a/src/NzbDrone.Core/Download/ProcessedDecisions.cs b/src/NzbDrone.Core/Download/ProcessedDecisions.cs
index c274b931a..b59df6e1a 100644
--- a/src/NzbDrone.Core/Download/ProcessedDecisions.cs
+++ b/src/NzbDrone.Core/Download/ProcessedDecisions.cs
@@ -7,11 +7,13 @@ namespace NzbDrone.Core.Download
{
public List Grabbed { get; set; }
public List Pending { get; set; }
+ public List Rejected { get; set; }
- public ProcessedDecisions(List grabbed, List pending)
+ public ProcessedDecisions(List grabbed, List pending, List rejected)
{
Grabbed = grabbed;
Pending = pending;
+ Rejected = rejected;
}
}
}
diff --git a/src/NzbDrone.Core/Download/TrackedDownloadStatusMessage.cs b/src/NzbDrone.Core/Download/TrackedDownloadStatusMessage.cs
index 903a2553e..0f03d2279 100644
--- a/src/NzbDrone.Core/Download/TrackedDownloadStatusMessage.cs
+++ b/src/NzbDrone.Core/Download/TrackedDownloadStatusMessage.cs
@@ -8,6 +8,10 @@ namespace NzbDrone.Core.Download
public String Title { get; set; }
public List Messages { get; set; }
+ private TrackedDownloadStatusMessage()
+ {
+ }
+
public TrackedDownloadStatusMessage(String title, List messages)
{
Title = title;
diff --git a/src/NzbDrone.Core/Indexers/RssSyncCompleteEvent.cs b/src/NzbDrone.Core/Indexers/RssSyncCompleteEvent.cs
index af4fef0ff..a15a1fd0b 100644
--- a/src/NzbDrone.Core/Indexers/RssSyncCompleteEvent.cs
+++ b/src/NzbDrone.Core/Indexers/RssSyncCompleteEvent.cs
@@ -1,8 +1,15 @@
using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Download;
namespace NzbDrone.Core.Indexers
{
public class RssSyncCompleteEvent : IEvent
{
+ public ProcessedDecisions ProcessedDecisions { get; private set; }
+
+ public RssSyncCompleteEvent(ProcessedDecisions processedDecisions)
+ {
+ ProcessedDecisions = processedDecisions;
+ }
}
}
diff --git a/src/NzbDrone.Core/Indexers/RssSyncService.cs b/src/NzbDrone.Core/Indexers/RssSyncService.cs
index 246bc801b..3313ebf00 100644
--- a/src/NzbDrone.Core/Indexers/RssSyncService.cs
+++ b/src/NzbDrone.Core/Indexers/RssSyncService.cs
@@ -40,15 +40,13 @@ namespace NzbDrone.Core.Indexers
}
- private List Sync()
+ private ProcessedDecisions Sync()
{
_logger.ProgressInfo("Starting RSS Sync");
var reports = _rssFetcherAndParser.Fetch().Concat(_pendingReleaseService.GetPending()).ToList();
var decisions = _downloadDecisionMaker.GetRssDecision(reports);
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);
@@ -59,20 +57,21 @@ namespace NzbDrone.Core.Indexers
_logger.ProgressInfo(message);
- return processed.Grabbed.Concat(processed.Pending).ToList();
+ return processed;
}
public void Execute(RssSyncCommand message)
{
var processed = Sync();
+ var grabbedOrPending = processed.Grabbed.Concat(processed.Pending).ToList();
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);
- _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));
}
}
}
diff --git a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs
index fa6509d3c..201e80f54 100644
--- a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs
+++ b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs
@@ -4,7 +4,6 @@ using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Common.Disk;
-using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Tv;
@@ -17,7 +16,9 @@ namespace NzbDrone.Core.MediaFiles
{
List ProcessRootFolder(DirectoryInfo directoryInfo);
List ProcessFolder(DirectoryInfo directoryInfo, DownloadClientItem downloadClientItem = null);
+ List ProcessFolder(DirectoryInfo directoryInfo, Series series, DownloadClientItem downloadClientItem = null);
List ProcessFile(FileInfo fileInfo, DownloadClientItem downloadClientItem = null);
+ List ProcessFile(FileInfo fileInfo, Series series, DownloadClientItem downloadClientItem = null);
}
public class DownloadedEpisodesImportService : IDownloadedEpisodesImportService
@@ -26,7 +27,6 @@ namespace NzbDrone.Core.MediaFiles
private readonly IDiskScanService _diskScanService;
private readonly ISeriesService _seriesService;
private readonly IParsingService _parsingService;
- private readonly IConfigService _configService;
private readonly IMakeImportDecision _importDecisionMaker;
private readonly IImportApprovedEpisodes _importApprovedEpisodes;
private readonly ISampleService _sampleService;
@@ -36,7 +36,6 @@ namespace NzbDrone.Core.MediaFiles
IDiskScanService diskScanService,
ISeriesService seriesService,
IParsingService parsingService,
- IConfigService configService,
IMakeImportDecision importDecisionMaker,
IImportApprovedEpisodes importApprovedEpisodes,
ISampleService sampleService,
@@ -46,7 +45,6 @@ namespace NzbDrone.Core.MediaFiles
_diskScanService = diskScanService;
_seriesService = seriesService;
_parsingService = parsingService;
- _configService = configService;
_importDecisionMaker = importDecisionMaker;
_importApprovedEpisodes = importApprovedEpisodes;
_sampleService = sampleService;
@@ -73,6 +71,25 @@ namespace NzbDrone.Core.MediaFiles
}
public List 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
+ {
+ UnknownSeriesResult("Unknown Series")
+ };
+ }
+
+ return ProcessFolder(directoryInfo, series, downloadClientItem);
+ }
+
+ public List ProcessFolder(DirectoryInfo directoryInfo, Series series,
+ DownloadClientItem downloadClientItem = null)
{
if (_seriesService.SeriesPathExists(directoryInfo.FullName))
{
@@ -81,18 +98,9 @@ namespace NzbDrone.Core.MediaFiles
}
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
- var series = _parsingService.GetSeries(cleanedUpName);
var quality = QualityParser.ParseQuality(cleanedUpName);
- _logger.Debug("{0} folder quality: {1}", cleanedUpName, quality);
- if (series == null)
- {
- _logger.Debug("Unknown Series {0}", cleanedUpName);
- return new List
- {
- new ImportResult(new ImportDecision(null, "Unknown Series"), "Unknown Series")
- };
- }
+ _logger.Debug("{0} folder quality: {1}", cleanedUpName, quality);
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
@@ -102,20 +110,18 @@ namespace NzbDrone.Core.MediaFiles
{
if (_diskProvider.IsFileLocked(videoFile))
{
- _logger.Debug("[{0}] is currently locked by another process, skipping", videoFile);
return new List
- {
- 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 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");
_diskProvider.DeleteFolder(directoryInfo.FullName, true);
@@ -131,15 +137,26 @@ namespace NzbDrone.Core.MediaFiles
if (series == null)
{
_logger.Debug("Unknown Series for file: {0}", fileInfo.Name);
- return new List() { new ImportResult(new ImportDecision(new LocalEpisode { Path = fileInfo.FullName }, "Unknown Series"), String.Format("Unknown Series for file: {0}", fileInfo.Name)) };
+
+ return new List
+ {
+ UnknownSeriesResult(String.Format("Unknown Series for file: {0}", fileInfo.Name), fileInfo.FullName)
+ };
}
+ return ProcessFile(fileInfo, series, downloadClientItem);
+ }
+
+ public List ProcessFile(FileInfo fileInfo, Series series, DownloadClientItem downloadClientItem = null)
+ {
if (downloadClientItem == null)
{
if (_diskProvider.IsFileLocked(fileInfo.FullName))
{
- _logger.Debug("[{0}] is currently locked by another process, skipping", fileInfo.FullName);
- return new List();
+ return new List
+ {
+ FileIsLockedResult(fileInfo.FullName)
+ };
}
}
@@ -155,11 +172,9 @@ namespace NzbDrone.Core.MediaFiles
return folder;
}
- private bool ShouldDeleteFolder(DirectoryInfo directoryInfo)
+ private bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Series series)
{
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
- var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
- var series = _parsingService.GetSeries(cleanedUpName);
foreach (var videoFile in videoFiles)
{
@@ -184,5 +199,18 @@ namespace NzbDrone.Core.MediaFiles
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);
+ }
}
-}
\ No newline at end of file
+}
diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs
index b6a29ed43..afa5da0fa 100644
--- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs
+++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs
@@ -59,40 +59,40 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
try
{
- var parsedEpisode = _parsingService.GetLocalEpisode(file, series, sceneSource);
+ var localEpisode = _parsingService.GetLocalEpisode(file, series, sceneSource);
- if (parsedEpisode != null)
+ if (localEpisode != null)
{
if (quality != null &&
- new QualityModelComparer(parsedEpisode.Series.Profile).Compare(quality,
- parsedEpisode.Quality) > 0)
+ new QualityModelComparer(localEpisode.Series.Profile).Compare(quality,
+ localEpisode.Quality) > 0)
{
_logger.Debug("Using quality from folder: {0}", quality);
- parsedEpisode.Quality = quality;
+ localEpisode.Quality = quality;
}
- parsedEpisode.Size = _diskProvider.GetFileSize(file);
- _logger.Debug("Size: {0}", parsedEpisode.Size);
+ localEpisode.Size = _diskProvider.GetFileSize(file);
+ _logger.Debug("Size: {0}", localEpisode.Size);
- parsedEpisode.MediaInfo = _videoFileInfoReader.GetMediaInfo(file);
+ localEpisode.MediaInfo = _videoFileInfoReader.GetMediaInfo(file);
- decision = GetDecision(parsedEpisode);
+ decision = GetDecision(localEpisode);
}
else
{
- parsedEpisode = new LocalEpisode();
- parsedEpisode.Path = file;
+ localEpisode = new LocalEpisode();
+ localEpisode.Path = file;
- decision = new ImportDecision(parsedEpisode, "Unable to parse file");
+ decision = new ImportDecision(localEpisode, "Unable to parse file");
}
}
catch (EpisodeNotFoundException e)
{
- var parsedEpisode = new LocalEpisode();
- parsedEpisode.Path = file;
+ var localEpisode = new LocalEpisode();
+ localEpisode.Path = file;
- decision = new ImportDecision(parsedEpisode, e.Message);
+ decision = new ImportDecision(localEpisode, e.Message);
}
catch (Exception e)
{
diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ManualImportService.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ManualImportService.cs
new file mode 100644
index 000000000..d415f98d7
--- /dev/null
+++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ManualImportService.cs
@@ -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)
+ {
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj
index 85336a6f4..0c00d49d3 100644
--- a/src/NzbDrone.Core/NzbDrone.Core.csproj
+++ b/src/NzbDrone.Core/NzbDrone.Core.csproj
@@ -543,6 +543,7 @@
+
diff --git a/src/NzbDrone.Core/Queue/QueueService.cs b/src/NzbDrone.Core/Queue/QueueService.cs
index 60e49fe62..cfd87c944 100644
--- a/src/NzbDrone.Core/Queue/QueueService.cs
+++ b/src/NzbDrone.Core/Queue/QueueService.cs
@@ -8,6 +8,7 @@ namespace NzbDrone.Core.Queue
public interface IQueueService
{
List GetQueue();
+ Queue Find(int id);
}
public class QueueService : IQueueService
@@ -28,6 +29,11 @@ namespace NzbDrone.Core.Queue
return MapQueue(queueItems);
}
+ public Queue Find(int id)
+ {
+ return GetQueue().SingleOrDefault(q => q.Id == id);
+ }
+
private List MapQueue(IEnumerable trackedDownloads)
{
var queued = new List();
diff --git a/src/UI/Activity/Queue/QueueActionsCell.js b/src/UI/Activity/Queue/QueueActionsCell.js
new file mode 100644
index 000000000..9c0dede97
--- /dev/null
+++ b/src/UI/Activity/Queue/QueueActionsCell.js
@@ -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(''.format(icon, title) +
+ '');
+ }
+
+ 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);
+ });
+ }
+ });
+ });
diff --git a/src/UI/Activity/Queue/QueueLayout.js b/src/UI/Activity/Queue/QueueLayout.js
index 6b86d46c1..cbcfc6ba4 100644
--- a/src/UI/Activity/Queue/QueueLayout.js
+++ b/src/UI/Activity/Queue/QueueLayout.js
@@ -9,6 +9,7 @@ define(
'Cells/EpisodeTitleCell',
'Cells/QualityCell',
'Activity/Queue/QueueStatusCell',
+ 'Activity/Queue/QueueActionsCell',
'Activity/Queue/TimeleftCell',
'Activity/Queue/ProgressCell',
'Shared/Grid/Pager'
@@ -20,6 +21,7 @@ define(
EpisodeTitleCell,
QualityCell,
QueueStatusCell,
+ QueueActionsCell,
TimeleftCell,
ProgressCell,
GridPager) {
@@ -74,6 +76,12 @@ define(
label : 'Progress',
cell : ProgressCell,
cellValue : 'this'
+ },
+ {
+ name : 'status',
+ label : '',
+ cell : QueueActionsCell,
+ cellValue : 'this'
}
],
diff --git a/src/UI/Cells/cells.less b/src/UI/Cells/cells.less
index 431df2260..00b39f8b5 100644
--- a/src/UI/Cells/cells.less
+++ b/src/UI/Cells/cells.less
@@ -145,26 +145,36 @@ td.episode-status-cell, td.quality-cell, td.history-quality-cell, td.progress-ce
}
.timeleft-cell {
- cursor : default;
- width : 80px;
- text-align: center;
+ cursor : default;
+ width : 80px;
+ text-align : center;
}
.queue-status-cell {
- width: 20px;
- text-align: center !important;
+ width : 20px;
+ text-align : center !important;
+}
+
+.queue-actions-cell {
+ width : 55px;
+ text-align : right !important;
+
+ i {
+ margin-left : 3px;
+ margin-right : 3px;
+ }
}
.download-log-cell {
- width: 80px;
+ width : 80px;
}
td.delete-episode-file-cell {
.clickable();
- text-align: center;
- width: 20px;
- font-size: 20px;
+ text-align : center;
+ width : 20px;
+ font-size : 20px;
i {
.clickable();
diff --git a/src/UI/Settings/Quality/Definition/QualityDefinitionItemView.js b/src/UI/Settings/Quality/Definition/QualityDefinitionView.js
similarity index 96%
rename from src/UI/Settings/Quality/Definition/QualityDefinitionItemView.js
rename to src/UI/Settings/Quality/Definition/QualityDefinitionView.js
index 821f02d2c..0d0e08a84 100644
--- a/src/UI/Settings/Quality/Definition/QualityDefinitionItemView.js
+++ b/src/UI/Settings/Quality/Definition/QualityDefinitionView.js
@@ -24,7 +24,8 @@ define(
'slide .x-slider': '_updateSize'
},
- initialize: function () {
+ initialize: function (options) {
+ this.profileCollection = options.profiles;
this.filesize = fileSize;
},
diff --git a/src/UI/Settings/Quality/Definition/QualityDefinitionItemViewTemplate.hbs b/src/UI/Settings/Quality/Definition/QualityDefinitionViewTemplate.hbs
similarity index 100%
rename from src/UI/Settings/Quality/Definition/QualityDefinitionItemViewTemplate.hbs
rename to src/UI/Settings/Quality/Definition/QualityDefinitionViewTemplate.hbs