-
- Removing will remove the download and the file(s) from the download client.
-
+
+ Remove From Download Client
+
+
+
Blacklist Release
@@ -83,7 +107,7 @@ class RemoveQueueItemModal extends Component {
type={inputTypes.CHECK}
name="blacklist"
value={blacklist}
- helpText="Prevents Radarr from automatically grabbing this movie again"
+ helpText="Starts a search for this movie again and prevents this release from being grabbed again"
onChange={this.onBlacklistChange}
/>
@@ -97,7 +121,7 @@ class RemoveQueueItemModal extends Component {
@@ -111,6 +135,7 @@ class RemoveQueueItemModal extends Component {
RemoveQueueItemModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
sourceTitle: PropTypes.string.isRequired,
+ canIgnore: PropTypes.bool.isRequired,
onRemovePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
diff --git a/frontend/src/Activity/Queue/RemoveQueueItemsModal.js b/frontend/src/Activity/Queue/RemoveQueueItemsModal.js
index 09a05890d..a41692bd5 100644
--- a/frontend/src/Activity/Queue/RemoveQueueItemsModal.js
+++ b/frontend/src/Activity/Queue/RemoveQueueItemsModal.js
@@ -21,26 +21,41 @@ class RemoveQueueItemsModal extends Component {
super(props, context);
this.state = {
+ remove: true,
blacklist: false
};
}
//
- // Listeners
+ // Control
+
+ resetState = function() {
+ this.setState({
+ remove: true,
+ blacklist: false
+ });
+ }
+
+ //
+ // Listeners
+
+ onRemoveChange = ({ value }) => {
+ this.setState({ remove: value });
+ }
onBlacklistChange = ({ value }) => {
this.setState({ blacklist: value });
}
- onRemoveQueueItemConfirmed = () => {
- const blacklist = this.state.blacklist;
+ onRemoveConfirmed = () => {
+ const state = this.state;
- this.setState({ blacklist: false });
- this.props.onRemovePress(blacklist);
+ this.resetState();
+ this.props.onRemovePress(state);
}
onModalClose = () => {
- this.setState({ blacklist: false });
+ this.resetState();
this.props.onModalClose();
}
@@ -50,10 +65,11 @@ class RemoveQueueItemsModal extends Component {
render() {
const {
isOpen,
- selectedCount
+ selectedCount,
+ canIgnore
} = this.props;
- const blacklist = this.state.blacklist;
+ const { remove, blacklist } = this.state;
return (
- Blacklist Release
+ Remove From Download Client
+
+
+
+
+
+
+ Blacklist Release{selectedCount > 1 ? 's' : ''}
+
+
@@ -93,7 +125,7 @@ class RemoveQueueItemsModal extends Component {
@@ -107,6 +139,7 @@ class RemoveQueueItemsModal extends Component {
RemoveQueueItemsModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
selectedCount: PropTypes.number.isRequired,
+ canIgnore: PropTypes.bool.isRequired,
onRemovePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
diff --git a/frontend/src/Commands/commandNames.js b/frontend/src/Commands/commandNames.js
index 7e3df59b8..22ed45322 100644
--- a/frontend/src/Commands/commandNames.js
+++ b/frontend/src/Commands/commandNames.js
@@ -1,6 +1,6 @@
export const APPLICATION_UPDATE = 'ApplicationUpdate';
export const BACKUP = 'Backup';
-export const CHECK_FOR_FINISHED_DOWNLOAD = 'CheckForFinishedDownload';
+export const REFRESH_MONITORED_DOWNLOADS = 'RefreshMonitoredDownloads';
export const CLEAR_BLACKLIST = 'ClearBlacklist';
export const CLEAR_LOGS = 'ClearLog';
export const CUTOFF_UNMET_MOVIES_SEARCH = 'CutoffUnmetMoviesSearch';
diff --git a/frontend/src/Components/SignalRConnector.js b/frontend/src/Components/SignalRConnector.js
index ee862f5c0..f9e586c98 100644
--- a/frontend/src/Components/SignalRConnector.js
+++ b/frontend/src/Components/SignalRConnector.js
@@ -224,7 +224,7 @@ class SignalRConnector extends Component {
}
handleSystemTask = () => {
- // No-op for now, we may want this later
+ this.props.dispatchFetchCommands();
}
handleRootfolder = () => {
diff --git a/frontend/src/Helpers/Props/icons.js b/frontend/src/Helpers/Props/icons.js
index 25e4c4627..3a895e1d3 100644
--- a/frontend/src/Helpers/Props/icons.js
+++ b/frontend/src/Helpers/Props/icons.js
@@ -153,6 +153,7 @@ export const HEALTH = fasMedkit;
export const HEART = fasHeart;
export const HISTORY = fasHistory;
export const HOUSEKEEPING = fasHome;
+export const IGNORE = fasTimesCircle;
export const IN_CINEMAS = fasTicketAlt;
export const INFO = fasInfoCircle;
export const INTERACTIVE = fasUser;
diff --git a/frontend/src/Store/Actions/historyActions.js b/frontend/src/Store/Actions/historyActions.js
index 54d1be04a..e1468d258 100644
--- a/frontend/src/Store/Actions/historyActions.js
+++ b/frontend/src/Store/Actions/historyActions.js
@@ -148,6 +148,17 @@ export const defaultState = {
type: filterTypes.EQUAL
}
]
+ },
+ {
+ key: 'ignored',
+ label: 'Ignored',
+ filters: [
+ {
+ key: 'eventType',
+ value: '9',
+ type: filterTypes.EQUAL
+ }
+ ]
}
]
diff --git a/frontend/src/Store/Actions/queueActions.js b/frontend/src/Store/Actions/queueActions.js
index 1f568c14b..15e971c43 100644
--- a/frontend/src/Store/Actions/queueActions.js
+++ b/frontend/src/Store/Actions/queueActions.js
@@ -351,13 +351,14 @@ export const actionHandlers = handleThunks({
[REMOVE_QUEUE_ITEM]: function(getState, payload, dispatch) {
const {
id,
+ remove,
blacklist
} = payload;
dispatch(updateItem({ section: paged, id, isRemoving: true }));
const promise = createAjaxRequest({
- url: `/queue/${id}?blacklist=${blacklist}`,
+ url: `/queue/${id}?removeFromClient=${remove}&blacklist=${blacklist}`,
method: 'DELETE'
}).request;
@@ -373,6 +374,7 @@ export const actionHandlers = handleThunks({
[REMOVE_QUEUE_ITEMS]: function(getState, payload, dispatch) {
const {
ids,
+ remove,
blacklist
} = payload;
@@ -389,7 +391,7 @@ export const actionHandlers = handleThunks({
]));
const promise = createAjaxRequest({
- url: `/queue/bulk?blacklist=${blacklist}`,
+ url: `/queue/bulk?removeFromClient=${remove}&blacklist=${blacklist}`,
method: 'DELETE',
dataType: 'json',
data: JSON.stringify({ ids })
diff --git a/frontend/src/System/Tasks/Queued/QueuedTaskRow.js b/frontend/src/System/Tasks/Queued/QueuedTaskRow.js
index 4aa6d76d6..1a64e6f84 100644
--- a/frontend/src/System/Tasks/Queued/QueuedTaskRow.js
+++ b/frontend/src/System/Tasks/Queued/QueuedTaskRow.js
@@ -168,7 +168,7 @@ class QueuedTaskRow extends Component {
isCancelConfirmModalOpen
} = this.state;
- let triggerIcon = icons.UNKNOWN;
+ let triggerIcon = icons.QUICK;
if (trigger === 'manual') {
triggerIcon = icons.INTERACTIVE;
diff --git a/src/NzbDrone.Api/Queue/QueueActionModule.cs b/src/NzbDrone.Api/Queue/QueueActionModule.cs
index 287e5eeca..01eb8e3d7 100644
--- a/src/NzbDrone.Api/Queue/QueueActionModule.cs
+++ b/src/NzbDrone.Api/Queue/QueueActionModule.cs
@@ -86,12 +86,7 @@ namespace NzbDrone.Api.Queue
private object Import()
{
- var resource = Request.Body.FromJson();
- var trackedDownload = GetTrackedDownload(resource.Id);
-
- _completedDownloadService.Process(trackedDownload, true);
-
- return resource;
+ throw new BadRequestException("No longer available");
}
private object Grab()
diff --git a/src/NzbDrone.Api/Queue/QueueResource.cs b/src/NzbDrone.Api/Queue/QueueResource.cs
index 99640a540..a395e982a 100644
--- a/src/NzbDrone.Api/Queue/QueueResource.cs
+++ b/src/NzbDrone.Api/Queue/QueueResource.cs
@@ -44,7 +44,7 @@ namespace NzbDrone.Api.Queue
Timeleft = model.Timeleft,
EstimatedCompletionTime = model.EstimatedCompletionTime,
Status = model.Status,
- TrackedDownloadStatus = model.TrackedDownloadStatus,
+ TrackedDownloadStatus = model.TrackedDownloadStatus.ToString(),
StatusMessages = model.StatusMessages,
DownloadId = model.DownloadId,
Protocol = model.Protocol,
diff --git a/src/NzbDrone.Common.Test/OsPathFixture.cs b/src/NzbDrone.Common.Test/OsPathFixture.cs
index 6d79e0cfc..9f2a3c42c 100644
--- a/src/NzbDrone.Common.Test/OsPathFixture.cs
+++ b/src/NzbDrone.Common.Test/OsPathFixture.cs
@@ -1,4 +1,4 @@
-using FluentAssertions;
+using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Test.Common;
@@ -185,6 +185,15 @@ namespace NzbDrone.Common.Test
osPath.FullPath.Should().Be(@"/just/a/test/to/verify the/slashes/");
}
+ [Test]
+ public void should_fix_double_slashes_unix()
+ {
+ var osPath = new OsPath(@"/just/a//test////to/verify the/slashes/");
+
+ osPath.Kind.Should().Be(OsPathKind.Unix);
+ osPath.FullPath.Should().Be(@"/just/a/test/to/verify the/slashes/");
+ }
+
[Test]
public void should_combine_mixed_slashes()
{
diff --git a/src/NzbDrone.Common/Disk/OsPath.cs b/src/NzbDrone.Common/Disk/OsPath.cs
index 02aaf6561..c4327ddeb 100644
--- a/src/NzbDrone.Common/Disk/OsPath.cs
+++ b/src/NzbDrone.Common/Disk/OsPath.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Extensions;
@@ -85,7 +85,13 @@ namespace NzbDrone.Common.Disk
case OsPathKind.Windows:
return path.Replace('/', '\\');
case OsPathKind.Unix:
- return path.Replace('\\', '/');
+ path = path.Replace('\\', '/');
+ while (path.Contains("//"))
+ {
+ path = path.Replace("//", "/");
+ }
+
+ return path;
}
return path;
diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/QueueSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/QueueSpecificationFixture.cs
index 06016bd1f..4ed94445c 100644
--- a/src/NzbDrone.Core.Test/DecisionEngineTests/QueueSpecificationFixture.cs
+++ b/src/NzbDrone.Core.Test/DecisionEngineTests/QueueSpecificationFixture.cs
@@ -6,6 +6,7 @@ using Moq;
using NUnit.Framework;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.DecisionEngine.Specifications;
+using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles;
@@ -24,6 +25,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
private Movie _otherMovie;
+ private ReleaseInfo _releaseInfo;
+
[SetUp]
public void Setup()
{
@@ -45,6 +48,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
.With(s => s.Id = 2)
.Build();
+ _releaseInfo = Builder.CreateNew()
+ .Build();
+
_remoteMovie = Builder.CreateNew()
.With(r => r.Movie = _movie)
.With(r => r.ParsedMovieInfo = new ParsedMovieInfo { Quality = new QualityModel(Quality.DVD) })
@@ -63,11 +69,12 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
.Returns(new List());
}
- private void GivenQueue(IEnumerable remoteMovies)
+ private void GivenQueue(IEnumerable remoteMovies, TrackedDownloadState trackedDownloadState = TrackedDownloadState.Downloading)
{
var queue = remoteMovies.Select(remoteMovie => new Queue.Queue
{
- RemoteMovie = remoteMovie
+ RemoteMovie = remoteMovie,
+ TrackedDownloadState = trackedDownloadState
});
Mocker.GetMock()
@@ -178,5 +185,24 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
GivenQueue(new List { remoteMovie });
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse();
}
+
+ [Test]
+ public void should_return_true_if_everything_is_the_same_for_failed_pending()
+ {
+ _movie.Profile.Cutoff = Quality.Bluray1080p.Id;
+
+ var remoteMovie = Builder.CreateNew()
+ .With(r => r.Movie = _movie)
+ .With(r => r.ParsedMovieInfo = new ParsedMovieInfo
+ {
+ Quality = new QualityModel(Quality.DVD)
+ })
+ .With(r => r.Release = _releaseInfo)
+ .Build();
+
+ GivenQueue(new List { remoteMovie }, TrackedDownloadState.FailedPending);
+
+ Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue();
+ }
}
}
diff --git a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ImportFixture.cs
similarity index 50%
rename from src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs
rename to src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ImportFixture.cs
index 1e2a56bb7..3744fdc6b 100644
--- a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ImportFixture.cs
@@ -20,7 +20,7 @@ using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Download
{
[TestFixture]
- public class CompletedDownloadServiceFixture : CoreTest
+ public class ImportFixture : CoreTest
{
private TrackedDownload _trackedDownload;
@@ -33,12 +33,12 @@ namespace NzbDrone.Core.Test.Download
.With(h => h.Title = "Drone.1998")
.Build();
- var remoteEpisode = BuildRemoteMovie();
+ var remoteMovie = BuildRemoteMovie();
_trackedDownload = Builder.CreateNew()
- .With(c => c.State = TrackedDownloadStage.Downloading)
+ .With(c => c.State = TrackedDownloadState.Downloading)
.With(c => c.DownloadItem = completed)
- .With(c => c.RemoteMovie = remoteEpisode)
+ .With(c => c.RemoteMovie = remoteMovie)
.Build();
Mocker.GetMock()
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.Test.Download
Mocker.GetMock()
.Setup(s => s.GetMovie("Drone.1998"))
- .Returns(remoteEpisode.Movie);
+ .Returns(remoteMovie.Movie);
}
private RemoteMovie BuildRemoteMovie()
@@ -66,23 +66,6 @@ namespace NzbDrone.Core.Test.Download
};
}
- private void GivenNoGrabbedHistory()
- {
- Mocker.GetMock()
- .Setup(s => s.MostRecentForDownloadId(_trackedDownload.DownloadItem.DownloadId))
- .Returns((History.History)null);
- }
-
- private void GivenSuccessfulImport()
- {
- Mocker.GetMock()
- .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
- .Returns(new List
- {
- new ImportResult(new ImportDecision(new LocalMovie() { Path = @"C:\TestPath\Droned.1998.mkv" }))
- });
- }
-
private void GivenABadlyNamedDownload()
{
_trackedDownload.DownloadItem.DownloadId = "1234";
@@ -107,75 +90,6 @@ namespace NzbDrone.Core.Test.Download
.Returns(_trackedDownload.RemoteMovie.Movie);
}
- [TestCase(DownloadItemStatus.Downloading)]
- [TestCase(DownloadItemStatus.Failed)]
- [TestCase(DownloadItemStatus.Queued)]
- [TestCase(DownloadItemStatus.Paused)]
- [TestCase(DownloadItemStatus.Warning)]
- public void should_not_process_if_download_status_isnt_completed(DownloadItemStatus status)
- {
- _trackedDownload.DownloadItem.Status = status;
-
- Subject.Process(_trackedDownload);
-
- AssertNoAttemptedImport();
- }
-
- [Test]
- public void should_not_process_if_matching_history_is_not_found_and_no_category_specified()
- {
- _trackedDownload.DownloadItem.Category = null;
- GivenNoGrabbedHistory();
-
- Subject.Process(_trackedDownload);
-
- AssertNoAttemptedImport();
- }
-
- [Test]
- public void should_process_if_matching_history_is_not_found_but_category_specified()
- {
- _trackedDownload.DownloadItem.Category = "tv";
- GivenNoGrabbedHistory();
- GivenSeriesMatch();
- GivenSuccessfulImport();
-
- Subject.Process(_trackedDownload);
-
- AssertCompletedDownload();
- }
-
- [Test]
- public void should_not_process_if_output_path_is_empty()
- {
- _trackedDownload.DownloadItem.OutputPath = default;
-
- Subject.Process(_trackedDownload);
-
- AssertNoAttemptedImport();
- }
-
- [Test]
- public void should_mark_as_imported_if_all_episodes_were_imported()
- {
- Mocker.GetMock()
- .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
- .Returns(new List
- {
- new ImportResult(
- new ImportDecision(
- new LocalMovie { Path = @"C:\TestPath\Droned.1998.mkv" })),
-
- new ImportResult(
- new ImportDecision(
- new LocalMovie { Path = @"C:\TestPath\Droned.1998.mkv" }))
- });
-
- Subject.Process(_trackedDownload);
-
- AssertCompletedDownload();
- }
-
[Test]
public void should_not_mark_as_imported_if_all_files_were_rejected()
{
@@ -192,16 +106,16 @@ namespace NzbDrone.Core.Test.Download
new LocalMovie { Path = @"C:\TestPath\Droned.1999.mkv" }, new Rejection("Rejected!")), "Test Failure")
});
- Subject.Process(_trackedDownload);
+ Subject.Import(_trackedDownload);
Mocker.GetMock()
.Verify(v => v.PublishEvent(It.IsAny()), Times.Never());
- AssertNoCompletedDownload();
+ AssertNotImported();
}
[Test]
- public void should_not_mark_as_imported_if_no_episodes_were_parsed()
+ public void should_not_mark_as_imported_if_no_movies_were_parsed()
{
Mocker.GetMock()
.Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
@@ -218,9 +132,9 @@ namespace NzbDrone.Core.Test.Download
_trackedDownload.RemoteMovie.Movie = null;
- Subject.Process(_trackedDownload);
+ Subject.Import(_trackedDownload);
- AssertNoCompletedDownload();
+ AssertNotImported();
}
[Test]
@@ -234,138 +148,61 @@ namespace NzbDrone.Core.Test.Download
new ImportResult(new ImportDecision(new LocalMovie { Path = @"C:\TestPath\Droned.1998.mkv" }), "Test Failure")
});
- Subject.Process(_trackedDownload);
+ Subject.Import(_trackedDownload);
- AssertNoCompletedDownload();
+ AssertNotImported();
}
[Test]
- public void should_mark_as_imported_if_all_episodes_were_imported_but_extra_files_were_not()
+ public void should_mark_as_imported_if_all_movies_were_imported_but_extra_files_were_not()
{
GivenSeriesMatch();
+ _trackedDownload.RemoteMovie.Movie = new Movie();
+
Mocker.GetMock()
.Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
.Returns(new List
- {
- new ImportResult(new ImportDecision(new LocalMovie { Path = @"C:\TestPath\Droned.1998.mkv" })),
- new ImportResult(new ImportDecision(new LocalMovie { Path = @"C:\TestPath\Droned.1998.mkv" }), "Test Failure")
- });
+ {
+ new ImportResult(new ImportDecision(new LocalMovie { Path = @"C:\TestPath\Droned.S01E01.mkv", Movie = _trackedDownload.RemoteMovie.Movie })),
+ new ImportResult(new ImportDecision(new LocalMovie { Path = @"C:\TestPath\Droned.S01E01.mkv" }), "Test Failure")
+ });
- Subject.Process(_trackedDownload);
+ Subject.Import(_trackedDownload);
- AssertCompletedDownload();
+ AssertImported();
}
[Test]
- public void should_mark_as_imported_if_the_download_can_be_tracked_using_the_source_seriesid()
+ public void should_mark_as_imported_if_the_download_can_be_tracked_using_the_source_movieid()
{
GivenABadlyNamedDownload();
Mocker.GetMock()
.Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
.Returns(new List
- {
- new ImportResult(new ImportDecision(new LocalMovie { Path = @"C:\TestPath\Droned.1998.mkv" }))
- });
+ {
+ new ImportResult(new ImportDecision(new LocalMovie { Path = @"C:\TestPath\Droned.S01E01.mkv", Movie = _trackedDownload.RemoteMovie.Movie }))
+ });
Mocker.GetMock()
.Setup(v => v.GetMovie(It.IsAny()))
.Returns(BuildRemoteMovie().Movie);
- Subject.Process(_trackedDownload);
+ Subject.Import(_trackedDownload);
- AssertCompletedDownload();
+ AssertImported();
}
- [Test]
- public void should_not_mark_as_imported_if_the_download_cannot_be_tracked_using_the_source_title_as_it_was_initiated_externally()
- {
- GivenABadlyNamedDownload();
-
- Mocker.GetMock()
- .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
- .Returns(new List
- {
- new ImportResult(new ImportDecision(new LocalMovie { Path = @"C:\TestPath\Droned.1998.mkv" }))
- });
-
- Mocker.GetMock()
- .Setup(s => s.MostRecentForDownloadId(It.Is(i => i == "1234")));
-
- Subject.Process(_trackedDownload);
-
- AssertNoCompletedDownload();
- }
-
- [Test]
- public void should_not_import_when_there_is_a_title_mismatch()
- {
- Mocker.GetMock()
- .Setup(s => s.GetMovie("Drone.1998"))
- .Returns((Movie)null);
-
- Subject.Process(_trackedDownload);
-
- AssertNoCompletedDownload();
- }
-
- [Test]
- public void should_mark_as_import_title_mismatch_if_ignore_warnings_is_true()
- {
- Mocker.GetMock()
- .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
- .Returns(new List
- {
- new ImportResult(new ImportDecision(new LocalMovie { Path = @"C:\TestPath\Droned.1998.mkv" }))
- });
-
- Subject.Process(_trackedDownload, true);
-
- AssertCompletedDownload();
- }
-
- [Test]
- public void should_warn_if_path_is_not_valid_for_windows()
- {
- WindowsOnly();
-
- _trackedDownload.DownloadItem.OutputPath = new OsPath(@"/invalid/Windows/Path");
-
- Subject.Process(_trackedDownload);
-
- AssertNoAttemptedImport();
- }
-
- [Test]
- public void should_warn_if_path_is_not_valid_for_linux()
- {
- PosixOnly();
-
- _trackedDownload.DownloadItem.OutputPath = new OsPath(@"C:\Invalid\Mono\Path");
-
- Subject.Process(_trackedDownload);
-
- AssertNoAttemptedImport();
- }
-
- private void AssertNoAttemptedImport()
- {
- Mocker.GetMock()
- .Verify(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never());
-
- AssertNoCompletedDownload();
- }
-
- private void AssertNoCompletedDownload()
+ private void AssertNotImported()
{
Mocker.GetMock()
.Verify(v => v.PublishEvent(It.IsAny()), Times.Never());
- _trackedDownload.State.Should().NotBe(TrackedDownloadStage.Imported);
+ _trackedDownload.State.Should().Be(TrackedDownloadState.ImportPending);
}
- private void AssertCompletedDownload()
+ private void AssertImported()
{
Mocker.GetMock()
.Verify(v => v.ProcessPath(_trackedDownload.DownloadItem.OutputPath.FullPath, ImportMode.Auto, _trackedDownload.RemoteMovie.Movie, _trackedDownload.DownloadItem), Times.Once());
@@ -373,7 +210,7 @@ namespace NzbDrone.Core.Test.Download
Mocker.GetMock()
.Verify(v => v.PublishEvent(It.IsAny()), Times.Once());
- _trackedDownload.State.Should().Be(TrackedDownloadStage.Imported);
+ _trackedDownload.State.Should().Be(TrackedDownloadState.Imported);
}
}
}
diff --git a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ProcessFixture.cs b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ProcessFixture.cs
new file mode 100644
index 000000000..9e97eecb5
--- /dev/null
+++ b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ProcessFixture.cs
@@ -0,0 +1,189 @@
+using System.Collections.Generic;
+using FizzWare.NBuilder;
+using FluentAssertions;
+using Moq;
+using NUnit.Framework;
+using NzbDrone.Common.Disk;
+using NzbDrone.Core.DecisionEngine;
+using NzbDrone.Core.Download;
+using NzbDrone.Core.Download.TrackedDownloads;
+using NzbDrone.Core.History;
+using NzbDrone.Core.MediaFiles;
+using NzbDrone.Core.MediaFiles.MovieImport;
+using NzbDrone.Core.Messaging.Events;
+using NzbDrone.Core.Movies;
+using NzbDrone.Core.Parser;
+using NzbDrone.Core.Parser.Model;
+using NzbDrone.Core.Test.Framework;
+using NzbDrone.Test.Common;
+
+namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
+{
+ [TestFixture]
+ public class ProcessFixture : CoreTest
+ {
+ private TrackedDownload _trackedDownload;
+
+ [SetUp]
+ public void Setup()
+ {
+ var completed = Builder.CreateNew()
+ .With(h => h.Status = DownloadItemStatus.Completed)
+ .With(h => h.OutputPath = new OsPath(@"C:\DropFolder\MyDownload".AsOsAgnostic()))
+ .With(h => h.Title = "Drone.S01E01.HDTV")
+ .Build();
+
+ var remoteMovie = BuildRemoteMovie();
+
+ _trackedDownload = Builder.CreateNew()
+ .With(c => c.State = TrackedDownloadState.Downloading)
+ .With(c => c.DownloadItem = completed)
+ .With(c => c.RemoteMovie = remoteMovie)
+ .Build();
+
+ Mocker.GetMock()
+ .SetupGet(c => c.Definition)
+ .Returns(new DownloadClientDefinition { Id = 1, Name = "testClient" });
+
+ Mocker.GetMock()
+ .Setup(c => c.Get(It.IsAny()))
+ .Returns(Mocker.GetMock().Object);
+
+ Mocker.GetMock()
+ .Setup(s => s.MostRecentForDownloadId(_trackedDownload.DownloadItem.DownloadId))
+ .Returns(new History.History());
+
+ Mocker.GetMock()
+ .Setup(s => s.GetMovie("Drone.S01E01.HDTV"))
+ .Returns(remoteMovie.Movie);
+ }
+
+ private RemoteMovie BuildRemoteMovie()
+ {
+ return new RemoteMovie
+ {
+ Movie = new Movie(),
+ };
+ }
+
+ private void GivenNoGrabbedHistory()
+ {
+ Mocker.GetMock()
+ .Setup(s => s.MostRecentForDownloadId(_trackedDownload.DownloadItem.DownloadId))
+ .Returns((History.History)null);
+ }
+
+ private void GivenMovieMatch()
+ {
+ Mocker.GetMock()
+ .Setup(s => s.GetMovie(It.IsAny()))
+ .Returns(_trackedDownload.RemoteMovie.Movie);
+ }
+
+ private void GivenABadlyNamedDownload()
+ {
+ _trackedDownload.DownloadItem.DownloadId = "1234";
+ _trackedDownload.DownloadItem.Title = "Droned Pilot"; // Set a badly named download
+ Mocker.GetMock()
+ .Setup(s => s.MostRecentForDownloadId(It.Is(i => i == "1234")))
+ .Returns(new History.History() { SourceTitle = "Droned S01E01" });
+
+ Mocker.GetMock()
+ .Setup(s => s.GetMovie(It.IsAny()))
+ .Returns((Movie)null);
+
+ Mocker.GetMock()
+ .Setup(s => s.GetMovie("Droned S01E01"))
+ .Returns(BuildRemoteMovie().Movie);
+ }
+
+ [TestCase(DownloadItemStatus.Downloading)]
+ [TestCase(DownloadItemStatus.Failed)]
+ [TestCase(DownloadItemStatus.Queued)]
+ [TestCase(DownloadItemStatus.Paused)]
+ [TestCase(DownloadItemStatus.Warning)]
+ public void should_not_process_if_download_status_isnt_completed(DownloadItemStatus status)
+ {
+ _trackedDownload.DownloadItem.Status = status;
+
+ Subject.Check(_trackedDownload);
+
+ AssertNotReadyToImport();
+ }
+
+ [Test]
+ public void should_not_process_if_matching_history_is_not_found_and_no_category_specified()
+ {
+ _trackedDownload.DownloadItem.Category = null;
+ GivenNoGrabbedHistory();
+
+ Subject.Check(_trackedDownload);
+
+ AssertNotReadyToImport();
+ }
+
+ [Test]
+ public void should_process_if_matching_history_is_not_found_but_category_specified()
+ {
+ _trackedDownload.DownloadItem.Category = "tv";
+ GivenNoGrabbedHistory();
+ GivenMovieMatch();
+
+ Subject.Check(_trackedDownload);
+
+ AssertReadyToImport();
+ }
+
+ [Test]
+ public void should_not_process_if_output_path_is_empty()
+ {
+ _trackedDownload.DownloadItem.OutputPath = default(OsPath);
+
+ Subject.Check(_trackedDownload);
+
+ AssertNotReadyToImport();
+ }
+
+ [Test]
+ public void should_not_process_if_the_download_cannot_be_tracked_using_the_source_title_as_it_was_initiated_externally()
+ {
+ GivenABadlyNamedDownload();
+
+ Mocker.GetMock()
+ .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
+ .Returns(new List
+ {
+ new ImportResult(new ImportDecision(new LocalMovie { Path = @"C:\TestPath\Droned.S01E01.mkv" }))
+ });
+
+ Mocker.GetMock()
+ .Setup(s => s.MostRecentForDownloadId(It.Is(i => i == "1234")));
+
+ Subject.Check(_trackedDownload);
+
+ AssertNotReadyToImport();
+ }
+
+ [Test]
+ public void should_not_process_when_there_is_a_title_mismatch()
+ {
+ Mocker.GetMock()
+ .Setup(s => s.GetMovie("Drone.S01E01.HDTV"))
+ .Returns((Movie)null);
+
+ Subject.Check(_trackedDownload);
+
+ AssertNotReadyToImport();
+ }
+
+ private void AssertNotReadyToImport()
+ {
+ _trackedDownload.State.Should().NotBe(TrackedDownloadState.ImportPending);
+ }
+
+ private void AssertReadyToImport()
+ {
+ _trackedDownload.State.Should().Be(TrackedDownloadState.ImportPending);
+ }
+ }
+}
diff --git a/src/NzbDrone.Core.Test/Download/FailedDownloadServiceTests/ProcessFailedFixture.cs b/src/NzbDrone.Core.Test/Download/FailedDownloadServiceTests/ProcessFailedFixture.cs
new file mode 100644
index 000000000..b1bcd9808
--- /dev/null
+++ b/src/NzbDrone.Core.Test/Download/FailedDownloadServiceTests/ProcessFailedFixture.cs
@@ -0,0 +1,100 @@
+using System.Collections.Generic;
+using FizzWare.NBuilder;
+using FluentAssertions;
+using Moq;
+using NUnit.Framework;
+using NzbDrone.Common.Disk;
+using NzbDrone.Core.Download;
+using NzbDrone.Core.Download.TrackedDownloads;
+using NzbDrone.Core.History;
+using NzbDrone.Core.Messaging.Events;
+using NzbDrone.Core.Movies;
+using NzbDrone.Core.Parser.Model;
+using NzbDrone.Core.Test.Framework;
+using NzbDrone.Test.Common;
+
+namespace NzbDrone.Core.Test.Download.FailedDownloadServiceTests
+{
+ [TestFixture]
+ public class ProcessFailedFixture : CoreTest
+ {
+ private TrackedDownload _trackedDownload;
+ private List _grabHistory;
+
+ [SetUp]
+ public void Setup()
+ {
+ var completed = Builder.CreateNew()
+ .With(h => h.Status = DownloadItemStatus.Completed)
+ .With(h => h.OutputPath = new OsPath(@"C:\DropFolder\MyDownload".AsOsAgnostic()))
+ .With(h => h.Title = "Drone.S01E01.HDTV")
+ .Build();
+
+ _grabHistory = Builder.CreateListOfSize(2).BuildList();
+
+ var remoteMovie = new RemoteMovie
+ {
+ Movie = new Movie()
+ };
+
+ _trackedDownload = Builder.CreateNew()
+ .With(c => c.State = TrackedDownloadState.FailedPending)
+ .With(c => c.DownloadItem = completed)
+ .With(c => c.RemoteMovie = remoteMovie)
+ .Build();
+
+ Mocker.GetMock()
+ .Setup(s => s.Find(_trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed))
+ .Returns(_grabHistory);
+ }
+
+ [Test]
+ public void should_mark_failed_if_encrypted()
+ {
+ _trackedDownload.DownloadItem.IsEncrypted = true;
+
+ Subject.ProcessFailed(_trackedDownload);
+
+ AssertDownloadFailed();
+ }
+
+ [Test]
+ public void should_mark_failed_if_download_item_is_failed()
+ {
+ _trackedDownload.DownloadItem.Status = DownloadItemStatus.Failed;
+
+ Subject.ProcessFailed(_trackedDownload);
+
+ AssertDownloadFailed();
+ }
+
+ [Test]
+ public void should_include_tracked_download_in_message()
+ {
+ _trackedDownload.DownloadItem.Status = DownloadItemStatus.Failed;
+
+ Subject.ProcessFailed(_trackedDownload);
+
+ Mocker.GetMock()
+ .Verify(v => v.PublishEvent(It.Is(c => c.TrackedDownload != null)), Times.Once());
+
+ AssertDownloadFailed();
+ }
+
+ private void AssertDownloadNotFailed()
+ {
+ Mocker.GetMock()
+ .Verify(v => v.PublishEvent(It.IsAny()), Times.Never());
+
+ _trackedDownload.State.Should().NotBe(TrackedDownloadState.Failed);
+ }
+
+ private void AssertDownloadFailed()
+ {
+ Mocker.GetMock()
+ .Verify(v => v.PublishEvent(It.IsAny()), Times.Once());
+
+ _trackedDownload.State.Should().Be(TrackedDownloadState.Failed);
+ }
+ }
+}
diff --git a/src/NzbDrone.Core.Test/Download/FailedDownloadServiceFixture.cs b/src/NzbDrone.Core.Test/Download/FailedDownloadServiceTests/ProcessFixture.cs
similarity index 69%
rename from src/NzbDrone.Core.Test/Download/FailedDownloadServiceFixture.cs
rename to src/NzbDrone.Core.Test/Download/FailedDownloadServiceTests/ProcessFixture.cs
index 14487be50..009383586 100644
--- a/src/NzbDrone.Core.Test/Download/FailedDownloadServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/Download/FailedDownloadServiceTests/ProcessFixture.cs
@@ -13,10 +13,10 @@ using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
-namespace NzbDrone.Core.Test.Download
+namespace NzbDrone.Core.Test.Download.FailedDownloadServiceTests
{
[TestFixture]
- public class FailedDownloadServiceFixture : CoreTest
+ public class ProcessFixture : CoreTest
{
private TrackedDownload _trackedDownload;
private List _grabHistory;
@@ -32,15 +32,15 @@ namespace NzbDrone.Core.Test.Download
_grabHistory = Builder.CreateListOfSize(2).BuildList();
- var remoteEpisode = new RemoteMovie
+ var remoteMovie = new RemoteMovie
{
Movie = new Movie(),
};
_trackedDownload = Builder.CreateNew()
- .With(c => c.State = TrackedDownloadStage.Downloading)
+ .With(c => c.State = TrackedDownloadState.Downloading)
.With(c => c.DownloadItem = completed)
- .With(c => c.RemoteMovie = remoteEpisode)
+ .With(c => c.RemoteMovie = remoteMovie)
.Build();
Mocker.GetMock()
@@ -60,7 +60,7 @@ namespace NzbDrone.Core.Test.Download
{
GivenNoGrabbedHistory();
- Subject.Process(_trackedDownload);
+ Subject.Check(_trackedDownload);
AssertDownloadNotFailed();
}
@@ -71,7 +71,7 @@ namespace NzbDrone.Core.Test.Download
_trackedDownload.DownloadItem.Status = DownloadItemStatus.Failed;
GivenNoGrabbedHistory();
- Subject.Process(_trackedDownload);
+ Subject.Check(_trackedDownload);
_trackedDownload.StatusMessages.Should().NotBeEmpty();
}
@@ -82,50 +82,17 @@ namespace NzbDrone.Core.Test.Download
_trackedDownload.DownloadItem.Status = DownloadItemStatus.Failed;
GivenNoGrabbedHistory();
- Subject.Process(_trackedDownload);
+ Subject.Check(_trackedDownload);
_trackedDownload.StatusMessages.Should().NotBeEmpty();
}
- [Test]
- public void should_mark_failed_if_encrypted()
- {
- _trackedDownload.DownloadItem.IsEncrypted = true;
-
- Subject.Process(_trackedDownload);
-
- AssertDownloadFailed();
- }
-
- [Test]
- public void should_mark_failed_if_download_item_is_failed()
- {
- _trackedDownload.DownloadItem.Status = DownloadItemStatus.Failed;
-
- Subject.Process(_trackedDownload);
-
- AssertDownloadFailed();
- }
-
- [Test]
- public void should_include_tracked_download_in_message()
- {
- _trackedDownload.DownloadItem.Status = DownloadItemStatus.Failed;
-
- Subject.Process(_trackedDownload);
-
- Mocker.GetMock()
- .Verify(v => v.PublishEvent(It.Is(c => c.TrackedDownload != null)), Times.Once());
-
- AssertDownloadFailed();
- }
-
private void AssertDownloadNotFailed()
{
Mocker.GetMock()
.Verify(v => v.PublishEvent(It.IsAny()), Times.Never());
- _trackedDownload.State.Should().NotBe(TrackedDownloadStage.DownloadFailed);
+ _trackedDownload.State.Should().NotBe(TrackedDownloadState.Failed);
}
private void AssertDownloadFailed()
@@ -133,7 +100,7 @@ namespace NzbDrone.Core.Test.Download
Mocker.GetMock()
.Verify(v => v.PublishEvent(It.IsAny()), Times.Once());
- _trackedDownload.State.Should().Be(TrackedDownloadStage.DownloadFailed);
+ _trackedDownload.State.Should().Be(TrackedDownloadState.Failed);
}
}
}
diff --git a/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadAlreadyImportedFixture.cs b/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadAlreadyImportedFixture.cs
new file mode 100644
index 000000000..a571da5fb
--- /dev/null
+++ b/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadAlreadyImportedFixture.cs
@@ -0,0 +1,76 @@
+using System.Collections.Generic;
+using FizzWare.NBuilder;
+using FluentAssertions;
+using NUnit.Framework;
+using NzbDrone.Core.Download.TrackedDownloads;
+using NzbDrone.Core.History;
+using NzbDrone.Core.Movies;
+using NzbDrone.Core.Parser.Model;
+using NzbDrone.Core.Test.Framework;
+
+namespace NzbDrone.Core.Test.Download.TrackedDownloads
+{
+ [TestFixture]
+ public class TrackedDownloadAlreadyImportedFixture : CoreTest
+ {
+ private Movie _movie;
+ private TrackedDownload _trackedDownload;
+ private List _historyItems;
+
+ [SetUp]
+ public void Setup()
+ {
+ _movie = Builder.CreateNew().Build();
+
+ var remoteMovie = Builder.CreateNew()
+ .With(r => r.Movie = _movie)
+ .Build();
+
+ _trackedDownload = Builder.CreateNew()
+ .With(t => t.RemoteMovie = remoteMovie)
+ .Build();
+
+ _historyItems = new List();
+ }
+
+ public void GivenHistoryForMovie(Movie movie, params HistoryEventType[] eventTypes)
+ {
+ foreach (var eventType in eventTypes)
+ {
+ _historyItems.Add(
+ Builder.CreateNew()
+ .With(h => h.MovieId = movie.Id)
+ .With(h => h.EventType = eventType)
+ .Build());
+ }
+ }
+
+ [Test]
+ public void should_return_false_if_there_is_no_history()
+ {
+ Subject.IsImported(_trackedDownload, _historyItems)
+ .Should()
+ .BeFalse();
+ }
+
+ [Test]
+ public void should_return_false_if_single_movie_download_is_not_imported()
+ {
+ GivenHistoryForMovie(_movie, HistoryEventType.Grabbed);
+
+ Subject.IsImported(_trackedDownload, _historyItems)
+ .Should()
+ .BeFalse();
+ }
+
+ [Test]
+ public void should_return_true_if_single_movie_download_is_imported()
+ {
+ GivenHistoryForMovie(_movie, HistoryEventType.DownloadFolderImported, HistoryEventType.Grabbed);
+
+ Subject.IsImported(_trackedDownload, _historyItems)
+ .Should()
+ .BeTrue();
+ }
+ }
+}
diff --git a/src/NzbDrone.Core.Test/MediaFiles/DownloadedMoviesCommandServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/DownloadedMoviesCommandServiceFixture.cs
index 943ce4f2f..aa8c017fe 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/DownloadedMoviesCommandServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/DownloadedMoviesCommandServiceFixture.cs
@@ -48,7 +48,7 @@ namespace NzbDrone.Core.Test.MediaFiles
{
DownloadItem = downloadItem,
RemoteMovie = remoteMovie,
- State = TrackedDownloadStage.Downloading
+ State = TrackedDownloadState.Downloading
};
}
diff --git a/src/NzbDrone.Core.Test/Messaging/Commands/CommandQueueManagerFixture.cs b/src/NzbDrone.Core.Test/Messaging/Commands/CommandQueueManagerFixture.cs
index 68ec47951..d0bf4a26e 100644
--- a/src/NzbDrone.Core.Test/Messaging/Commands/CommandQueueManagerFixture.cs
+++ b/src/NzbDrone.Core.Test/Messaging/Commands/CommandQueueManagerFixture.cs
@@ -41,7 +41,7 @@ namespace NzbDrone.Core.Test.Messaging.Commands
[Test]
public void should_not_remove_commands_for_five_minutes_after_they_end()
{
- var command = Subject.Push(new CheckForFinishedDownloadCommand());
+ var command = Subject.Push(new RefreshMonitoredDownloadsCommand());
// Start the command to mimic CommandQueue's behaviour
command.StartedAt = DateTime.Now;
diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs
index b65e6ee25..be362d03e 100644
--- a/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs
+++ b/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs
@@ -2,6 +2,7 @@ using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.CustomFormats;
+using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Queue;
@@ -41,6 +42,14 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
var remoteMovie = queueItem.RemoteMovie;
var qualityProfile = subject.Movie.Profile;
+ // To avoid a race make sure it's not FailedPending (failed awaiting removal/search).
+ // Failed items (already searching for a replacement) won't be part of the queue since
+ // it's a copy, of the tracked download, not a reference.
+ if (queueItem.TrackedDownloadState == TrackedDownloadState.FailedPending)
+ {
+ continue;
+ }
+
var customFormats = _formatService.ParseCustomFormat(remoteMovie.ParsedMovieInfo);
_logger.Debug("Checking if existing release in queue meets cutoff. Queued quality is: {0} - {1}",
diff --git a/src/NzbDrone.Core/Download/CheckForFinishedDownloadCommand.cs b/src/NzbDrone.Core/Download/CheckForFinishedDownloadCommand.cs
index 71f7f3d5e..a038f1a97 100644
--- a/src/NzbDrone.Core/Download/CheckForFinishedDownloadCommand.cs
+++ b/src/NzbDrone.Core/Download/CheckForFinishedDownloadCommand.cs
@@ -4,6 +4,5 @@ namespace NzbDrone.Core.Download
{
public class CheckForFinishedDownloadCommand : Command
{
- public override bool RequiresDiskAccess => true;
}
}
diff --git a/src/NzbDrone.Core/Download/CompletedDownloadService.cs b/src/NzbDrone.Core/Download/CompletedDownloadService.cs
index d23559703..ec16784e7 100644
--- a/src/NzbDrone.Core/Download/CompletedDownloadService.cs
+++ b/src/NzbDrone.Core/Download/CompletedDownloadService.cs
@@ -16,103 +16,131 @@ namespace NzbDrone.Core.Download
{
public interface ICompletedDownloadService
{
- void Process(TrackedDownload trackedDownload, bool ignoreWarnings = false);
+ void Check(TrackedDownload trackedDownload);
+ void Import(TrackedDownload trackedDownload);
}
public class CompletedDownloadService : ICompletedDownloadService
{
- private readonly IConfigService _configService;
private readonly IEventAggregator _eventAggregator;
private readonly IHistoryService _historyService;
private readonly IDownloadedMovieImportService _downloadedMovieImportService;
private readonly IParsingService _parsingService;
private readonly IMovieService _movieService;
- private readonly Logger _logger;
+ private readonly ITrackedDownloadAlreadyImported _trackedDownloadAlreadyImported;
- public CompletedDownloadService(IConfigService configService,
- IEventAggregator eventAggregator,
+ public CompletedDownloadService(IEventAggregator eventAggregator,
IHistoryService historyService,
IDownloadedMovieImportService downloadedMovieImportService,
IParsingService parsingService,
IMovieService movieService,
- Logger logger)
+ ITrackedDownloadAlreadyImported trackedDownloadAlreadyImported)
{
- _configService = configService;
_eventAggregator = eventAggregator;
_historyService = historyService;
_downloadedMovieImportService = downloadedMovieImportService;
_parsingService = parsingService;
_movieService = movieService;
- _logger = logger;
+ _trackedDownloadAlreadyImported = trackedDownloadAlreadyImported;
}
- public void Process(TrackedDownload trackedDownload, bool ignoreWarnings = false)
+ public void Check(TrackedDownload trackedDownload)
{
if (trackedDownload.DownloadItem.Status != DownloadItemStatus.Completed)
{
return;
}
- if (!ignoreWarnings)
+ // Only process tracked downloads that are still downloading
+ if (trackedDownload.State != TrackedDownloadState.Downloading)
{
- var historyItem = _historyService.MostRecentForDownloadId(trackedDownload.DownloadItem.DownloadId);
+ return;
+ }
- if (historyItem == null && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
+ var historyItem = _historyService.MostRecentForDownloadId(trackedDownload.DownloadItem.DownloadId);
+
+ if (historyItem == null && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
+ {
+ trackedDownload.Warn("Download wasn't grabbed by Sonarr and not in a category, Skipping.");
+ return;
+ }
+
+ var downloadItemOutputPath = trackedDownload.DownloadItem.OutputPath;
+
+ if (downloadItemOutputPath.IsEmpty)
+ {
+ trackedDownload.Warn("Download doesn't contain intermediate path, Skipping.");
+ return;
+ }
+
+ if ((OsInfo.IsWindows && !downloadItemOutputPath.IsWindowsPath) ||
+ (OsInfo.IsNotWindows && !downloadItemOutputPath.IsUnixPath))
+ {
+ trackedDownload.Warn("[{0}] is not a valid local path. You may need a Remote Path Mapping.", downloadItemOutputPath);
+ return;
+ }
+
+ var movie = _parsingService.GetMovie(trackedDownload.DownloadItem.Title);
+
+ if (movie == null)
+ {
+ if (historyItem != null)
{
- trackedDownload.Warn("Download wasn't grabbed by Radarr and not in a category, Skipping.");
- return;
+ movie = _movieService.GetMovie(historyItem.MovieId);
}
- var downloadItemOutputPath = trackedDownload.DownloadItem.OutputPath;
-
- if (downloadItemOutputPath.IsEmpty)
- {
- trackedDownload.Warn("Download doesn't contain intermediate path, Skipping.");
- return;
- }
-
- if ((OsInfo.IsWindows && !downloadItemOutputPath.IsWindowsPath) ||
- (OsInfo.IsNotWindows && !downloadItemOutputPath.IsUnixPath))
- {
- trackedDownload.Warn("[{0}] is not a valid local path. You may need a Remote Path Mapping.", downloadItemOutputPath);
- return;
- }
-
- var movie = _parsingService.GetMovie(trackedDownload.DownloadItem.Title);
if (movie == null)
{
- if (historyItem != null)
- {
- movie = _movieService.GetMovie(historyItem.MovieId);
- }
-
- if (movie == null)
- {
- trackedDownload.Warn("Movie title mismatch, automatic import is not possible.");
- return;
- }
+ trackedDownload.Warn("Series title mismatch, automatic import is not possible.");
+ return;
}
}
- Import(trackedDownload);
+ trackedDownload.State = TrackedDownloadState.ImportPending;
}
- private void Import(TrackedDownload trackedDownload)
+ public void Import(TrackedDownload trackedDownload)
{
+ trackedDownload.State = TrackedDownloadState.Importing;
+
var outputPath = trackedDownload.DownloadItem.OutputPath.FullPath;
var importResults = _downloadedMovieImportService.ProcessPath(outputPath, ImportMode.Auto, trackedDownload.RemoteMovie.Movie, trackedDownload.DownloadItem);
+ var allMoviesImported = importResults.Where(c => c.Result == ImportResultType.Imported)
+ .Select(c => c.ImportDecision.LocalMovie.Movie)
+ .Count() >= 1;
+
+ if (allMoviesImported)
+ {
+ trackedDownload.State = TrackedDownloadState.Imported;
+ _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
+ return;
+ }
+
+ // Double check if all episodes were imported by checking the history if at least one
+ // file was imported. This will allow the decision engine to reject already imported
+ // episode files and still mark the download complete when all files are imported.
+ if (importResults.Any(c => c.Result == ImportResultType.Imported))
+ {
+ var historyItems = _historyService.FindByDownloadId(trackedDownload.DownloadItem.DownloadId)
+ .OrderByDescending(h => h.Date)
+ .ToList();
+
+ var allEpisodesImportedInHistory = _trackedDownloadAlreadyImported.IsImported(trackedDownload, historyItems);
+
+ if (allEpisodesImportedInHistory)
+ {
+ trackedDownload.State = TrackedDownloadState.Imported;
+ _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
+ return;
+ }
+ }
+
+ trackedDownload.State = TrackedDownloadState.ImportPending;
+
if (importResults.Empty())
{
trackedDownload.Warn("No files found are eligible for import in {0}", outputPath);
- return;
- }
-
- if (importResults.Count(c => c.Result == ImportResultType.Imported) >= 1)
- {
- trackedDownload.State = TrackedDownloadStage.Imported;
- _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
- return;
}
if (importResults.Any(c => c.Result != ImportResultType.Imported))
diff --git a/src/NzbDrone.Core/Download/DownloadIgnoredEvent.cs b/src/NzbDrone.Core/Download/DownloadIgnoredEvent.cs
new file mode 100644
index 000000000..023218336
--- /dev/null
+++ b/src/NzbDrone.Core/Download/DownloadIgnoredEvent.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Languages;
+using NzbDrone.Core.Qualities;
+
+namespace NzbDrone.Core.Download
+{
+ public class DownloadIgnoredEvent : IEvent
+ {
+ public int MovieId { get; set; }
+ public List Languages { get; set; }
+ public QualityModel Quality { get; set; }
+ public string SourceTitle { get; set; }
+ public string DownloadClient { get; set; }
+ public string DownloadId { get; set; }
+ public string Message { get; set; }
+ }
+}
diff --git a/src/NzbDrone.Core/Download/DownloadProcessingService.cs b/src/NzbDrone.Core/Download/DownloadProcessingService.cs
new file mode 100644
index 000000000..fd96a49a0
--- /dev/null
+++ b/src/NzbDrone.Core/Download/DownloadProcessingService.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NLog;
+using NzbDrone.Core.Configuration;
+using NzbDrone.Core.Download.TrackedDownloads;
+using NzbDrone.Core.Messaging.Commands;
+using NzbDrone.Core.Messaging.Events;
+
+namespace NzbDrone.Core.Download
+{
+ public class DownloadProcessingService : IExecute
+ {
+ private readonly IConfigService _configService;
+ private readonly ICompletedDownloadService _completedDownloadService;
+ private readonly IFailedDownloadService _failedDownloadService;
+ private readonly ITrackedDownloadService _trackedDownloadService;
+ private readonly IEventAggregator _eventAggregator;
+ private readonly Logger _logger;
+
+ public DownloadProcessingService(IConfigService configService,
+ ICompletedDownloadService completedDownloadService,
+ IFailedDownloadService failedDownloadService,
+ ITrackedDownloadService trackedDownloadService,
+ IEventAggregator eventAggregator,
+ Logger logger)
+ {
+ _configService = configService;
+ _completedDownloadService = completedDownloadService;
+ _failedDownloadService = failedDownloadService;
+ _trackedDownloadService = trackedDownloadService;
+ _eventAggregator = eventAggregator;
+ _logger = logger;
+ }
+
+ private void RemoveCompletedDownloads(List trackedDownloads)
+ {
+ foreach (var trackedDownload in trackedDownloads.Where(c => c.DownloadItem.CanBeRemoved && c.State == TrackedDownloadState.Imported))
+ {
+ _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
+ }
+ }
+
+ public void Execute(ProcessMonitoredDownloadsCommand message)
+ {
+ var enableCompletedDownloadHandling = _configService.EnableCompletedDownloadHandling;
+ var trackedDownloads = _trackedDownloadService.GetTrackedDownloads();
+
+ foreach (var trackedDownload in trackedDownloads)
+ {
+ try
+ {
+ if (trackedDownload.State == TrackedDownloadState.FailedPending)
+ {
+ _failedDownloadService.ProcessFailed(trackedDownload);
+ }
+
+ if (enableCompletedDownloadHandling && trackedDownload.State == TrackedDownloadState.ImportPending)
+ {
+ _completedDownloadService.Import(trackedDownload);
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.Debug(e, "Failed to process download: {0}", trackedDownload.DownloadItem.Title);
+ }
+ }
+
+ if (enableCompletedDownloadHandling && _configService.RemoveCompletedDownloads)
+ {
+ // Remove tracked downloads that are now complete
+ RemoveCompletedDownloads(trackedDownloads);
+ }
+
+ _eventAggregator.PublishEvent(new DownloadsProcessedEvent());
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Download/DownloadsProcessedEvent.cs b/src/NzbDrone.Core/Download/DownloadsProcessedEvent.cs
new file mode 100644
index 000000000..2dea12b8d
--- /dev/null
+++ b/src/NzbDrone.Core/Download/DownloadsProcessedEvent.cs
@@ -0,0 +1,11 @@
+using NzbDrone.Common.Messaging;
+
+namespace NzbDrone.Core.Download
+{
+ public class DownloadsProcessedEvent : IEvent
+ {
+ public DownloadsProcessedEvent()
+ {
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Download/FailedDownloadService.cs b/src/NzbDrone.Core/Download/FailedDownloadService.cs
index b983cdde7..a1d9cb271 100644
--- a/src/NzbDrone.Core/Download/FailedDownloadService.cs
+++ b/src/NzbDrone.Core/Download/FailedDownloadService.cs
@@ -11,7 +11,8 @@ namespace NzbDrone.Core.Download
{
void MarkAsFailed(int historyId);
void MarkAsFailed(string downloadId);
- void Process(TrackedDownload trackedDownload);
+ void Check(TrackedDownload trackedDownload);
+ void ProcessFailed(TrackedDownload trackedDownload);
}
public class FailedDownloadService : IFailedDownloadService
@@ -52,23 +53,20 @@ namespace NzbDrone.Core.Download
}
}
- public void Process(TrackedDownload trackedDownload)
+ public void Check(TrackedDownload trackedDownload)
{
- string failure = null;
-
- if (trackedDownload.DownloadItem.IsEncrypted)
+ // Only process tracked downloads that are still downloading
+ if (trackedDownload.State != TrackedDownloadState.Downloading)
{
- failure = "Encrypted download detected";
- }
- else if (trackedDownload.DownloadItem.Status == DownloadItemStatus.Failed)
- {
- failure = trackedDownload.DownloadItem.Message ?? "Failed download detected";
+ return;
}
- if (failure != null)
+ if (trackedDownload.DownloadItem.IsEncrypted ||
+ trackedDownload.DownloadItem.Status == DownloadItemStatus.Failed)
{
- var grabbedItems = _historyService.Find(trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed)
- .ToList();
+ var grabbedItems = _historyService
+ .Find(trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed)
+ .ToList();
if (grabbedItems.Empty())
{
@@ -76,11 +74,41 @@ namespace NzbDrone.Core.Download
return;
}
- trackedDownload.State = TrackedDownloadStage.DownloadFailed;
- PublishDownloadFailedEvent(grabbedItems, failure, trackedDownload);
+ trackedDownload.State = TrackedDownloadState.FailedPending;
}
}
+ public void ProcessFailed(TrackedDownload trackedDownload)
+ {
+ if (trackedDownload.State != TrackedDownloadState.FailedPending)
+ {
+ return;
+ }
+
+ var grabbedItems = _historyService
+ .Find(trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed)
+ .ToList();
+
+ if (grabbedItems.Empty())
+ {
+ return;
+ }
+
+ var failure = "Failed download detected";
+
+ if (trackedDownload.DownloadItem.IsEncrypted)
+ {
+ failure = "Encrypted download detected";
+ }
+ else if (trackedDownload.DownloadItem.Status == DownloadItemStatus.Failed && trackedDownload.DownloadItem.Message.IsNotNullOrWhiteSpace())
+ {
+ failure = trackedDownload.DownloadItem.Message;
+ }
+
+ trackedDownload.State = TrackedDownloadState.Failed;
+ PublishDownloadFailedEvent(grabbedItems, failure, trackedDownload);
+ }
+
private void PublishDownloadFailedEvent(List historyItems, string message, TrackedDownload trackedDownload = null)
{
var historyItem = historyItems.First();
diff --git a/src/NzbDrone.Core/Download/IgnoredDownloadService.cs b/src/NzbDrone.Core/Download/IgnoredDownloadService.cs
new file mode 100644
index 000000000..efedd0896
--- /dev/null
+++ b/src/NzbDrone.Core/Download/IgnoredDownloadService.cs
@@ -0,0 +1,49 @@
+using NLog;
+using NzbDrone.Core.Download.TrackedDownloads;
+using NzbDrone.Core.Messaging.Events;
+
+namespace NzbDrone.Core.Download
+{
+ public interface IIgnoredDownloadService
+ {
+ bool IgnoreDownload(TrackedDownload trackedDownload);
+ }
+
+ public class IgnoredDownloadService : IIgnoredDownloadService
+ {
+ private readonly IEventAggregator _eventAggregator;
+ private readonly Logger _logger;
+
+ public IgnoredDownloadService(IEventAggregator eventAggregator,
+ Logger logger)
+ {
+ _eventAggregator = eventAggregator;
+ _logger = logger;
+ }
+
+ public bool IgnoreDownload(TrackedDownload trackedDownload)
+ {
+ var movie = trackedDownload.RemoteMovie.Movie;
+
+ if (movie == null)
+ {
+ _logger.Warn("Unable to ignore download for unknown movie");
+ return false;
+ }
+
+ var downloadIgnoredEvent = new DownloadIgnoredEvent
+ {
+ MovieId = movie.Id,
+ Languages = trackedDownload.RemoteMovie.ParsedMovieInfo.Languages,
+ Quality = trackedDownload.RemoteMovie.ParsedMovieInfo.Quality,
+ SourceTitle = trackedDownload.DownloadItem.Title,
+ DownloadClient = trackedDownload.DownloadItem.DownloadClient,
+ DownloadId = trackedDownload.DownloadItem.DownloadId,
+ Message = "Manually ignored"
+ };
+
+ _eventAggregator.PublishEvent(downloadIgnoredEvent);
+ return true;
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Download/ProcessMonitoredDownloadsCommand.cs b/src/NzbDrone.Core/Download/ProcessMonitoredDownloadsCommand.cs
new file mode 100644
index 000000000..c3c934031
--- /dev/null
+++ b/src/NzbDrone.Core/Download/ProcessMonitoredDownloadsCommand.cs
@@ -0,0 +1,9 @@
+using NzbDrone.Core.Messaging.Commands;
+
+namespace NzbDrone.Core.Download
+{
+ public class ProcessMonitoredDownloadsCommand : Command
+ {
+ public override bool RequiresDiskAccess => true;
+ }
+}
diff --git a/src/NzbDrone.Core/Download/RedownloadFailedDownloadService.cs b/src/NzbDrone.Core/Download/RedownloadFailedDownloadService.cs
index d73963114..9f8565f7c 100644
--- a/src/NzbDrone.Core/Download/RedownloadFailedDownloadService.cs
+++ b/src/NzbDrone.Core/Download/RedownloadFailedDownloadService.cs
@@ -2,31 +2,30 @@ using System.Collections.Generic;
using NLog;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.IndexerSearch;
+using NzbDrone.Core.Messaging;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Movies;
namespace NzbDrone.Core.Download
{
- public class RedownloadFailedDownloadService : IHandleAsync
+ public class RedownloadFailedDownloadService : IHandle
{
private readonly IConfigService _configService;
- private readonly IMovieService _movieService;
private readonly IManageCommandQueue _commandQueueManager;
private readonly Logger _logger;
public RedownloadFailedDownloadService(IConfigService configService,
- IMovieService movieService,
IManageCommandQueue commandQueueManager,
Logger logger)
{
_configService = configService;
- _movieService = movieService;
_commandQueueManager = commandQueueManager;
_logger = logger;
}
- public void HandleAsync(DownloadFailedEvent message)
+ [EventHandleOrder(EventHandleOrder.Last)]
+ public void Handle(DownloadFailedEvent message)
{
if (!_configService.AutoRedownloadFailed)
{
diff --git a/src/NzbDrone.Core/Download/RefreshDownloadsCommand.cs b/src/NzbDrone.Core/Download/RefreshDownloadsCommand.cs
new file mode 100644
index 000000000..b4b516b61
--- /dev/null
+++ b/src/NzbDrone.Core/Download/RefreshDownloadsCommand.cs
@@ -0,0 +1,8 @@
+using NzbDrone.Core.Messaging.Commands;
+
+namespace NzbDrone.Core.Download
+{
+ public class RefreshMonitoredDownloadsCommand : Command
+ {
+ }
+}
diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/DownloadMonitoringService.cs b/src/NzbDrone.Core/Download/TrackedDownloads/DownloadMonitoringService.cs
index 7d4998dc3..6e41c24ab 100644
--- a/src/NzbDrone.Core/Download/TrackedDownloads/DownloadMonitoringService.cs
+++ b/src/NzbDrone.Core/Download/TrackedDownloads/DownloadMonitoringService.cs
@@ -11,9 +11,11 @@ using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Download.TrackedDownloads
{
- public class DownloadMonitoringService : IExecute,
+ public class DownloadMonitoringService : IExecute,
+ IExecute,
IHandle,
IHandle,
+ IHandle,
IHandle
{
private readonly IProvideDownloadClient _downloadClientProvider;
@@ -49,7 +51,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
private void QueueRefresh()
{
- _manageCommandQueue.Push(new CheckForFinishedDownloadCommand());
+ _manageCommandQueue.Push(new RefreshMonitoredDownloadsCommand());
}
private void Refresh()
@@ -70,6 +72,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
_trackedDownloadService.UpdateTrackable(trackedDownloads);
_eventAggregator.PublishEvent(new TrackedDownloadRefreshedEvent(trackedDownloads));
+ _manageCommandQueue.Push(new ProcessMonitoredDownloadsCommand());
}
finally
{
@@ -79,70 +82,54 @@ namespace NzbDrone.Core.Download.TrackedDownloads
private List ProcessClientDownloads(IDownloadClient downloadClient)
{
- List downloadClientHistory = new List();
+ var downloadClientItems = new List();
var trackedDownloads = new List();
try
{
- downloadClientHistory = downloadClient.GetItems().ToList();
+ downloadClientItems = downloadClient.GetItems().ToList();
}
catch (Exception ex)
{
_logger.Warn(ex, "Unable to retrieve queue and history items from " + downloadClient.Definition.Name);
}
- foreach (var downloadItem in downloadClientHistory)
+ foreach (var downloadItem in downloadClientItems)
{
- var newItems = ProcessClientItems(downloadClient, downloadItem);
- trackedDownloads.AddRange(newItems);
- }
-
- if (_configService.EnableCompletedDownloadHandling && _configService.RemoveCompletedDownloads)
- {
- RemoveCompletedDownloads(trackedDownloads);
+ var item = ProcessClientItem(downloadClient, downloadItem);
+ trackedDownloads.AddIfNotNull(item);
}
return trackedDownloads;
}
- private void RemoveCompletedDownloads(List trackedDownloads)
+ private TrackedDownload ProcessClientItem(IDownloadClient downloadClient, DownloadClientItem downloadItem)
{
- foreach (var trackedDownload in trackedDownloads.Where(c => c.DownloadItem.CanBeRemoved && c.State == TrackedDownloadStage.Imported))
- {
- _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
- }
- }
-
- private List ProcessClientItems(IDownloadClient downloadClient, DownloadClientItem downloadItem)
- {
- var trackedDownloads = new List();
try
{
var trackedDownload = _trackedDownloadService.TrackDownload((DownloadClientDefinition)downloadClient.Definition, downloadItem);
- if (trackedDownload != null && trackedDownload.State == TrackedDownloadStage.Downloading)
+ if (trackedDownload != null && trackedDownload.State == TrackedDownloadState.Downloading)
{
- _failedDownloadService.Process(trackedDownload);
-
- if (_configService.EnableCompletedDownloadHandling)
- {
- _completedDownloadService.Process(trackedDownload);
- }
+ _failedDownloadService.Check(trackedDownload);
+ _completedDownloadService.Check(trackedDownload);
}
- trackedDownloads.AddIfNotNull(trackedDownload);
+ return trackedDownload;
}
catch (Exception e)
{
_logger.Error(e, "Couldn't process tracked download {0}", downloadItem.Title);
}
- return trackedDownloads;
+ return null;
}
private bool DownloadIsTrackable(TrackedDownload trackedDownload)
{
- // If the download has already been imported or failed don't track it
- if (trackedDownload.State != TrackedDownloadStage.Downloading)
+ // If the download has already been imported, failed or the user ignored it don't track it
+ if (trackedDownload.State == TrackedDownloadState.Imported ||
+ trackedDownload.State == TrackedDownloadState.Failed ||
+ trackedDownload.State == TrackedDownloadState.Ignored)
{
return false;
}
@@ -156,8 +143,14 @@ namespace NzbDrone.Core.Download.TrackedDownloads
return true;
}
+ public void Execute(RefreshMonitoredDownloadsCommand message)
+ {
+ Refresh();
+ }
+
public void Execute(CheckForFinishedDownloadCommand message)
{
+ _logger.Warn("A third party app used the deprecated CheckForFinishedDownload command, it should be updated RefreshMonitoredDownloads instead");
Refresh();
}
@@ -171,6 +164,13 @@ namespace NzbDrone.Core.Download.TrackedDownloads
_refreshDebounce.Execute();
}
+ public void Handle(DownloadsProcessedEvent message)
+ {
+ var trackedDownloads = _trackedDownloadService.GetTrackedDownloads().Where(t => t.IsTrackable && DownloadIsTrackable(t)).ToList();
+
+ _eventAggregator.PublishEvent(new TrackedDownloadRefreshedEvent(trackedDownloads));
+ }
+
public void Handle(TrackedDownloadsRemovedEvent message)
{
var trackedDownloads = _trackedDownloadService.GetTrackedDownloads().Where(t => t.IsTrackable && DownloadIsTrackable(t)).ToList();
diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownload.cs b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownload.cs
index 7b1a78aa8..7cb021983 100644
--- a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownload.cs
+++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownload.cs
@@ -7,7 +7,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
{
public int DownloadClient { get; set; }
public DownloadClientItem DownloadItem { get; set; }
- public TrackedDownloadStage State { get; set; }
+ public TrackedDownloadState State { get; set; }
public TrackedDownloadStatus Status { get; private set; }
public RemoteMovie RemoteMovie { get; set; }
public TrackedDownloadStatusMessage[] StatusMessages { get; private set; }
@@ -33,16 +33,21 @@ namespace NzbDrone.Core.Download.TrackedDownloads
}
}
- public enum TrackedDownloadStage
+ public enum TrackedDownloadState
{
Downloading,
+ ImportPending,
+ Importing,
Imported,
- DownloadFailed
+ FailedPending,
+ Failed,
+ Ignored
}
public enum TrackedDownloadStatus
{
Ok,
- Warning
+ Warning,
+ Error
}
}
diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadAlreadyImported.cs b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadAlreadyImported.cs
new file mode 100644
index 000000000..be3903a38
--- /dev/null
+++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadAlreadyImported.cs
@@ -0,0 +1,36 @@
+using System.Collections.Generic;
+using System.Linq;
+using NzbDrone.Common.Extensions;
+using NzbDrone.Core.History;
+
+namespace NzbDrone.Core.Download.TrackedDownloads
+{
+ public interface ITrackedDownloadAlreadyImported
+ {
+ bool IsImported(TrackedDownload trackedDownload, List historyItems);
+ }
+
+ public class TrackedDownloadAlreadyImported : ITrackedDownloadAlreadyImported
+ {
+ public bool IsImported(TrackedDownload trackedDownload, List historyItems)
+ {
+ if (historyItems.Empty())
+ {
+ return false;
+ }
+
+ var movie = trackedDownload.RemoteMovie.Movie;
+
+ var lastHistoryItem = historyItems.FirstOrDefault(h => h.MovieId == movie.Id);
+
+ if (lastHistoryItem == null)
+ {
+ return false;
+ }
+
+ var allEpisodesImportedInHistory = lastHistoryItem.EventType == HistoryEventType.DownloadFolderImported;
+
+ return allEpisodesImportedInHistory;
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs
index 572c5b863..f016531aa 100644
--- a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs
+++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs
@@ -27,6 +27,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
private readonly IParsingService _parsingService;
private readonly IHistoryService _historyService;
private readonly IEventAggregator _eventAggregator;
+ private readonly ITrackedDownloadAlreadyImported _trackedDownloadAlreadyImported;
private readonly IConfigService _config;
private readonly ICustomFormatCalculationService _formatCalculator;
private readonly Logger _logger;
@@ -38,6 +39,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
IConfigService config,
ICustomFormatCalculationService formatCalculator,
IEventAggregator eventAggregator,
+ ITrackedDownloadAlreadyImported trackedDownloadAlreadyImported,
Logger logger)
{
_parsingService = parsingService;
@@ -46,6 +48,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
_config = config;
_formatCalculator = formatCalculator;
_eventAggregator = eventAggregator;
+ _trackedDownloadAlreadyImported = trackedDownloadAlreadyImported;
_logger = logger;
}
@@ -81,7 +84,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
{
var existingItem = Find(downloadItem.DownloadId);
- if (existingItem != null && existingItem.State != TrackedDownloadStage.Downloading)
+ if (existingItem != null && existingItem.State != TrackedDownloadState.Downloading)
{
LogItemChange(existingItem, existingItem.DownloadItem, downloadItem);
@@ -101,9 +104,10 @@ namespace NzbDrone.Core.Download.TrackedDownloads
try
{
- var historyItems = _historyService.FindByDownloadId(downloadItem.DownloadId);
- var grabbedHistoryItem = historyItems.OrderByDescending(h => h.Date).FirstOrDefault(h => h.EventType == HistoryEventType.Grabbed);
- var firstHistoryItem = historyItems.OrderByDescending(h => h.Date).FirstOrDefault();
+ var historyItems = _historyService.FindByDownloadId(downloadItem.DownloadId)
+ .OrderByDescending(h => h.Date)
+ .ToList();
+ var grabbedHistoryItem = historyItems.FirstOrDefault(h => h.EventType == HistoryEventType.Grabbed);
//TODO: Create release info from history and use that here, so we don't loose indexer flags!
var parsedMovieInfo = _parsingService.ParseMovieInfo(trackedDownload.DownloadItem.Title, new List