From 2e392e0f5e4d0f40308f20cd1caec1c89014d87d Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 29 Mar 2017 13:12:37 -0700 Subject: [PATCH 001/156] New: Additional variables for custom script on grab events --- src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs b/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs index 69733ae54..cb40c2c54 100644 --- a/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs +++ b/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs @@ -43,9 +43,14 @@ namespace NzbDrone.Core.Notifications.CustomScript environmentVariables.Add("Sonarr_Release_EpisodeCount", remoteEpisode.Episodes.Count.ToString()); environmentVariables.Add("Sonarr_Release_SeasonNumber", remoteEpisode.ParsedEpisodeInfo.SeasonNumber.ToString()); environmentVariables.Add("Sonarr_Release_EpisodeNumbers", string.Join(",", remoteEpisode.Episodes.Select(e => e.EpisodeNumber))); + environmentVariables.Add("Sonarr_Release_EpisodeAirDates", string.Join(",", remoteEpisode.Episodes.Select(e => e.AirDate))); + environmentVariables.Add("Sonarr_Release_EpisodeAirDatesUtc", string.Join(",", remoteEpisode.Episodes.Select(e => e.AirDateUtc))); + environmentVariables.Add("Sonarr_Release_EpisodeTitles", string.Join("|", remoteEpisode.Episodes.Select(e => e.Title))); environmentVariables.Add("Sonarr_Release_Title", remoteEpisode.Release.Title); environmentVariables.Add("Sonarr_Release_Indexer", remoteEpisode.Release.Indexer); environmentVariables.Add("Sonarr_Release_Size", remoteEpisode.Release.Size.ToString()); + environmentVariables.Add("Sonarr_Release_Quality", remoteEpisode.ParsedEpisodeInfo.Quality.Quality.Name); + environmentVariables.Add("Sonarr_Release_QualityVersion", remoteEpisode.ParsedEpisodeInfo.Quality.Revision.Version.ToString()); environmentVariables.Add("Sonarr_Release_ReleaseGroup", releaseGroup); ExecuteScript(environmentVariables); From 5d9d2e684ebf8636c171ec684caad3c1de7fd81d Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 29 Mar 2017 13:14:10 -0700 Subject: [PATCH 002/156] New: Paths for deleted files when upgrading an existing file --- .../Notifications/CustomScript/CustomScript.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs b/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs index cb40c2c54..9ca13be5b 100644 --- a/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs +++ b/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs @@ -64,6 +64,7 @@ namespace NzbDrone.Core.Notifications.CustomScript var environmentVariables = new StringDictionary(); environmentVariables.Add("Sonarr_EventType", "Download"); + environmentVariables.Add("Sonarr_IsUpgrade", message.OldFiles.Any().ToString()); environmentVariables.Add("Sonarr_Series_Id", series.Id.ToString()); environmentVariables.Add("Sonarr_Series_Title", series.Title); environmentVariables.Add("Sonarr_Series_Path", series.Path); @@ -85,6 +86,12 @@ namespace NzbDrone.Core.Notifications.CustomScript environmentVariables.Add("Sonarr_EpisodeFile_SourcePath", sourcePath); environmentVariables.Add("Sonarr_EpisodeFile_SourceFolder", Path.GetDirectoryName(sourcePath)); + if (message.OldFiles.Any()) + { + environmentVariables.Add("Sonarr_DeletedRelativePaths", string.Join("|", message.OldFiles.Select(e => e.RelativePath))); + environmentVariables.Add("Sonarr_DeletedPaths", string.Join("|", message.OldFiles.Select(e => Path.Combine(series.Path, e.RelativePath)))); + } + ExecuteScript(environmentVariables); } From e48600da42275b8c4fc5b0f768015afbc8178870 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 29 Mar 2017 18:21:43 -0700 Subject: [PATCH 003/156] New: TvMaze and IMDB IDs added to custom script environment variables --- .../Notifications/CustomScript/CustomScript.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs b/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs index 9ca13be5b..4686ed939 100644 --- a/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs +++ b/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs @@ -39,6 +39,8 @@ namespace NzbDrone.Core.Notifications.CustomScript environmentVariables.Add("Sonarr_Series_Id", series.Id.ToString()); environmentVariables.Add("Sonarr_Series_Title", series.Title); environmentVariables.Add("Sonarr_Series_TvdbId", series.TvdbId.ToString()); + environmentVariables.Add("Sonarr_Series_TvMazeId", series.TvMazeId.ToString()); + environmentVariables.Add("Sonarr_Series_ImdbId", series.ImdbId); environmentVariables.Add("Sonarr_Series_Type", series.SeriesType.ToString()); environmentVariables.Add("Sonarr_Release_EpisodeCount", remoteEpisode.Episodes.Count.ToString()); environmentVariables.Add("Sonarr_Release_SeasonNumber", remoteEpisode.ParsedEpisodeInfo.SeasonNumber.ToString()); @@ -69,6 +71,8 @@ namespace NzbDrone.Core.Notifications.CustomScript environmentVariables.Add("Sonarr_Series_Title", series.Title); environmentVariables.Add("Sonarr_Series_Path", series.Path); environmentVariables.Add("Sonarr_Series_TvdbId", series.TvdbId.ToString()); + environmentVariables.Add("Sonarr_Series_TvMazeId", series.TvMazeId.ToString()); + environmentVariables.Add("Sonarr_Series_ImdbId", series.ImdbId); environmentVariables.Add("Sonarr_Series_Type", series.SeriesType.ToString()); environmentVariables.Add("Sonarr_EpisodeFile_Id", episodeFile.Id.ToString()); environmentVariables.Add("Sonarr_EpisodeFile_EpisodeCount", episodeFile.Episodes.Value.Count.ToString()); @@ -104,6 +108,8 @@ namespace NzbDrone.Core.Notifications.CustomScript environmentVariables.Add("Sonarr_Series_Title", series.Title); environmentVariables.Add("Sonarr_Series_Path", series.Path); environmentVariables.Add("Sonarr_Series_TvdbId", series.TvdbId.ToString()); + environmentVariables.Add("Sonarr_Series_TvMazeId", series.TvMazeId.ToString()); + environmentVariables.Add("Sonarr_Series_ImdbId", series.ImdbId); environmentVariables.Add("Sonarr_Series_Type", series.SeriesType.ToString()); ExecuteScript(environmentVariables); From ea1616586fc38d4f24e33bcb48c218981a1ec2d6 Mon Sep 17 00:00:00 2001 From: Marcelo Castagna Date: Thu, 30 Mar 2017 17:26:11 -0300 Subject: [PATCH 004/156] Fixed: Import from torrent Download Station should move since DS maintains an internal copy for seeding. --- .../DelugeTests/DelugeFixture.cs | 26 ++++++++++--------- .../TorrentDownloadStationFixture.cs | 16 +++++++----- .../UsenetDownloadStationFixture.cs | 18 ------------- .../QBittorrentTests/QBittorrentFixture.cs | 20 ++++++++------ .../TransmissionTests/TransmissionFixture.cs | 17 ++++++------ .../UTorrentTests/UTorrentFixture.cs | 15 ++++++----- .../VuzeTests/VuzeFixture.cs | 19 +++++++------- .../HistoryTests/HistoryServiceFixture.cs | 4 +-- .../ImportApprovedEpisodesFixture.cs | 6 ++--- .../Clients/Blackhole/TorrentBlackhole.cs | 3 ++- .../Clients/Blackhole/UsenetBlackhole.cs | 5 +++- .../Download/Clients/Deluge/Deluge.cs | 11 ++------ .../DownloadStation/TorrentDownloadStation.cs | 8 +++++- .../DownloadStation/UsenetDownloadStation.cs | 8 ++---- .../Download/Clients/Hadouken/Hadouken.cs | 11 ++------ .../Download/Clients/NzbVortex/NzbVortex.cs | 8 +++--- .../Download/Clients/Nzbget/Nzbget.cs | 2 ++ .../Download/Clients/Pneumatic/Pneumatic.cs | 3 +++ .../Clients/QBittorrent/QBittorrent.cs | 4 +-- .../Download/Clients/Sabnzbd/Sabnzbd.cs | 2 ++ .../Clients/Transmission/TransmissionBase.cs | 6 ++--- .../Download/Clients/rTorrent/RTorrent.cs | 24 ++++++++++++----- .../Download/Clients/uTorrent/UTorrent.cs | 2 +- .../Download/DownloadClientItem.cs | 4 ++- .../Download/DownloadEventHub.cs | 6 ++--- .../DownloadMonitoringService.cs | 4 +-- .../DownloadedEpisodesImportService.cs | 2 +- .../EpisodeImport/ImportApprovedEpisodes.cs | 4 +-- .../Manual/ManualImportService.cs | 2 +- .../MediaFiles/Events/EpisodeImportedEvent.cs | 8 +++--- 30 files changed, 139 insertions(+), 129 deletions(-) diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DelugeTests/DelugeFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DelugeTests/DelugeFixture.cs index af24f2797..4e3d10f01 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DelugeTests/DelugeFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DelugeTests/DelugeFixture.cs @@ -114,7 +114,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DelugeTests .Returns("CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951".ToLower()) .Callback(PrepareClientToReturnQueuedItem); } - + protected virtual void GivenTorrents(List torrents) { if (torrents == null) @@ -129,7 +129,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DelugeTests protected void PrepareClientToReturnQueuedItem() { - GivenTorrents(new List + GivenTorrents(new List { _queued }); @@ -137,7 +137,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DelugeTests protected void PrepareClientToReturnDownloadingItem() { - GivenTorrents(new List + GivenTorrents(new List { _downloading }); @@ -145,7 +145,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DelugeTests protected void PrepareClientToReturnFailedItem() { - GivenTorrents(new List + GivenTorrents(new List { _failed }); @@ -248,11 +248,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DelugeTests item.Status.Should().Be(expectedItemStatus); } - [TestCase(DelugeTorrentStatus.Paused, DownloadItemStatus.Completed, true)] - [TestCase(DelugeTorrentStatus.Checking, DownloadItemStatus.Downloading, true)] - [TestCase(DelugeTorrentStatus.Queued, DownloadItemStatus.Completed, true)] - [TestCase(DelugeTorrentStatus.Seeding, DownloadItemStatus.Completed, true)] - public void GetItems_should_return_completed_item_as_downloadItemStatus(string apiStatus, DownloadItemStatus expectedItemStatus, bool expectedReadOnly) + [TestCase(DelugeTorrentStatus.Paused, DownloadItemStatus.Completed, false)] + [TestCase(DelugeTorrentStatus.Checking, DownloadItemStatus.Downloading, false)] + [TestCase(DelugeTorrentStatus.Queued, DownloadItemStatus.Completed, false)] + [TestCase(DelugeTorrentStatus.Seeding, DownloadItemStatus.Completed, false)] + public void GetItems_should_return_completed_item_as_downloadItemStatus(string apiStatus, DownloadItemStatus expectedItemStatus, bool expectedValue) { _completed.State = apiStatus; @@ -261,11 +261,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DelugeTests var item = Subject.GetItems().Single(); item.Status.Should().Be(expectedItemStatus); - item.IsReadOnly.Should().Be(expectedReadOnly); + item.CanBeRemoved.Should().Be(expectedValue); + item.CanMoveFiles.Should().Be(expectedValue); } [Test] - public void GetItems_should_check_share_ratio_for_readonly() + public void GetItems_should_check_share_ratio_for_moveFiles_and_remove() { _completed.State = DelugeTorrentStatus.Paused; _completed.IsAutoManaged = true; @@ -278,7 +279,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DelugeTests var item = Subject.GetItems().Single(); item.Status.Should().Be(DownloadItemStatus.Completed); - item.IsReadOnly.Should().BeFalse(); + item.CanMoveFiles.Should().BeTrue(); + item.CanBeRemoved.Should().BeTrue(); } [Test] diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/TorrentDownloadStationFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/TorrentDownloadStationFixture.cs index 8269acda6..4142ddc65 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/TorrentDownloadStationFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/TorrentDownloadStationFixture.cs @@ -576,11 +576,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests items.Should().OnlyContain(v => !v.OutputPath.IsEmpty); } - [TestCase(DownloadStationTaskStatus.Downloading, DownloadItemStatus.Downloading, true)] - [TestCase(DownloadStationTaskStatus.Finished, DownloadItemStatus.Completed, false)] - [TestCase(DownloadStationTaskStatus.Seeding, DownloadItemStatus.Completed, true)] - [TestCase(DownloadStationTaskStatus.Waiting, DownloadItemStatus.Queued, true)] - public void GetItems_should_return_readonly_expected(DownloadStationTaskStatus apiStatus, DownloadItemStatus expectedItemStatus, bool readOnlyExpected) + [TestCase(DownloadStationTaskStatus.Downloading, false, false)] + [TestCase(DownloadStationTaskStatus.Finished, true, true)] + [TestCase(DownloadStationTaskStatus.Seeding, true, false)] + [TestCase(DownloadStationTaskStatus.Waiting, false, false)] + public void GetItems_should_return_canBeMoved_and_canBeDeleted_as_expected(DownloadStationTaskStatus apiStatus, bool canMoveFilesExpected, bool canBeRemovedExpected) { GivenSerialNumber(); GivenSharedFolder(); @@ -592,7 +592,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests var items = Subject.GetItems(); items.Should().HaveCount(1); - items.First().IsReadOnly.Should().Be(readOnlyExpected); + + var item = items.First(); + + item.CanBeRemoved.Should().Be(canBeRemovedExpected); + item.CanMoveFiles.Should().Be(canMoveFilesExpected); } [TestCase(DownloadStationTaskStatus.Downloading, DownloadItemStatus.Downloading)] diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/UsenetDownloadStationFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/UsenetDownloadStationFixture.cs index 48df65841..6d0a0f0e9 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/UsenetDownloadStationFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/UsenetDownloadStationFixture.cs @@ -408,24 +408,6 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests items.Should().OnlyContain(v => !v.OutputPath.IsEmpty); } - [TestCase(DownloadStationTaskStatus.Downloading, DownloadItemStatus.Downloading, true)] - [TestCase(DownloadStationTaskStatus.Finished, DownloadItemStatus.Completed, false)] - [TestCase(DownloadStationTaskStatus.Waiting, DownloadItemStatus.Queued, true)] - public void GetItems_should_return_readonly_expected(DownloadStationTaskStatus apiStatus, DownloadItemStatus expectedItemStatus, bool readOnlyExpected) - { - GivenSerialNumber(); - GivenSharedFolder(); - - _queued.Status = apiStatus; - - GivenTasks(new List() { _queued }); - - var items = Subject.GetItems(); - - items.Should().HaveCount(1); - items.First().IsReadOnly.Should().Be(readOnlyExpected); - } - [TestCase(DownloadStationTaskStatus.Downloading, DownloadItemStatus.Downloading)] [TestCase(DownloadStationTaskStatus.Error, DownloadItemStatus.Failed)] [TestCase(DownloadStationTaskStatus.Extracting, DownloadItemStatus.Downloading)] diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs index 3ceece6f6..3e9ca837d 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs @@ -311,7 +311,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests } [Test] - public void should_be_read_only_if_max_ratio_not_reached() + public void should_not_be_removable_and_should_not_allow_move_files_if_max_ratio_not_reached() { GivenMaxRatio(1.0f); @@ -330,11 +330,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests GivenTorrents(new List { torrent }); var item = Subject.GetItems().Single(); - item.IsReadOnly.Should().BeTrue(); + item.CanBeRemoved.Should().BeFalse(); + item.CanMoveFiles.Should().BeFalse(); } [Test] - public void should_be_read_only_if_max_ratio_reached_and_not_paused() + public void should_not_be_removable_and_should_not_allow_move_files_if_max_ratio_reached_and_not_paused() { GivenMaxRatio(1.0f); @@ -353,11 +354,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests GivenTorrents(new List { torrent }); var item = Subject.GetItems().Single(); - item.IsReadOnly.Should().BeTrue(); + item.CanBeRemoved.Should().BeFalse(); + item.CanMoveFiles.Should().BeFalse(); } [Test] - public void should_be_read_only_if_max_ratio_is_not_set() + public void should_not_be_removable_and_should_not_allow_move_files_if_max_ratio_is_not_set() { GivenMaxRatio(1.0f, false); @@ -376,11 +378,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests GivenTorrents(new List { torrent }); var item = Subject.GetItems().Single(); - item.IsReadOnly.Should().BeTrue(); + item.CanBeRemoved.Should().BeFalse(); + item.CanMoveFiles.Should().BeFalse(); } [Test] - public void should_not_be_read_only_if_max_ratio_reached_and_paused() + public void should_be_removable_and_should_allow_move_files_if_max_ratio_reached_and_paused() { GivenMaxRatio(1.0f); @@ -399,7 +402,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests GivenTorrents(new List { torrent }); var item = Subject.GetItems().Single(); - item.IsReadOnly.Should().BeFalse(); + item.CanBeRemoved.Should().BeTrue(); + item.CanMoveFiles.Should().BeTrue(); } [Test] diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/TransmissionTests/TransmissionFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/TransmissionTests/TransmissionFixture.cs index 39ec56789..c8b842cef 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/TransmissionTests/TransmissionFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/TransmissionTests/TransmissionFixture.cs @@ -172,13 +172,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests item.Status.Should().Be(expectedItemStatus); } - [TestCase(TransmissionTorrentStatus.Stopped, DownloadItemStatus.Completed, false)] - [TestCase(TransmissionTorrentStatus.CheckWait, DownloadItemStatus.Downloading, true)] - [TestCase(TransmissionTorrentStatus.Check, DownloadItemStatus.Downloading, true)] - [TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Completed, true)] - [TestCase(TransmissionTorrentStatus.SeedingWait, DownloadItemStatus.Completed, true)] - [TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Completed, true)] - public void GetItems_should_return_completed_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus, bool expectedReadOnly) + [TestCase(TransmissionTorrentStatus.Stopped, DownloadItemStatus.Completed, true)] + [TestCase(TransmissionTorrentStatus.CheckWait, DownloadItemStatus.Downloading, false)] + [TestCase(TransmissionTorrentStatus.Check, DownloadItemStatus.Downloading, false)] + [TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Completed, false)] + [TestCase(TransmissionTorrentStatus.SeedingWait, DownloadItemStatus.Completed, false)] + [TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Completed, false)] + public void GetItems_should_return_completed_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus, bool expectedValue) { _completed.Status = apiStatus; @@ -187,7 +187,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests var item = Subject.GetItems().Single(); item.Status.Should().Be(expectedItemStatus); - item.IsReadOnly.Should().Be(expectedReadOnly); + item.CanBeRemoved.Should().Be(expectedValue); + item.CanMoveFiles.Should().Be(expectedValue); } [Test] diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/UTorrentTests/UTorrentFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/UTorrentTests/UTorrentFixture.cs index 1d9f037d2..1068bd762 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/UTorrentTests/UTorrentFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/UTorrentTests/UTorrentFixture.cs @@ -292,12 +292,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.UTorrentTests item.Status.Should().Be(expectedItemStatus); } - [TestCase(UTorrentTorrentStatus.Loaded | UTorrentTorrentStatus.Checking, DownloadItemStatus.Queued, false)] - [TestCase(UTorrentTorrentStatus.Loaded | UTorrentTorrentStatus.Checked, DownloadItemStatus.Completed, false)] - [TestCase(UTorrentTorrentStatus.Loaded | UTorrentTorrentStatus.Checked | UTorrentTorrentStatus.Queued, DownloadItemStatus.Completed, true)] - [TestCase(UTorrentTorrentStatus.Loaded | UTorrentTorrentStatus.Checked | UTorrentTorrentStatus.Started, DownloadItemStatus.Completed, true)] - [TestCase(UTorrentTorrentStatus.Loaded | UTorrentTorrentStatus.Checked | UTorrentTorrentStatus.Queued | UTorrentTorrentStatus.Paused, DownloadItemStatus.Completed, true)] - public void GetItems_should_return_completed_item_as_downloadItemStatus(UTorrentTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus, bool expectedReadOnly) + [TestCase(UTorrentTorrentStatus.Loaded | UTorrentTorrentStatus.Checking, DownloadItemStatus.Queued, true)] + [TestCase(UTorrentTorrentStatus.Loaded | UTorrentTorrentStatus.Checked, DownloadItemStatus.Completed, true)] + [TestCase(UTorrentTorrentStatus.Loaded | UTorrentTorrentStatus.Checked | UTorrentTorrentStatus.Queued, DownloadItemStatus.Completed, false)] + [TestCase(UTorrentTorrentStatus.Loaded | UTorrentTorrentStatus.Checked | UTorrentTorrentStatus.Started, DownloadItemStatus.Completed, false)] + [TestCase(UTorrentTorrentStatus.Loaded | UTorrentTorrentStatus.Checked | UTorrentTorrentStatus.Queued | UTorrentTorrentStatus.Paused, DownloadItemStatus.Completed, false)] + public void GetItems_should_return_completed_item_as_downloadItemStatus(UTorrentTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus, bool expectedValue) { _completed.Status = apiStatus; @@ -306,7 +306,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.UTorrentTests var item = Subject.GetItems().Single(); item.Status.Should().Be(expectedItemStatus); - item.IsReadOnly.Should().Be(expectedReadOnly); + item.CanBeRemoved.Should().Be(expectedValue); + item.CanMoveFiles.Should().Be(expectedValue); } [Test] diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/VuzeTests/VuzeFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/VuzeTests/VuzeFixture.cs index 00278c811..e6782eb4f 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/VuzeTests/VuzeFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/VuzeTests/VuzeFixture.cs @@ -174,13 +174,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests item.Status.Should().Be(expectedItemStatus); } - [TestCase(TransmissionTorrentStatus.Stopped, DownloadItemStatus.Completed, false)] - [TestCase(TransmissionTorrentStatus.CheckWait, DownloadItemStatus.Downloading, true)] - [TestCase(TransmissionTorrentStatus.Check, DownloadItemStatus.Downloading, true)] - [TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Completed, true)] - [TestCase(TransmissionTorrentStatus.SeedingWait, DownloadItemStatus.Completed, true)] - [TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Completed, true)] - public void GetItems_should_return_completed_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus, bool expectedReadOnly) + [TestCase(TransmissionTorrentStatus.Stopped, DownloadItemStatus.Completed, true)] + [TestCase(TransmissionTorrentStatus.CheckWait, DownloadItemStatus.Downloading, false)] + [TestCase(TransmissionTorrentStatus.Check, DownloadItemStatus.Downloading, false)] + [TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Completed, false)] + [TestCase(TransmissionTorrentStatus.SeedingWait, DownloadItemStatus.Completed, false)] + [TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Completed, false)] + public void GetItems_should_return_completed_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus, bool expectedValue) { _completed.Status = apiStatus; @@ -189,7 +189,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests var item = Subject.GetItems().Single(); item.Status.Should().Be(expectedItemStatus); - item.IsReadOnly.Should().Be(expectedReadOnly); + item.CanBeRemoved.Should().Be(expectedValue); + item.CanMoveFiles.Should().Be(expectedValue); } [Test] @@ -312,4 +313,4 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs b/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs index c2d436ec8..de657ff6c 100644 --- a/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs +++ b/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs @@ -81,10 +81,10 @@ namespace NzbDrone.Core.Test.HistoryTests Path = @"C:\Test\Unsorted\Series.s01e01.mkv" }; - Subject.Handle(new EpisodeImportedEvent(localEpisode, episodeFile, true, "sab", "abcd", true)); + Subject.Handle(new EpisodeImportedEvent(localEpisode, episodeFile, true, "sab", "abcd")); Mocker.GetMock() .Verify(v => v.Insert(It.Is(h => h.SourceTitle == Path.GetFileNameWithoutExtension(localEpisode.Path)))); } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs index 6ae1ccc10..f7be82b81 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs @@ -224,9 +224,9 @@ namespace NzbDrone.Core.Test.MediaFiles } [Test] - public void should_copy_readonly_downloads() + public void should_copy_when_cannot_move_files_downloads() { - Subject.Import(new List { _approvedDecisions.First() }, true, new DownloadClientItem { Title = "30.Rock.S01E01", IsReadOnly = true }); + Subject.Import(new List { _approvedDecisions.First() }, true, new DownloadClientItem { Title = "30.Rock.S01E01", CanMoveFiles = false}); Mocker.GetMock() .Verify(v => v.UpgradeEpisodeFile(It.IsAny(), _approvedDecisions.First().LocalEpisode, true), Times.Once()); @@ -235,7 +235,7 @@ namespace NzbDrone.Core.Test.MediaFiles [Test] public void should_use_override_importmode() { - Subject.Import(new List { _approvedDecisions.First() }, true, new DownloadClientItem { Title = "30.Rock.S01E01", IsReadOnly = true }, ImportMode.Move); + Subject.Import(new List { _approvedDecisions.First() }, true, new DownloadClientItem { Title = "30.Rock.S01E01", CanMoveFiles = false }, ImportMode.Move); Mocker.GetMock() .Verify(v => v.UpgradeEpisodeFile(It.IsAny(), _approvedDecisions.First().LocalEpisode, false), Times.Once()); diff --git a/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackhole.cs b/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackhole.cs index e95297c97..260304ed6 100644 --- a/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackhole.cs +++ b/src/NzbDrone.Core/Download/Clients/Blackhole/TorrentBlackhole.cs @@ -103,7 +103,8 @@ namespace NzbDrone.Core.Download.Clients.Blackhole Status = item.Status, - IsReadOnly = Settings.ReadOnly + CanMoveFiles = !Settings.ReadOnly, + CanBeRemoved = !Settings.ReadOnly }; } } diff --git a/src/NzbDrone.Core/Download/Clients/Blackhole/UsenetBlackhole.cs b/src/NzbDrone.Core/Download/Clients/Blackhole/UsenetBlackhole.cs index 2cc13a235..31cefcb90 100644 --- a/src/NzbDrone.Core/Download/Clients/Blackhole/UsenetBlackhole.cs +++ b/src/NzbDrone.Core/Download/Clients/Blackhole/UsenetBlackhole.cs @@ -68,7 +68,10 @@ namespace NzbDrone.Core.Download.Clients.Blackhole OutputPath = item.OutputPath, - Status = item.Status + Status = item.Status, + + CanBeRemoved = true, + CanMoveFiles = true }; } } diff --git a/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs b/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs index 6e4d023a0..7bd224472 100644 --- a/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs +++ b/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs @@ -138,14 +138,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge } // Here we detect if Deluge is managing the torrent and whether the seed criteria has been met. This allows drone to delete the torrent as appropriate. - if (torrent.IsAutoManaged && torrent.StopAtRatio && torrent.Ratio >= torrent.StopRatio && torrent.State == DelugeTorrentStatus.Paused) - { - item.IsReadOnly = false; - } - else - { - item.IsReadOnly = true; - } + item.CanMoveFiles = item.CanBeRemoved = (torrent.IsAutoManaged && torrent.StopAtRatio && torrent.Ratio >= torrent.StopRatio && torrent.State == DelugeTorrentStatus.Paused); items.Add(item); } @@ -178,7 +171,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge { status.OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, destDir) }; } - + return status; } diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs index 911152694..75c990276 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs @@ -90,7 +90,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation RemainingTime = GetRemainingTime(torrent), Status = GetStatus(torrent), Message = GetMessage(torrent), - IsReadOnly = !IsFinished(torrent) + CanMoveFiles = IsCompleted(torrent), + CanBeRemoved = IsFinished(torrent) }; if (item.Status == DownloadItemStatus.Completed || item.Status == DownloadItemStatus.Failed) @@ -199,6 +200,11 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation return torrent.Status == DownloadStationTaskStatus.Finished; } + protected bool IsCompleted(DownloadStationTask torrent) + { + return torrent.Status == DownloadStationTaskStatus.Seeding || IsFinished(torrent) || (torrent.Status == DownloadStationTaskStatus.Waiting && torrent.Size != 0 && GetRemainingSize(torrent) <= 0); + } + protected string GetMessage(DownloadStationTask torrent) { if (torrent.StatusExtra != null) diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs index ad7045cbb..ed24e2443 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs @@ -99,7 +99,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation RemainingSize = taskRemainingSize, Status = GetStatus(nzb), Message = GetMessage(nzb), - IsReadOnly = !IsFinished(nzb) + CanBeRemoved = true, + CanMoveFiles = true }; if (item.Status != DownloadItemStatus.Paused) @@ -291,11 +292,6 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation return null; } - protected bool IsFinished(DownloadStationTask task) - { - return task.Status == DownloadStationTaskStatus.Finished; - } - protected string GetMessage(DownloadStationTask task) { if (task.StatusExtra != null) diff --git a/src/NzbDrone.Core/Download/Clients/Hadouken/Hadouken.cs b/src/NzbDrone.Core/Download/Clients/Hadouken/Hadouken.cs index 5727dea8b..17041c51c 100644 --- a/src/NzbDrone.Core/Download/Clients/Hadouken/Hadouken.cs +++ b/src/NzbDrone.Core/Download/Clients/Hadouken/Hadouken.cs @@ -97,14 +97,7 @@ namespace NzbDrone.Core.Download.Clients.Hadouken item.Status = DownloadItemStatus.Downloading; } - if (torrent.IsFinished && torrent.State == HadoukenTorrentState.Paused) - { - item.IsReadOnly = false; - } - else - { - item.IsReadOnly = true; - } + item.CanMoveFiles = item.CanBeRemoved = (torrent.IsFinished && torrent.State == HadoukenTorrentState.Paused); items.Add(item); } @@ -170,7 +163,7 @@ namespace NzbDrone.Core.Download.Clients.Hadouken if (version < new Version("5.1")) { - return new ValidationFailure(string.Empty, "Old Hadouken client with unsupported API, need 5.1 or higher"); + return new ValidationFailure(string.Empty, "Old Hadouken client with unsupported API, need 5.1 or higher"); } } catch (DownloadClientAuthenticationException ex) diff --git a/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortex.cs b/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortex.cs index dc3595615..fb025ae82 100644 --- a/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortex.cs +++ b/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortex.cs @@ -72,7 +72,9 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex queueItem.TotalSize = vortexQueueItem.TotalDownloadSize; queueItem.RemainingSize = vortexQueueItem.TotalDownloadSize - vortexQueueItem.DownloadedSize; queueItem.RemainingTime = null; - + queueItem.CanBeRemoved = true; + queueItem.CanMoveFiles = true; + if (vortexQueueItem.IsPaused) { queueItem.Status = DownloadItemStatus.Paused; @@ -132,7 +134,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex { _proxy.Remove(queueItem.Id, deleteData, Settings); } - } + } } protected List GetGroups() @@ -256,4 +258,4 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex return new OsPath(Path.Combine(outputPath.FullPath, filesResponse.First().FileName)); } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs index 949186fc7..ff379f8b0 100644 --- a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs +++ b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs @@ -83,6 +83,8 @@ namespace NzbDrone.Core.Download.Clients.Nzbget queueItem.TotalSize = totalSize; queueItem.Category = item.Category; queueItem.DownloadClient = Definition.Name; + queueItem.CanMoveFiles = true; + queueItem.CanBeRemoved = true; if (globalStatus.DownloadPaused || remainingSize == pausedSize && remainingSize != 0) { diff --git a/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs b/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs index 5eab58b3b..618a1e843 100644 --- a/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs +++ b/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs @@ -77,6 +77,9 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic DownloadId = GetDownloadClientId(file), Title = title, + CanBeRemoved = true, + CanMoveFiles = true, + TotalSize = _diskProvider.GetFileSize(file), OutputPath = new OsPath(file) diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs index 55eec2682..107a85d56 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs @@ -106,7 +106,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent // Avoid removing torrents that haven't reached the global max ratio. // Removal also requires the torrent to be paused, in case a higher max ratio was set on the torrent itself (which is not exposed by the api). - item.IsReadOnly = (config.MaxRatioEnabled && config.MaxRatio > torrent.Ratio) || torrent.State != "pausedUP"; + item.CanMoveFiles = item.CanBeRemoved = (!config.MaxRatioEnabled || config.MaxRatio <= torrent.Ratio) && torrent.State == "pausedUP"; if (!item.OutputPath.IsEmpty && item.OutputPath.FileName != torrent.Name) { @@ -129,7 +129,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent item.Status = DownloadItemStatus.Queued; break; - case "pausedUP": // torrent is paused and has finished downloading + case "pausedUP": // torrent is paused and has finished downloading: case "uploading": // torrent is being seeded and data is being transfered case "stalledUP": // torrent is being seeded, but no connection were made case "queuedUP": // queuing is enabled and torrent is queued for upload diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs index 70ae19373..9ccc2aa9d 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs @@ -78,6 +78,8 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd queueItem.TotalSize = (long)(sabQueueItem.Size * 1024 * 1024); queueItem.RemainingSize = (long)(sabQueueItem.Sizeleft * 1024 * 1024); queueItem.RemainingTime = sabQueueItem.Timeleft; + queueItem.CanBeRemoved = true; + queueItem.CanMoveFiles = true; if (sabQueue.Paused || sabQueueItem.Status == SabnzbdDownloadStatus.Paused) { diff --git a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionBase.cs b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionBase.cs index 3fa69c06b..5093315b6 100644 --- a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionBase.cs +++ b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionBase.cs @@ -105,7 +105,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission item.Status = DownloadItemStatus.Downloading; } - item.IsReadOnly = torrent.Status != TransmissionTorrentStatus.Stopped; + item.CanMoveFiles = item.CanBeRemoved = torrent.Status == TransmissionTorrentStatus.Stopped; items.Add(item); } @@ -122,7 +122,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission { var config = _proxy.GetConfig(Settings); var destDir = config.GetValueOrDefault("download-dir") as string; - + if (Settings.TvCategory.IsNotNullOrWhiteSpace()) { destDir = string.Format("{0}/.{1}", destDir, Settings.TvCategory); @@ -246,4 +246,4 @@ namespace NzbDrone.Core.Download.Clients.Transmission return null; } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs index ee384fc92..5ea9015a7 100644 --- a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs @@ -107,19 +107,31 @@ namespace NzbDrone.Core.Download.Clients.RTorrent item.RemainingSize = torrent.RemainingSize; item.Category = torrent.Category; - if (torrent.DownRate > 0) { + if (torrent.DownRate > 0) + { var secondsLeft = torrent.RemainingSize / torrent.DownRate; item.RemainingTime = TimeSpan.FromSeconds(secondsLeft); - } else { + } + else + { item.RemainingTime = TimeSpan.Zero; } - if (torrent.IsFinished) item.Status = DownloadItemStatus.Completed; - else if (torrent.IsActive) item.Status = DownloadItemStatus.Downloading; - else if (!torrent.IsActive) item.Status = DownloadItemStatus.Paused; + if (torrent.IsFinished) + { + item.Status = DownloadItemStatus.Completed; + } + else if (torrent.IsActive) + { + item.Status = DownloadItemStatus.Downloading; + } + else if (!torrent.IsActive) + { + item.Status = DownloadItemStatus.Paused; + } // No stop ratio data is present, so do not delete - item.IsReadOnly = true; + item.CanMoveFiles = item.CanBeRemoved = false; items.Add(item); } diff --git a/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs b/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs index 8f442eb7b..1c9f88990 100644 --- a/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs @@ -165,7 +165,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent } // 'Started' without 'Queued' is when the torrent is 'forced seeding' - item.IsReadOnly = torrent.Status.HasFlag(UTorrentTorrentStatus.Queued) || torrent.Status.HasFlag(UTorrentTorrentStatus.Started); + item.CanMoveFiles = item.CanBeRemoved = (!torrent.Status.HasFlag(UTorrentTorrentStatus.Queued) && !torrent.Status.HasFlag(UTorrentTorrentStatus.Started)); queueItems.Add(item); } diff --git a/src/NzbDrone.Core/Download/DownloadClientItem.cs b/src/NzbDrone.Core/Download/DownloadClientItem.cs index 2e0533e50..acd0b0579 100644 --- a/src/NzbDrone.Core/Download/DownloadClientItem.cs +++ b/src/NzbDrone.Core/Download/DownloadClientItem.cs @@ -21,7 +21,9 @@ namespace NzbDrone.Core.Download public DownloadItemStatus Status { get; set; } public bool IsEncrypted { get; set; } - public bool IsReadOnly { get; set; } + + public bool CanMoveFiles { get; set; } + public bool CanBeRemoved { get; set; } public bool Removed { get; set; } } diff --git a/src/NzbDrone.Core/Download/DownloadEventHub.cs b/src/NzbDrone.Core/Download/DownloadEventHub.cs index f738f5c2e..0168b013e 100644 --- a/src/NzbDrone.Core/Download/DownloadEventHub.cs +++ b/src/NzbDrone.Core/Download/DownloadEventHub.cs @@ -37,7 +37,7 @@ namespace NzbDrone.Core.Download { if (!_configService.RemoveCompletedDownloads || message.TrackedDownload.DownloadItem.Removed || - message.TrackedDownload.DownloadItem.IsReadOnly || + !message.TrackedDownload.DownloadItem.CanBeRemoved || message.TrackedDownload.DownloadItem.Status == DownloadItemStatus.Downloading) { return; @@ -50,7 +50,7 @@ namespace NzbDrone.Core.Download { var trackedDownload = message.TrackedDownload; - if (trackedDownload == null || trackedDownload.DownloadItem.IsReadOnly || _configService.RemoveFailedDownloads == false) + if (trackedDownload == null || !trackedDownload.DownloadItem.CanBeRemoved || _configService.RemoveFailedDownloads == false) { return; } @@ -78,4 +78,4 @@ namespace NzbDrone.Core.Download } } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/DownloadMonitoringService.cs b/src/NzbDrone.Core/Download/TrackedDownloads/DownloadMonitoringService.cs index dcaefd073..4fb031dc5 100644 --- a/src/NzbDrone.Core/Download/TrackedDownloads/DownloadMonitoringService.cs +++ b/src/NzbDrone.Core/Download/TrackedDownloads/DownloadMonitoringService.cs @@ -64,7 +64,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads { var clientTrackedDownloads = ProcessClientDownloads(downloadClient); - // Only track completed downloads if + // Only track completed downloads if trackedDownloads.AddRange(clientTrackedDownloads.Where(DownloadIsTrackable)); } @@ -107,7 +107,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads private void RemoveCompletedDownloads(List trackedDownloads) { - foreach (var trackedDownload in trackedDownloads.Where(c => !c.DownloadItem.IsReadOnly && c.State == TrackedDownloadStage.Imported)) + foreach (var trackedDownload in trackedDownloads.Where(c => c.DownloadItem.CanBeRemoved && c.State == TrackedDownloadStage.Imported)) { _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload)); } diff --git a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs index 69e154a72..385bf2b8b 100644 --- a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs @@ -185,7 +185,7 @@ namespace NzbDrone.Core.MediaFiles var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), series, folderInfo, true); var importResults = _importApprovedEpisodes.Import(decisions, true, downloadClientItem, importMode); - if ((downloadClientItem == null || !downloadClientItem.IsReadOnly) && + if ((downloadClientItem == null || downloadClientItem.CanMoveFiles) && importResults.Any(i => i.Result == ImportResultType.Imported) && ShouldDeleteFolder(directoryInfo, series)) { diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs index cdfd289db..b90902954 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs @@ -90,7 +90,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport { default: case ImportMode.Auto: - copyOnly = downloadClientItem != null && downloadClientItem.IsReadOnly; + copyOnly = downloadClientItem != null && !downloadClientItem.CanMoveFiles; break; case ImportMode.Move: copyOnly = false; @@ -122,7 +122,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport if (downloadClientItem != null) { - _eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile, newDownload, downloadClientItem.DownloadClient, downloadClientItem.DownloadId, downloadClientItem.IsReadOnly)); + _eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile, newDownload, downloadClientItem.DownloadClient, downloadClientItem.DownloadId));//, !downloadClientItem.CanMoveFiles)); } else { diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs index d85a2e119..bc01d462a 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs @@ -251,7 +251,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual { if (_downloadedEpisodesImportService.ShouldDeleteFolder( new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath), - trackedDownload.RemoteEpisode.Series) && !trackedDownload.DownloadItem.IsReadOnly) + trackedDownload.RemoteEpisode.Series) && trackedDownload.DownloadItem.CanMoveFiles) { _diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true); } diff --git a/src/NzbDrone.Core/MediaFiles/Events/EpisodeImportedEvent.cs b/src/NzbDrone.Core/MediaFiles/Events/EpisodeImportedEvent.cs index 518132857..932eb1888 100644 --- a/src/NzbDrone.Core/MediaFiles/Events/EpisodeImportedEvent.cs +++ b/src/NzbDrone.Core/MediaFiles/Events/EpisodeImportedEvent.cs @@ -10,7 +10,7 @@ namespace NzbDrone.Core.MediaFiles.Events public bool NewDownload { get; private set; } public string DownloadClient { get; private set; } public string DownloadId { get; private set; } - public bool IsReadOnly { get; set; } + //public bool IsReadOnly { get; set; } public EpisodeImportedEvent(LocalEpisode episodeInfo, EpisodeFile importedEpisode, bool newDownload) { @@ -19,14 +19,14 @@ namespace NzbDrone.Core.MediaFiles.Events NewDownload = newDownload; } - public EpisodeImportedEvent(LocalEpisode episodeInfo, EpisodeFile importedEpisode, bool newDownload, string downloadClient, string downloadId, bool isReadOnly) + public EpisodeImportedEvent(LocalEpisode episodeInfo, EpisodeFile importedEpisode, bool newDownload, string downloadClient, string downloadId)//, bool isReadOnly) { EpisodeInfo = episodeInfo; ImportedEpisode = importedEpisode; NewDownload = newDownload; DownloadClient = downloadClient; DownloadId = downloadId; - IsReadOnly = isReadOnly; + // IsReadOnly = isReadOnly; } } -} \ No newline at end of file +} From 28c45f941bc6119eebbe2db3e6b43dc5bdc7ae17 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Thu, 30 Mar 2017 22:28:00 +0200 Subject: [PATCH 005/156] Cleanup of commented out code. --- .../ParserTests/SingleEpisodeParserFixture.cs | 1 + .../MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs | 2 +- src/NzbDrone.Core/MediaFiles/Events/EpisodeImportedEvent.cs | 4 +--- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/NzbDrone.Core.Test/ParserTests/SingleEpisodeParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/SingleEpisodeParserFixture.cs index 05cdaa6eb..e82d9c685 100644 --- a/src/NzbDrone.Core.Test/ParserTests/SingleEpisodeParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/SingleEpisodeParserFixture.cs @@ -127,6 +127,7 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase("this.is.not.happening.2015.0308-yestv", "this is not happening 2015", 3, 8)] [TestCase("Jeopardy - S2016E231", "Jeopardy", 2016, 231)] [TestCase("Jeopardy - 2016x231", "Jeopardy", 2016, 231)] + [TestCase("Shortland.Street.S26E022.HDTV.x264-FiHTV", "Shortland Street", 26, 22)] //[TestCase("", "", 0, 0)] public void should_parse_single_episode(string postTitle, string title, int seasonNumber, int episodeNumber) { diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs index b90902954..b6b2c7573 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs @@ -122,7 +122,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport if (downloadClientItem != null) { - _eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile, newDownload, downloadClientItem.DownloadClient, downloadClientItem.DownloadId));//, !downloadClientItem.CanMoveFiles)); + _eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile, newDownload, downloadClientItem.DownloadClient, downloadClientItem.DownloadId)); } else { diff --git a/src/NzbDrone.Core/MediaFiles/Events/EpisodeImportedEvent.cs b/src/NzbDrone.Core/MediaFiles/Events/EpisodeImportedEvent.cs index 932eb1888..44cbff6b1 100644 --- a/src/NzbDrone.Core/MediaFiles/Events/EpisodeImportedEvent.cs +++ b/src/NzbDrone.Core/MediaFiles/Events/EpisodeImportedEvent.cs @@ -10,7 +10,6 @@ namespace NzbDrone.Core.MediaFiles.Events public bool NewDownload { get; private set; } public string DownloadClient { get; private set; } public string DownloadId { get; private set; } - //public bool IsReadOnly { get; set; } public EpisodeImportedEvent(LocalEpisode episodeInfo, EpisodeFile importedEpisode, bool newDownload) { @@ -19,14 +18,13 @@ namespace NzbDrone.Core.MediaFiles.Events NewDownload = newDownload; } - public EpisodeImportedEvent(LocalEpisode episodeInfo, EpisodeFile importedEpisode, bool newDownload, string downloadClient, string downloadId)//, bool isReadOnly) + public EpisodeImportedEvent(LocalEpisode episodeInfo, EpisodeFile importedEpisode, bool newDownload, string downloadClient, string downloadId) { EpisodeInfo = episodeInfo; ImportedEpisode = importedEpisode; NewDownload = newDownload; DownloadClient = downloadClient; DownloadId = downloadId; - // IsReadOnly = isReadOnly; } } } From 372442af2c4b097a9f83a55b8947df685ef6ed8c Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Thu, 30 Mar 2017 23:20:49 +0200 Subject: [PATCH 006/156] fixed broken tests. --- .../MediaFiles/UpgradeMediaFileServiceFixture.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/NzbDrone.Core.Test/MediaFiles/UpgradeMediaFileServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/UpgradeMediaFileServiceFixture.cs index 59c97a8d0..615d6a418 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/UpgradeMediaFileServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/UpgradeMediaFileServiceFixture.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.IO; +using System.Linq; using FizzWare.NBuilder; using FluentAssertions; using Marr.Data; @@ -31,10 +32,13 @@ namespace NzbDrone.Core.Test.MediaFiles .CreateNew() .Build(); - Mocker.GetMock() .Setup(c => c.FileExists(It.IsAny())) .Returns(true); + + Mocker.GetMock() + .Setup(c => c.GetParentFolder(It.IsAny())) + .Returns(c => Path.GetDirectoryName(c)); } private void GivenSingleEpisodeWithSingleEpisodeFile() From 5613ab05e02839231095a52ecd251c7cb60485ff Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Fri, 31 Mar 2017 18:52:30 +0200 Subject: [PATCH 007/156] Fixed: Sabnzbd/NzbGet not processing history items properly after last update. --- .../Blackhole/TorrentBlackholeFixture.cs | 3 ++ .../Blackhole/UsenetBlackholeFixture.cs | 3 ++ .../DelugeTests/DelugeFixture.cs | 33 +++++++++++-------- .../HadoukenTests/HadoukenFixture.cs | 5 ++- .../NzbVortexTests/NzbVortexFixture.cs | 5 ++- .../NzbgetTests/NzbgetFixture.cs | 5 ++- .../SabnzbdTests/SabnzbdFixture.cs | 3 ++ .../TransmissionTests/TransmissionFixture.cs | 3 ++ .../UTorrentTests/UTorrentFixture.cs | 3 ++ .../VuzeTests/VuzeFixture.cs | 3 ++ .../Download/Clients/Nzbget/Nzbget.cs | 4 +-- .../Download/Clients/Sabnzbd/Sabnzbd.cs | 4 +-- 12 files changed, 54 insertions(+), 20 deletions(-) diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/TorrentBlackholeFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/TorrentBlackholeFixture.cs index 5a61271cf..6b70b916f 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/TorrentBlackholeFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/TorrentBlackholeFixture.cs @@ -99,6 +99,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole var result = Subject.GetItems().Single(); VerifyCompleted(result); + + result.CanBeRemoved.Should().BeFalse(); + result.CanMoveFiles.Should().BeFalse(); } [Test] diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/UsenetBlackholeFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/UsenetBlackholeFixture.cs index d48d9e0b8..b59aacd5a 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/UsenetBlackholeFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/Blackhole/UsenetBlackholeFixture.cs @@ -77,6 +77,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole var result = Subject.GetItems().Single(); VerifyCompleted(result); + + result.CanBeRemoved.Should().BeTrue(); + result.CanMoveFiles.Should().BeTrue(); } [Test] diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DelugeTests/DelugeFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DelugeTests/DelugeFixture.cs index 4e3d10f01..f9bbc2ed0 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DelugeTests/DelugeFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DelugeTests/DelugeFixture.cs @@ -19,6 +19,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DelugeTests protected DelugeTorrent _downloading; protected DelugeTorrent _failed; protected DelugeTorrent _completed; + protected DelugeTorrent _seeding; [SetUp] public void Setup() @@ -75,7 +76,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DelugeTests Size = 1000, BytesDownloaded = 1000, Progress = 100.0, - DownloadPath = "somepath" + DownloadPath = "somepath", + IsAutoManaged = true, + StopAtRatio = true, + StopRatio = 1.0, + Ratio = 1.5 }; Mocker.GetMock() @@ -189,6 +194,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DelugeTests PrepareClientToReturnCompletedItem(); var item = Subject.GetItems().Single(); VerifyCompleted(item); + + item.CanBeRemoved.Should().BeTrue(); + item.CanMoveFiles.Should().BeTrue(); } [Test] @@ -248,11 +256,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DelugeTests item.Status.Should().Be(expectedItemStatus); } - [TestCase(DelugeTorrentStatus.Paused, DownloadItemStatus.Completed, false)] - [TestCase(DelugeTorrentStatus.Checking, DownloadItemStatus.Downloading, false)] - [TestCase(DelugeTorrentStatus.Queued, DownloadItemStatus.Completed, false)] - [TestCase(DelugeTorrentStatus.Seeding, DownloadItemStatus.Completed, false)] - public void GetItems_should_return_completed_item_as_downloadItemStatus(string apiStatus, DownloadItemStatus expectedItemStatus, bool expectedValue) + [TestCase(DelugeTorrentStatus.Paused, DownloadItemStatus.Completed)] + [TestCase(DelugeTorrentStatus.Checking, DownloadItemStatus.Downloading)] + [TestCase(DelugeTorrentStatus.Queued, DownloadItemStatus.Completed)] + [TestCase(DelugeTorrentStatus.Seeding, DownloadItemStatus.Completed)] + public void GetItems_should_return_completed_item_as_downloadItemStatus(string apiStatus, DownloadItemStatus expectedItemStatus) { _completed.State = apiStatus; @@ -261,26 +269,25 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DelugeTests var item = Subject.GetItems().Single(); item.Status.Should().Be(expectedItemStatus); - item.CanBeRemoved.Should().Be(expectedValue); - item.CanMoveFiles.Should().Be(expectedValue); } - [Test] - public void GetItems_should_check_share_ratio_for_moveFiles_and_remove() + [TestCase(0.5, false)] + [TestCase(1.01, true)] + public void GetItems_should_check_share_ratio_for_moveFiles_and_remove(double ratio, bool canBeRemoved) { _completed.State = DelugeTorrentStatus.Paused; _completed.IsAutoManaged = true; _completed.StopAtRatio = true; _completed.StopRatio = 1.0; - _completed.Ratio = 1.01; + _completed.Ratio = ratio; PrepareClientToReturnCompletedItem(); var item = Subject.GetItems().Single(); item.Status.Should().Be(DownloadItemStatus.Completed); - item.CanMoveFiles.Should().BeTrue(); - item.CanBeRemoved.Should().BeTrue(); + item.CanMoveFiles.Should().Be(canBeRemoved); + item.CanBeRemoved.Should().Be(canBeRemoved); } [Test] diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/HadoukenTests/HadoukenFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/HadoukenTests/HadoukenFixture.cs index adcffe633..5762fdf08 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/HadoukenTests/HadoukenFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/HadoukenTests/HadoukenFixture.cs @@ -190,6 +190,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.HadoukenTests PrepareClientToReturnCompletedItem(); var item = Subject.GetItems().Single(); VerifyCompleted(item); + + item.CanBeRemoved.Should().BeTrue(); + item.CanMoveFiles.Should().BeTrue(); } [Test] @@ -298,7 +301,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.HadoukenTests .Returns("hash"); var result = Subject.Download(remoteEpisode); - + Assert.IsFalse(result.Any(c => char.IsLower(c))); } diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/NzbVortexTests/NzbVortexFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/NzbVortexTests/NzbVortexFixture.cs index ccdaba3f1..bfcd4e08c 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/NzbVortexTests/NzbVortexFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/NzbVortexTests/NzbVortexFixture.cs @@ -103,7 +103,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbVortexTests public void queued_item_should_have_required_properties() { GivenQueue(_queued); - + var result = Subject.GetItems().Single(); VerifyQueued(result); @@ -139,6 +139,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbVortexTests var result = Subject.GetItems().Single(); VerifyCompleted(result); + + result.CanBeRemoved.Should().BeTrue(); + result.CanMoveFiles.Should().BeTrue(); } [Test] diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetTests/NzbgetFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetTests/NzbgetFixture.cs index 226288464..a79236740 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetTests/NzbgetFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetTests/NzbgetFixture.cs @@ -163,7 +163,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests GivenQueue(_queued); GivenHistory(null); - + var result = Subject.GetItems().Single(); VerifyQueued(result); @@ -204,6 +204,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests var result = Subject.GetItems().Single(); VerifyCompleted(result); + + result.CanBeRemoved.Should().BeTrue(); + result.CanMoveFiles.Should().BeTrue(); } [Test] diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs index e58e4b9a8..18ecc6f27 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs @@ -239,6 +239,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests var result = Subject.GetItems().Single(); VerifyCompleted(result); + + result.CanBeRemoved.Should().BeTrue(); + result.CanMoveFiles.Should().BeTrue(); } [Test] diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/TransmissionTests/TransmissionFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/TransmissionTests/TransmissionFixture.cs index c8b842cef..b9bea1da0 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/TransmissionTests/TransmissionFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/TransmissionTests/TransmissionFixture.cs @@ -41,6 +41,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests PrepareClientToReturnCompletedItem(); var item = Subject.GetItems().Single(); VerifyCompleted(item); + + item.CanBeRemoved.Should().BeTrue(); + item.CanMoveFiles.Should().BeTrue(); } [Test] diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/UTorrentTests/UTorrentFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/UTorrentTests/UTorrentFixture.cs index 1068bd762..e66ce42dc 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/UTorrentTests/UTorrentFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/UTorrentTests/UTorrentFixture.cs @@ -222,6 +222,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.UTorrentTests PrepareClientToReturnCompletedItem(); var item = Subject.GetItems().Single(); VerifyCompleted(item); + + item.CanBeRemoved.Should().BeTrue(); + item.CanMoveFiles.Should().BeTrue(); } [Test] diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/VuzeTests/VuzeFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/VuzeTests/VuzeFixture.cs index e6782eb4f..fa244d70e 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/VuzeTests/VuzeFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/VuzeTests/VuzeFixture.cs @@ -43,6 +43,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests PrepareClientToReturnCompletedItem(); var item = Subject.GetItems().Single(); VerifyCompleted(item); + + item.CanBeRemoved.Should().BeTrue(); + item.CanMoveFiles.Should().BeTrue(); } [Test] diff --git a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs index ff379f8b0..197f59c23 100644 --- a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs +++ b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs @@ -83,8 +83,6 @@ namespace NzbDrone.Core.Download.Clients.Nzbget queueItem.TotalSize = totalSize; queueItem.Category = item.Category; queueItem.DownloadClient = Definition.Name; - queueItem.CanMoveFiles = true; - queueItem.CanBeRemoved = true; if (globalStatus.DownloadPaused || remainingSize == pausedSize && remainingSize != 0) { @@ -147,6 +145,8 @@ namespace NzbDrone.Core.Download.Clients.Nzbget historyItem.Message = $"PAR Status: {item.ParStatus} - Unpack Status: {item.UnpackStatus} - Move Status: {item.MoveStatus} - Script Status: {item.ScriptStatus} - Delete Status: {item.DeleteStatus} - Mark Status: {item.MarkStatus}"; historyItem.Status = DownloadItemStatus.Completed; historyItem.RemainingTime = TimeSpan.Zero; + historyItem.CanMoveFiles = true; + historyItem.CanBeRemoved = true; if (item.DeleteStatus == "MANUAL") { diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs index 9ccc2aa9d..5da48c53c 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs @@ -78,8 +78,6 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd queueItem.TotalSize = (long)(sabQueueItem.Size * 1024 * 1024); queueItem.RemainingSize = (long)(sabQueueItem.Sizeleft * 1024 * 1024); queueItem.RemainingTime = sabQueueItem.Timeleft; - queueItem.CanBeRemoved = true; - queueItem.CanMoveFiles = true; if (sabQueue.Paused || sabQueueItem.Status == SabnzbdDownloadStatus.Paused) { @@ -162,6 +160,8 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd else if (sabHistoryItem.Status == SabnzbdDownloadStatus.Completed) { historyItem.Status = DownloadItemStatus.Completed; + historyItem.CanBeRemoved = true; + historyItem.CanMoveFiles = true; } else // Verifying/Moving etc { From e4c3418987cf189651fa704b8c9dbb86fada23f1 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Fri, 7 Apr 2017 19:10:08 +0200 Subject: [PATCH 008/156] Fixed: Failing Newznab capabilities request should trigger automatic indexer backoff logic. --- .../NewznabTests/NewznabFixture.cs | 26 +++++++++- src/NzbDrone.Core/Indexers/HttpIndexerBase.cs | 49 +++++++++---------- 2 files changed, 47 insertions(+), 28 deletions(-) diff --git a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabFixture.cs index d8dd4bae3..d5dd2cfb7 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabFixture.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Net; using FluentAssertions; using Moq; using NUnit.Framework; @@ -7,6 +8,7 @@ using NzbDrone.Common.Http; using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers.Newznab; using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common; namespace NzbDrone.Core.Test.IndexerTests.NewznabTests { @@ -43,7 +45,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests Mocker.GetMock() .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) .Returns(r => new HttpResponse(r, new HttpHeader(), recentFeed)); - + var releases = Subject.FetchRecent(); releases.Should().HaveCount(100); @@ -69,5 +71,27 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests Subject.PageSize.Should().Be(25); } + + [Test] + public void should_record_indexer_failure_if_caps_throw() + { + var request = new HttpRequest("http://my.indexer.com"); + var response = new HttpResponse(request, new HttpHeader(), new byte[0], (HttpStatusCode)429); + response.Headers["Retry-After"] = "300"; + + Mocker.GetMock() + .Setup(v => v.GetCapabilities(It.IsAny())) + .Throws(new TooManyRequestsException(request, response)); + + _caps.MaxPageSize = 30; + _caps.DefaultPageSize = 25; + + Subject.FetchRecent().Should().BeEmpty(); + + Mocker.GetMock() + .Verify(v => v.RecordFailure(It.IsAny(), TimeSpan.FromMinutes(5.0)), Times.Once()); + + ExceptionVerification.ExpectedWarns(1); + } } } diff --git a/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs b/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs index b88158b14..1405eb0d4 100644 --- a/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs +++ b/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs @@ -46,9 +46,7 @@ namespace NzbDrone.Core.Indexers return new List(); } - var generator = GetRequestGenerator(); - - return FetchReleases(generator.GetRecentRequests(), true); + return FetchReleases(g => g.GetRecentRequests(), true); } public override IList Fetch(SingleEpisodeSearchCriteria searchCriteria) @@ -58,9 +56,7 @@ namespace NzbDrone.Core.Indexers return new List(); } - var generator = GetRequestGenerator(); - - return FetchReleases(generator.GetSearchRequests(searchCriteria)); + return FetchReleases(g => g.GetSearchRequests(searchCriteria)); } public override IList Fetch(SeasonSearchCriteria searchCriteria) @@ -70,9 +66,7 @@ namespace NzbDrone.Core.Indexers return new List(); } - var generator = GetRequestGenerator(); - - return FetchReleases(generator.GetSearchRequests(searchCriteria)); + return FetchReleases(g => g.GetSearchRequests(searchCriteria)); } public override IList Fetch(DailyEpisodeSearchCriteria searchCriteria) @@ -82,9 +76,7 @@ namespace NzbDrone.Core.Indexers return new List(); } - var generator = GetRequestGenerator(); - - return FetchReleases(generator.GetSearchRequests(searchCriteria)); + return FetchReleases(g => g.GetSearchRequests(searchCriteria)); } public override IList Fetch(AnimeEpisodeSearchCriteria searchCriteria) @@ -94,9 +86,7 @@ namespace NzbDrone.Core.Indexers return new List(); } - var generator = GetRequestGenerator(); - - return FetchReleases(generator.GetSearchRequests(searchCriteria)); + return FetchReleases(g => g.GetSearchRequests(searchCriteria)); } public override IList Fetch(SpecialEpisodeSearchCriteria searchCriteria) @@ -106,20 +96,21 @@ namespace NzbDrone.Core.Indexers return new List(); } - var generator = GetRequestGenerator(); - - return FetchReleases(generator.GetSearchRequests(searchCriteria)); + return FetchReleases(g => g.GetSearchRequests(searchCriteria)); } - protected virtual IList FetchReleases(IndexerPageableRequestChain pageableRequestChain, bool isRecent = false) + protected virtual IList FetchReleases(Func pageableRequestChainSelector, bool isRecent = false) { var releases = new List(); var url = string.Empty; - var parser = GetParser(); - try { + var generator = GetRequestGenerator(); + var parser = GetParser(); + + var pageableRequestChain = pageableRequestChainSelector(generator); + var fullyUpdated = false; ReleaseInfo lastReleaseInfo = null; if (isRecent) @@ -222,18 +213,22 @@ namespace NzbDrone.Core.Indexers _logger.Warn("{0} {1} {2}", this, url, webException.Message); } } - catch (HttpException httpException) + catch (TooManyRequestsException ex) { - if ((int)httpException.Response.StatusCode == 429) + if (ex.RetryAfter != TimeSpan.Zero) { - _indexerStatusService.RecordFailure(Definition.Id, TimeSpan.FromHours(1)); - _logger.Warn("API Request Limit reached for {0}", this); + _indexerStatusService.RecordFailure(Definition.Id, ex.RetryAfter); } else { - _indexerStatusService.RecordFailure(Definition.Id); - _logger.Warn("{0} {1}", this, httpException.Message); + _indexerStatusService.RecordFailure(Definition.Id, TimeSpan.FromHours(1)); } + _logger.Warn("API Request Limit reached for {0}", this); + } + catch (HttpException ex) + { + _indexerStatusService.RecordFailure(Definition.Id); + _logger.Warn("{0} {1}", this, ex.Message); } catch (RequestLimitReachedException) { From 94886e767b074ef4d1c219de26bf7ba82c8da1f6 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Fri, 7 Apr 2017 19:32:12 +0200 Subject: [PATCH 009/156] Fixed: UnsupportedFeedException should log error for each item --- .../TorrentRssIndexerFixture.cs | 19 +++++++++++++++++-- src/NzbDrone.Core/Indexers/RssParser.cs | 5 +++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/NzbDrone.Core.Test/IndexerTests/TorrentRssIndexerTests/TorrentRssIndexerFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/TorrentRssIndexerTests/TorrentRssIndexerFixture.cs index e1ad09a5e..23d653e5a 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/TorrentRssIndexerTests/TorrentRssIndexerFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/TorrentRssIndexerTests/TorrentRssIndexerFixture.cs @@ -5,9 +5,11 @@ using Moq; using NUnit.Framework; using NzbDrone.Common.Http; using NzbDrone.Core.Indexers; +using NzbDrone.Core.Indexers.Exceptions; using NzbDrone.Core.Indexers.TorrentRss; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common; namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests { @@ -48,7 +50,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests releases.Should().HaveCount(50); releases.First().Should().BeOfType(); - + var torrentInfo = (TorrentInfo)releases.First(); torrentInfo.Title.Should().Be("Conan.2015.02.05.Jeff.Bridges.720p.HDTV.X264-CROOKS"); @@ -239,7 +241,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests torrentInfo.Title.Should().Be("DAYS - 05 (1280x720 HEVC2 AAC).mkv"); torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent); - torrentInfo.DownloadUrl.Should().Be("http://storage.animetosho.org/torrents/4b58360143d59a55cbd922397a3eaa378165f3ff/DAYS%20-%2005%20%281280x720%20HEVC2%20AAC%29.torrent"); + torrentInfo.DownloadUrl.Should().Be("http://storage.animetosho.org/torrents/4b58360143d59a55cbd922397a3eaa378165f3ff/DAYS%20-%2005%20%281280x720%20HEVC2%20AAC%29.torrent"); } [Test] @@ -258,5 +260,18 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent); torrentInfo.DownloadUrl.Should().Be("https://alpharatio.cc/torrents.php?action=download&authkey=private_auth_key&torrent_pass=private_torrent_pass&id=465831"); } + + [Test] + public void should_record_indexer_failure_if_unsupported_feed() + { + GivenRecentFeedResponse("TorrentRss/invalid/TorrentDay_NoPubDate.xml"); + + Subject.FetchRecent().Should().BeEmpty(); + + Mocker.GetMock() + .Verify(v => v.RecordFailure(It.IsAny(), TimeSpan.Zero), Times.Once()); + + ExceptionVerification.ExpectedErrors(1); + } } } diff --git a/src/NzbDrone.Core/Indexers/RssParser.cs b/src/NzbDrone.Core/Indexers/RssParser.cs index 4a4919288..1cd46e754 100644 --- a/src/NzbDrone.Core/Indexers/RssParser.cs +++ b/src/NzbDrone.Core/Indexers/RssParser.cs @@ -63,6 +63,11 @@ namespace NzbDrone.Core.Indexers releases.AddIfNotNull(reportInfo); } + catch (UnsupportedFeedException itemEx) + { + itemEx.Data.Add("Item", item.Title()); + throw; + } catch (Exception itemEx) { itemEx.Data.Add("Item", item.Title()); From c9d1807670c31696b0026c5e7fcf8cc093f86618 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Fri, 7 Apr 2017 20:42:39 +0200 Subject: [PATCH 010/156] Sentry should use CleanseLogMessage. --- .../Instrumentation/Sentry/SentryTarget.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs b/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs index b0b20eeee..744d8063a 100644 --- a/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs +++ b/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs @@ -111,11 +111,18 @@ namespace NzbDrone.Common.Instrumentation.Sentry return; } - var extras = logEvent.Properties.ToDictionary(x => x.Key.ToString(), x => x.Value.ToString()); + var extras = logEvent.Properties.ToDictionary(x => x.Key.ToString(), x => CleanseLogMessage.Cleanse(x.Value.ToString())); _client.Logger = logEvent.LoggerName; + string cleansedMessage = CleanseLogMessage.Cleanse(logEvent.Message); + string cleansedFormattedMessage = cleansedMessage; - var sentryMessage = new SentryMessage(logEvent.Message, logEvent.Parameters); + if (logEvent.Parameters != null) + { + cleansedFormattedMessage = CleanseLogMessage.Cleanse(string.Format(logEvent.Message, logEvent.Parameters)); + } + + var sentryMessage = new SentryMessage(cleansedFormattedMessage); var sentryEvent = new SentryEvent(logEvent.Exception) { @@ -126,7 +133,7 @@ namespace NzbDrone.Common.Instrumentation.Sentry { logEvent.Level.ToString(), logEvent.LoggerName, - logEvent.Message + cleansedMessage } }; From 35741b9cae954a5474f99499a8d0063a026517c4 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Fri, 7 Apr 2017 22:07:42 +0200 Subject: [PATCH 011/156] Added a few more files to ignore during file copy. --- src/NzbDrone.Common/Disk/DiskTransferService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NzbDrone.Common/Disk/DiskTransferService.cs b/src/NzbDrone.Common/Disk/DiskTransferService.cs index 3f93c11e4..913894abb 100644 --- a/src/NzbDrone.Common/Disk/DiskTransferService.cs +++ b/src/NzbDrone.Common/Disk/DiskTransferService.cs @@ -590,7 +590,7 @@ namespace NzbDrone.Common.Disk private bool ShouldIgnore(FileInfo file) { - if (file.Name.StartsWith(".nfs")) + if (file.Name.StartsWith(".nfs") || file.Name == "debug.log" || file.Name.EndsWith(".socket")) { _logger.Trace("Ignoring file {0}", file.FullName); return true; From cd450a44bf34da3720f4a1551acd2f0ce2aa313d Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Fri, 7 Apr 2017 22:09:49 +0200 Subject: [PATCH 012/156] Should not empty install folder, MirrorFolder will take care of it. --- src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs b/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs index 9c2866330..2f9c04c2d 100644 --- a/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs +++ b/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs @@ -110,9 +110,6 @@ namespace NzbDrone.Update.UpdateEngine try { - _logger.Info("Emptying installation folder"); - _diskProvider.EmptyFolder(installationFolder); - _logger.Info("Copying new files to target folder"); _diskTransferService.MirrorFolder(_appFolderInfo.GetUpdatePackageFolder(), installationFolder); From 924fe80997b6227693fcc6a29aee554f86ef3a4e Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Fri, 7 Apr 2017 22:36:39 +0200 Subject: [PATCH 013/156] Fixed RssParser test. --- .../TorrentRssSettingsDetectorFixture.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/NzbDrone.Core.Test/IndexerTests/TorrentRssIndexerTests/TorrentRssSettingsDetectorFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/TorrentRssIndexerTests/TorrentRssSettingsDetectorFixture.cs index e0e87b178..74934b160 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/TorrentRssIndexerTests/TorrentRssSettingsDetectorFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/TorrentRssIndexerTests/TorrentRssSettingsDetectorFixture.cs @@ -287,9 +287,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests var ex = Assert.Throws(() => Subject.Detect(_indexerSettings)); - ex.Message.Should().Contain("Empty feed"); - - ExceptionVerification.ExpectedErrors(1); + ex.Message.Should().Contain("Rss feed must have a pubDate"); } [TestCase("Torrentleech/Torrentleech.xml")] From b70d1679119a6aaaf2ec6fd48a441092bacbfcd3 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Sat, 8 Apr 2017 12:38:39 +0200 Subject: [PATCH 014/156] Apply Cleanse to Exception Data as well. --- .../Extensions/ExceptionExtensions.cs | 55 +++++++++++ .../Instrumentation/CleansingJsonVisitor.cs | 47 +++++++++ .../Sentry/SentryPacketCleanser.cs | 31 ++++++ .../Instrumentation/Sentry/SentryTarget.cs | 14 +-- .../Sentry/SonarrJsonPacketFactory.cs | 12 ++- src/NzbDrone.Common/NzbDrone.Common.csproj | 4 + src/NzbDrone.Common/Serializer/JsonVisitor.cs | 95 +++++++++++++++++++ src/NzbDrone.Core/Indexers/HttpIndexerBase.cs | 7 +- .../Newznab/NewznabCapabilitiesProvider.cs | 2 + src/NzbDrone.Core/Indexers/RssParser.cs | 9 +- .../TorrentRss/TorrentRssSettingsDetector.cs | 32 ++++--- 11 files changed, 276 insertions(+), 32 deletions(-) create mode 100644 src/NzbDrone.Common/Extensions/ExceptionExtensions.cs create mode 100644 src/NzbDrone.Common/Instrumentation/CleansingJsonVisitor.cs create mode 100644 src/NzbDrone.Common/Instrumentation/Sentry/SentryPacketCleanser.cs create mode 100644 src/NzbDrone.Common/Serializer/JsonVisitor.cs diff --git a/src/NzbDrone.Common/Extensions/ExceptionExtensions.cs b/src/NzbDrone.Common/Extensions/ExceptionExtensions.cs new file mode 100644 index 000000000..c719cef3e --- /dev/null +++ b/src/NzbDrone.Common/Extensions/ExceptionExtensions.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Common.Extensions +{ + public static class ExceptionExtensions + { + public static T WithData(this T ex, string key, string value) where T : Exception + { + ex.AddData(key, value); + + return ex; + } + public static T WithData(this T ex, string key, int value) where T : Exception + { + ex.AddData(key, value.ToString()); + + return ex; + } + + public static T WithData(this T ex, string key, Http.HttpUri value) where T : Exception + { + ex.AddData(key, value.ToString()); + + return ex; + } + + + public static T WithData(this T ex, Http.HttpResponse response, int maxSampleLength = 512) where T : Exception + { + if (response == null || response.Content == null) return ex; + + var contentSample = response.Content.Substring(0, Math.Min(response.Content.Length, 512)); + + if (response.Headers != null) + { + ex.AddData("ContentType", response.Headers.ContentType ?? string.Empty); + } + ex.AddData("ContentLength", response.Content.Length.ToString()); + ex.AddData("ContentSample", contentSample); + + return ex; + } + + + private static void AddData(this Exception ex, string key, string value) + { + if (value.IsNullOrWhiteSpace()) return; + + ex.Data[key] = value; + } + } +} diff --git a/src/NzbDrone.Common/Instrumentation/CleansingJsonVisitor.cs b/src/NzbDrone.Common/Instrumentation/CleansingJsonVisitor.cs new file mode 100644 index 000000000..f33f4587b --- /dev/null +++ b/src/NzbDrone.Common/Instrumentation/CleansingJsonVisitor.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Newtonsoft.Json.Linq; +using NzbDrone.Common.Serializer; + +namespace NzbDrone.Common.Instrumentation +{ + public class CleansingJsonVisitor : JsonVisitor + { + public override void Visit(JArray json) + { + for (var i = 0; i < json.Count; i++) + { + if (json[i].Type == JTokenType.String) + { + var text = json[i].Value(); + json[i] = new JValue(CleanseLogMessage.Cleanse(text)); + } + } + foreach (JToken token in json) + { + Visit(token); + } + } + + public override void Visit(JProperty property) + { + if (property.Value.Type == JTokenType.String) + { + property.Value = CleanseValue(property.Value as JValue); + } + else + { + base.Visit(property); + } + } + + private JValue CleanseValue(JValue value) + { + var text = value.Value(); + var cleansed = CleanseLogMessage.Cleanse(text); + return new JValue(cleansed); + } + } +} diff --git a/src/NzbDrone.Common/Instrumentation/Sentry/SentryPacketCleanser.cs b/src/NzbDrone.Common/Instrumentation/Sentry/SentryPacketCleanser.cs new file mode 100644 index 000000000..0815c0d48 --- /dev/null +++ b/src/NzbDrone.Common/Instrumentation/Sentry/SentryPacketCleanser.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Newtonsoft.Json.Linq; + +namespace NzbDrone.Common.Instrumentation.Sentry +{ + public class SentryPacketCleanser + { + public void CleansePacket(SonarrSentryPacket packet) + { + packet.Message = CleanseLogMessage.Cleanse(packet.Message); + + if (packet.Fingerprint != null) + { + for (var i = 0; i < packet.Fingerprint.Length; i++) + { + packet.Fingerprint[i] = CleanseLogMessage.Cleanse(packet.Fingerprint[i]); + } + } + + if (packet.Extra != null) + { + var target = JObject.FromObject(packet.Extra); + new CleansingJsonVisitor().Visit(target); + packet.Extra = target; + } + } + } +} diff --git a/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs b/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs index 744d8063a..176884a31 100644 --- a/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs +++ b/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs @@ -111,18 +111,10 @@ namespace NzbDrone.Common.Instrumentation.Sentry return; } - var extras = logEvent.Properties.ToDictionary(x => x.Key.ToString(), x => CleanseLogMessage.Cleanse(x.Value.ToString())); + var extras = logEvent.Properties.ToDictionary(x => x.Key.ToString(), x => x.Value.ToString()); _client.Logger = logEvent.LoggerName; - string cleansedMessage = CleanseLogMessage.Cleanse(logEvent.Message); - string cleansedFormattedMessage = cleansedMessage; - - if (logEvent.Parameters != null) - { - cleansedFormattedMessage = CleanseLogMessage.Cleanse(string.Format(logEvent.Message, logEvent.Parameters)); - } - - var sentryMessage = new SentryMessage(cleansedFormattedMessage); + var sentryMessage = new SentryMessage(logEvent.Message, logEvent.Parameters); var sentryEvent = new SentryEvent(logEvent.Exception) { @@ -133,7 +125,7 @@ namespace NzbDrone.Common.Instrumentation.Sentry { logEvent.Level.ToString(), logEvent.LoggerName, - cleansedMessage + logEvent.Message } }; diff --git a/src/NzbDrone.Common/Instrumentation/Sentry/SonarrJsonPacketFactory.cs b/src/NzbDrone.Common/Instrumentation/Sentry/SonarrJsonPacketFactory.cs index fb639fb2f..3ba6b499c 100644 --- a/src/NzbDrone.Common/Instrumentation/Sentry/SonarrJsonPacketFactory.cs +++ b/src/NzbDrone.Common/Instrumentation/Sentry/SonarrJsonPacketFactory.cs @@ -6,6 +6,13 @@ namespace NzbDrone.Common.Instrumentation.Sentry { public class SonarrJsonPacketFactory : IJsonPacketFactory { + private readonly SentryPacketCleanser _cleanser; + + public SonarrJsonPacketFactory() + { + _cleanser = new SentryPacketCleanser(); + } + private static string ShortenPath(string path) { @@ -37,6 +44,8 @@ namespace NzbDrone.Common.Instrumentation.Sentry frame.Filename = ShortenPath(frame.Filename); } } + + _cleanser.CleansePacket(packet); } catch (Exception) { @@ -46,7 +55,6 @@ namespace NzbDrone.Common.Instrumentation.Sentry return packet; } - [Obsolete] public JsonPacket Create(string project, SentryMessage message, ErrorLevel level = ErrorLevel.Info, IDictionary tags = null, string[] fingerprint = null, object extra = null) @@ -61,4 +69,4 @@ namespace NzbDrone.Common.Instrumentation.Sentry throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj index 34ee38755..928b0e1bb 100644 --- a/src/NzbDrone.Common/NzbDrone.Common.csproj +++ b/src/NzbDrone.Common/NzbDrone.Common.csproj @@ -136,6 +136,7 @@ + @@ -175,12 +176,14 @@ + + @@ -205,6 +208,7 @@ + diff --git a/src/NzbDrone.Common/Serializer/JsonVisitor.cs b/src/NzbDrone.Common/Serializer/JsonVisitor.cs new file mode 100644 index 000000000..87fdeeeec --- /dev/null +++ b/src/NzbDrone.Common/Serializer/JsonVisitor.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Newtonsoft.Json.Linq; + +namespace NzbDrone.Common.Serializer +{ + + public class JsonVisitor + { + protected void Dispatch(JToken json) + { + switch (json.Type) + { + case JTokenType.Object: + Visit(json as JObject); + break; + + case JTokenType.Array: + Visit(json as JArray); + break; + + case JTokenType.Raw: + Visit(json as JRaw); + break; + + case JTokenType.Constructor: + Visit(json as JConstructor); + break; + + case JTokenType.Property: + Visit(json as JProperty); + break; + + case JTokenType.Comment: + case JTokenType.Integer: + case JTokenType.Float: + case JTokenType.String: + case JTokenType.Boolean: + case JTokenType.Null: + case JTokenType.Undefined: + case JTokenType.Date: + case JTokenType.Bytes: + case JTokenType.Guid: + case JTokenType.Uri: + case JTokenType.TimeSpan: + Visit(json as JValue); + break; + + default: + break; + } + } + + public virtual void Visit(JToken json) + { + Dispatch(json); + } + + public virtual void Visit(JContainer json) + { + Dispatch(json); + } + + public virtual void Visit(JArray json) + { + foreach (JToken token in json) + { + Visit(token); + } + } + public virtual void Visit(JConstructor json) + { + } + + public virtual void Visit(JObject json) + { + foreach (JProperty property in json.Properties()) + { + Visit(property); + } + } + + public virtual void Visit(JProperty property) + { + Visit(property.Value); + } + + public virtual void Visit(JValue value) + { + + } + } +} diff --git a/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs b/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs index 1405eb0d4..75ad1354e 100644 --- a/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs +++ b/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs @@ -243,6 +243,7 @@ namespace NzbDrone.Core.Indexers catch (CloudFlareCaptchaException ex) { _indexerStatusService.RecordFailure(Definition.Id); + ex.WithData("FeedUrl", url); if (ex.IsExpired) { _logger.Error(ex, "Expired CAPTCHA token for {0}, please refresh in indexer settings.", this); @@ -257,11 +258,11 @@ namespace NzbDrone.Core.Indexers _indexerStatusService.RecordFailure(Definition.Id); _logger.Warn(ex, "{0}", url); } - catch (Exception feedEx) + catch (Exception ex) { _indexerStatusService.RecordFailure(Definition.Id); - feedEx.Data.Add("FeedUrl", url); - _logger.Error(feedEx, "An error occurred while processing feed. {0}", url); + ex.WithData("FeedUrl", url); + _logger.Error(ex, "An error occurred while processing feed. {0}", url); } return CleanupReleases(releases); diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilitiesProvider.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilitiesProvider.cs index a3eac6d23..6e870ec3d 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilitiesProvider.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilitiesProvider.cs @@ -69,6 +69,8 @@ namespace NzbDrone.Core.Indexers.Newznab catch (XmlException ex) { _logger.Debug(ex, "Failed to parse newznab api capabilities for {0}.", indexerSettings.Url); + + ex.WithData(response); throw; } catch (Exception ex) diff --git a/src/NzbDrone.Core/Indexers/RssParser.cs b/src/NzbDrone.Core/Indexers/RssParser.cs index 1cd46e754..93304f2fd 100644 --- a/src/NzbDrone.Core/Indexers/RssParser.cs +++ b/src/NzbDrone.Core/Indexers/RssParser.cs @@ -65,12 +65,14 @@ namespace NzbDrone.Core.Indexers } catch (UnsupportedFeedException itemEx) { - itemEx.Data.Add("Item", item.Title()); + itemEx.WithData("FeedUrl", indexerResponse.Request.Url); + itemEx.WithData("ItemTitle", item.Title()); throw; } catch (Exception itemEx) { - itemEx.Data.Add("Item", item.Title()); + itemEx.WithData("FeedUrl", indexerResponse.Request.Url); + itemEx.WithData("ItemTitle", item.Title()); _logger.Error(itemEx, "An error occurred while processing feed item from {0}", indexerResponse.Request.Url); } } @@ -95,8 +97,7 @@ namespace NzbDrone.Core.Indexers var contentSample = indexerResponse.Content.Substring(0, Math.Min(indexerResponse.Content.Length, 512)); _logger.Debug("Truncated response content (originally {0} characters): {1}", indexerResponse.Content.Length, contentSample); - ex.Data.Add("ContentLength", indexerResponse.Content.Length); - ex.Data.Add("ContentSample", contentSample); + ex.WithData(indexerResponse.HttpResponse); throw; } diff --git a/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssSettingsDetector.cs b/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssSettingsDetector.cs index c20c09b2a..53ee117cc 100644 --- a/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssSettingsDetector.cs +++ b/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssSettingsDetector.cs @@ -40,23 +40,31 @@ namespace NzbDrone.Core.Indexers.TorrentRss { _logger.Debug("Evaluating TorrentRss feed '{0}'", indexerSettings.BaseUrl); - var requestGenerator = new TorrentRssIndexerRequestGenerator { Settings = indexerSettings }; - var request = requestGenerator.GetRecentRequests().GetAllTiers().First().First(); - - HttpResponse httpResponse = null; try { - httpResponse = _httpClient.Execute(request.HttpRequest); + var requestGenerator = new TorrentRssIndexerRequestGenerator { Settings = indexerSettings }; + var request = requestGenerator.GetRecentRequests().GetAllTiers().First().First(); + + HttpResponse httpResponse = null; + try + { + httpResponse = _httpClient.Execute(request.HttpRequest); + } + catch (Exception ex) + { + _logger.Warn(ex, string.Format("Unable to connect to indexer {0}: {1}", request.Url, ex.Message)); + return null; + } + + var indexerResponse = new IndexerResponse(request, httpResponse); + return GetParserSettings(indexerResponse, indexerSettings); } catch (Exception ex) { - _logger.Warn(ex, string.Format("Unable to connect to indexer {0}: {1}", request.Url, ex.Message)); - return null; + ex.WithData("FeedUrl", indexerSettings.BaseUrl); + throw; } - - var indexerResponse = new IndexerResponse(request, httpResponse); - return GetParserSettings(indexerResponse, indexerSettings); - } + } private TorrentRssIndexerParserSettings GetParserSettings(IndexerResponse response, TorrentRssIndexerSettings indexerSettings) { @@ -140,7 +148,7 @@ namespace NzbDrone.Core.Indexers.TorrentRss _logger.Trace("Feed doesn't have Seeders in Description, disabling option."); parser.ParseSeedersInDescription = settings.ParseSeedersInDescription = false; } - + if (!releases.Any(r => r.Size < ValidSizeThreshold)) { _logger.Trace("Feed has valid size in enclosure."); From b485bdaeecb48ae6be0ff7a885b5eb014b7082c0 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sat, 8 Apr 2017 08:20:57 -0700 Subject: [PATCH 015/156] Fixed: Unable to execute custom scripts if IMDB ID is null Fixes #1825 --- .../Processes/ProcessProvider.cs | 21 ++++++++++++++++++- .../CustomScript/CustomScript.cs | 6 +++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/NzbDrone.Common/Processes/ProcessProvider.cs b/src/NzbDrone.Common/Processes/ProcessProvider.cs index 49e61c621..0ddab771f 100644 --- a/src/NzbDrone.Common/Processes/ProcessProvider.cs +++ b/src/NzbDrone.Common/Processes/ProcessProvider.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using NLog; using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Extensions; using NzbDrone.Common.Model; namespace NzbDrone.Common.Processes @@ -129,7 +130,25 @@ namespace NzbDrone.Common.Processes { foreach (DictionaryEntry environmentVariable in environmentVariables) { - startInfo.EnvironmentVariables.Add(environmentVariable.Key.ToString(), environmentVariable.Value.ToString()); + try + { + _logger.Trace("Setting environment variable '{0}' to '{1}'", environmentVariable.Key, environmentVariable.Value); + startInfo.EnvironmentVariables.Add(environmentVariable.Key.ToString(), environmentVariable.Value.ToString()); + } + catch (Exception e) + { + if (environmentVariable.Value == null) + { + _logger.Error(e, "Unable to set environment variable '{0}', value is null", environmentVariable.Key); + } + + else + { + _logger.Error(e, "Unable to set environment variable '{0}'", environmentVariable.Key); + } + + throw; + } } } diff --git a/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs b/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs index 4686ed939..d29c956ee 100644 --- a/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs +++ b/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs @@ -40,7 +40,7 @@ namespace NzbDrone.Core.Notifications.CustomScript environmentVariables.Add("Sonarr_Series_Title", series.Title); environmentVariables.Add("Sonarr_Series_TvdbId", series.TvdbId.ToString()); environmentVariables.Add("Sonarr_Series_TvMazeId", series.TvMazeId.ToString()); - environmentVariables.Add("Sonarr_Series_ImdbId", series.ImdbId); + environmentVariables.Add("Sonarr_Series_ImdbId", series.ImdbId ?? string.Empty); environmentVariables.Add("Sonarr_Series_Type", series.SeriesType.ToString()); environmentVariables.Add("Sonarr_Release_EpisodeCount", remoteEpisode.Episodes.Count.ToString()); environmentVariables.Add("Sonarr_Release_SeasonNumber", remoteEpisode.ParsedEpisodeInfo.SeasonNumber.ToString()); @@ -72,7 +72,7 @@ namespace NzbDrone.Core.Notifications.CustomScript environmentVariables.Add("Sonarr_Series_Path", series.Path); environmentVariables.Add("Sonarr_Series_TvdbId", series.TvdbId.ToString()); environmentVariables.Add("Sonarr_Series_TvMazeId", series.TvMazeId.ToString()); - environmentVariables.Add("Sonarr_Series_ImdbId", series.ImdbId); + environmentVariables.Add("Sonarr_Series_ImdbId", series.ImdbId ?? string.Empty); environmentVariables.Add("Sonarr_Series_Type", series.SeriesType.ToString()); environmentVariables.Add("Sonarr_EpisodeFile_Id", episodeFile.Id.ToString()); environmentVariables.Add("Sonarr_EpisodeFile_EpisodeCount", episodeFile.Episodes.Value.Count.ToString()); @@ -109,7 +109,7 @@ namespace NzbDrone.Core.Notifications.CustomScript environmentVariables.Add("Sonarr_Series_Path", series.Path); environmentVariables.Add("Sonarr_Series_TvdbId", series.TvdbId.ToString()); environmentVariables.Add("Sonarr_Series_TvMazeId", series.TvMazeId.ToString()); - environmentVariables.Add("Sonarr_Series_ImdbId", series.ImdbId); + environmentVariables.Add("Sonarr_Series_ImdbId", series.ImdbId ?? string.Empty); environmentVariables.Add("Sonarr_Series_Type", series.SeriesType.ToString()); ExecuteScript(environmentVariables); From b63bcd16a799bb6de0f9ed5d45634701c01515a5 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Mon, 10 Apr 2017 17:46:08 +0200 Subject: [PATCH 016/156] Fixed: Sample check has too little margin for 2 min anime with 1 minute files. Lowered to 15 sec. --- .../EpisodeImport/SampleServiceFixture.cs | 18 ++++++++++++++++++ .../MediaFiles/EpisodeImport/DetectSample.cs | 6 ++++++ 2 files changed, 24 insertions(+) diff --git a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/SampleServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/SampleServiceFixture.cs index febb5c42f..dd3090116 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/SampleServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/SampleServiceFixture.cs @@ -121,6 +121,24 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport ShouldBeFalse(); } + [Test] + public void should_return_false_if_runtime_greater_than_anime_short_minimum() + { + _series.Runtime = 2; + GivenRuntime(60); + + ShouldBeFalse(); + } + + [Test] + public void should_return_true_if_runtime_less_than_anime_short_minimum() + { + _series.Runtime = 2; + GivenRuntime(10); + + ShouldBeTrue(); + } + [Test] public void should_fall_back_to_file_size_if_mediainfo_dll_not_found_acceptable_size() { diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/DetectSample.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/DetectSample.cs index 27492d56a..0f890eafd 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/DetectSample.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/DetectSample.cs @@ -100,6 +100,12 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport private int GetMinimumAllowedRuntime(Series series) { + //Anime short - 15 seconds + if (series.Runtime <= 3) + { + return 15; + } + //Webisodes - 90 seconds if (series.Runtime <= 10) { From 41f769790d55b66f5c23fe5b4f7afed2f4685dd2 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Tue, 11 Apr 2017 17:57:29 -0700 Subject: [PATCH 017/156] Fix issue adding a series when TitleSlug for another series is null Fixed: Adding a series when an existing series is has a null slug Closes #1840 --- .../NzbDrone.Core.Test.csproj | 1 + .../SeriesTitleSlugValidatorFixture.cs | 76 +++++++++++++++++++ .../Tv/SeriesTitleSlugValidator.cs | 9 ++- src/NzbDrone.Test.Common/App.config | 4 + .../NzbDrone.Test.Common.csproj | 4 + src/NzbDrone.Test.Common/TestValidator.cs | 16 ++++ src/NzbDrone.Test.Common/packages.config | 1 + 7 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 src/NzbDrone.Core.Test/TvTests/SeriesTitleSlugValidatorFixture.cs create mode 100644 src/NzbDrone.Test.Common/TestValidator.cs diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index f8e7b07c9..61dff5f73 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -383,6 +383,7 @@ + diff --git a/src/NzbDrone.Core.Test/TvTests/SeriesTitleSlugValidatorFixture.cs b/src/NzbDrone.Core.Test/TvTests/SeriesTitleSlugValidatorFixture.cs new file mode 100644 index 000000000..9cc7c435b --- /dev/null +++ b/src/NzbDrone.Core.Test/TvTests/SeriesTitleSlugValidatorFixture.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; +using System.Linq; +using FizzWare.NBuilder; +using FluentAssertions; +using FluentValidation.Validators; +using NUnit.Framework; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.TvTests +{ + [TestFixture] + public class SeriesTitleSlugValidatorFixture : CoreTest + { + private List _series; + private TestValidator _validator; + + [SetUp] + public void Setup() + { + _series = Builder.CreateListOfSize(1) + .Build() + .ToList(); + + _validator = new TestValidator + { + v => v.RuleFor(s => s.TitleSlug).SetValidator(Subject) + }; + + Mocker.GetMock() + .Setup(s => s.GetAllSeries()) + .Returns(_series); + } + + [Test] + public void should_not_be_valid_if_there_is_an_existing_series_with_the_same_title_slug() + { + var series = Builder.CreateNew() + .With(s => s.Id = 100) + .With(s => s.TitleSlug = _series.First().TitleSlug) + .Build(); + + _validator.Validate(series).IsValid.Should().BeFalse(); + } + + [Test] + public void should_be_valid_if_there_is_not_an_existing_series_with_the_same_title_slug() + { + var series = Builder.CreateNew() + .With(s => s.TitleSlug = "MyTitleSlug") + .Build(); + + _validator.Validate(series).IsValid.Should().BeTrue(); + } + + [Test] + public void should_be_valid_if_there_is_an_existing_series_with_a_null_title_slug() + { + _series.First().TitleSlug = null; + + var series = Builder.CreateNew() + .With(s => s.TitleSlug = "MyTitleSlug") + .Build(); + + _validator.Validate(series).IsValid.Should().BeTrue(); + } + + [Test] + public void should_be_valid_when_updating_an_existing_series() + { + _validator.Validate(_series.First().JsonClone()).IsValid.Should().BeTrue(); + } + } +} diff --git a/src/NzbDrone.Core/Tv/SeriesTitleSlugValidator.cs b/src/NzbDrone.Core/Tv/SeriesTitleSlugValidator.cs index 97ff29095..ce3d7ecbf 100644 --- a/src/NzbDrone.Core/Tv/SeriesTitleSlugValidator.cs +++ b/src/NzbDrone.Core/Tv/SeriesTitleSlugValidator.cs @@ -1,4 +1,6 @@ -using FluentValidation.Validators; +using System.Linq; +using FluentValidation.Validators; +using NzbDrone.Common.Extensions; namespace NzbDrone.Core.Tv { @@ -16,10 +18,13 @@ namespace NzbDrone.Core.Tv { if (context.PropertyValue == null) return true; + dynamic instance = context.ParentContext.InstanceToValidate; var instanceId = (int)instance.Id; - return !_seriesService.GetAllSeries().Exists(s => s.TitleSlug.Equals(context.PropertyValue.ToString()) && s.Id != instanceId); + return !_seriesService.GetAllSeries().Where(s => s.TitleSlug.IsNotNullOrWhiteSpace()) + .ToList() + .Exists(s => s.TitleSlug.Equals(context.PropertyValue.ToString()) && s.Id != instanceId); } } } diff --git a/src/NzbDrone.Test.Common/App.config b/src/NzbDrone.Test.Common/App.config index 886337c3a..c7a8ca18c 100644 --- a/src/NzbDrone.Test.Common/App.config +++ b/src/NzbDrone.Test.Common/App.config @@ -21,6 +21,10 @@ + + + + \ No newline at end of file diff --git a/src/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj b/src/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj index 08bf03d3f..addbe954c 100644 --- a/src/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj +++ b/src/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj @@ -43,6 +43,9 @@ ..\packages\FluentAssertions.4.19.0\lib\net40\FluentAssertions.Core.dll + + ..\packages\FluentValidation.6.2.1.0\lib\portable-net40+sl50+wp80+win8+wpa81\FluentValidation.dll + ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll True @@ -94,6 +97,7 @@ + diff --git a/src/NzbDrone.Test.Common/TestValidator.cs b/src/NzbDrone.Test.Common/TestValidator.cs new file mode 100644 index 000000000..2801eb442 --- /dev/null +++ b/src/NzbDrone.Test.Common/TestValidator.cs @@ -0,0 +1,16 @@ +using System; +using FluentValidation; + +namespace NzbDrone.Test.Common +{ + public class TestValidator : InlineValidator + { + public TestValidator(params Action>[] actions) + { + foreach (var action in actions) + { + action(this); + } + } + } +} diff --git a/src/NzbDrone.Test.Common/packages.config b/src/NzbDrone.Test.Common/packages.config index ea89c79fc..2a00b679d 100644 --- a/src/NzbDrone.Test.Common/packages.config +++ b/src/NzbDrone.Test.Common/packages.config @@ -2,6 +2,7 @@ + From 413ce1d9a7873c642b6a95c0ad255abd7f89e7dc Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Tue, 11 Apr 2017 20:32:34 -0700 Subject: [PATCH 018/156] Fixed: Double periods in extra file names after rename --- src/NzbDrone.Core/Extras/Files/ExtraFileManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/NzbDrone.Core/Extras/Files/ExtraFileManager.cs b/src/NzbDrone.Core/Extras/Files/ExtraFileManager.cs index f21e989aa..6d4450f72 100644 --- a/src/NzbDrone.Core/Extras/Files/ExtraFileManager.cs +++ b/src/NzbDrone.Core/Extras/Files/ExtraFileManager.cs @@ -90,7 +90,6 @@ namespace NzbDrone.Core.Extras.Files filenameBuilder.Append(fileNameSuffix); } - filenameBuilder.Append("."); filenameBuilder.Append(extraFile.Extension); var existingFileName = Path.Combine(series.Path, extraFile.RelativePath); From fa006d85fd7e8beb19cc5196e655dcaffd3f7413 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Sat, 11 Mar 2017 19:49:32 +0100 Subject: [PATCH 019/156] New: Check whether an existing episode file was deleted before grabbing an upgrade, to avoid timing issues in combination with Ignore Deleted Episodes. --- .../DownloadDecisionMakerFixture.cs | 38 ++++- .../DeletedEpisodeFileSpecificationFixture.cs | 139 ++++++++++++++++++ .../NzbDrone.Core.Test.csproj | 1 + .../DecisionEngine/DownloadDecisionMaker.cs | 12 +- .../IDecisionEngineSpecification.cs | 2 + .../DecisionEngine/SpecificationPriority.cs | 10 ++ .../AcceptableSizeSpecification.cs | 1 + .../AnimeVersionUpgradeSpecification.cs | 1 + .../Specifications/BlacklistSpecification.cs | 3 +- .../Specifications/CutoffSpecification.cs | 3 +- .../Specifications/FullSeasonSpecification.cs | 1 + .../Specifications/LanguageSpecification.cs | 3 +- .../Specifications/MinimumAgeSpecification.cs | 1 + .../Specifications/NotSampleSpecification.cs | 1 + .../Specifications/ProtocolSpecification.cs | 1 + .../QualityAllowedByProfileSpecification.cs | 1 + .../Specifications/QueueSpecification.cs | 1 + .../Specifications/RawDiskSpecification.cs | 1 + .../ReleaseRestrictionsSpecification.cs | 1 + .../Specifications/RetentionSpecification.cs | 1 + .../RssSync/DelaySpecification.cs | 1 + .../DeletedEpisodeFileSpecification.cs | 71 +++++++++ .../RssSync/HistorySpecification.cs | 3 +- .../RssSync/MonitoredEpisodeSpecification.cs | 1 + .../RssSync/ProperSpecification.cs | 1 + .../SameEpisodesGrabSpecification.cs | 1 + .../Search/DailyEpisodeMatchSpecification.cs | 3 +- .../Search/EpisodeRequestedSpecification.cs | 1 + .../Search/SeasonMatchSpecification.cs | 3 +- .../Search/SeriesSpecification.cs | 3 +- .../SingleEpisodeSearchMatchSpecification.cs | 3 +- .../Search/TorrentSeedingSpecification.cs | 3 +- .../UpgradeDiskSpecification.cs | 1 + src/NzbDrone.Core/NzbDrone.Core.csproj | 2 + 34 files changed, 302 insertions(+), 17 deletions(-) create mode 100644 src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DeletedEpisodeFileSpecificationFixture.cs create mode 100644 src/NzbDrone.Core/DecisionEngine/SpecificationPriority.cs create mode 100644 src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DeletedEpisodeFileSpecification.cs diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs index 0206abbd2..c9225540f 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs @@ -28,6 +28,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests private Mock _fail2; private Mock _fail3; + private Mock _failDelayed1; + [SetUp] public void Setup() { @@ -39,14 +41,19 @@ namespace NzbDrone.Core.Test.DecisionEngineTests _fail2 = new Mock(); _fail3 = new Mock(); + _failDelayed1 = new Mock(); + _pass1.Setup(c => c.IsSatisfiedBy(It.IsAny(), null)).Returns(Decision.Accept); _pass2.Setup(c => c.IsSatisfiedBy(It.IsAny(), null)).Returns(Decision.Accept); _pass3.Setup(c => c.IsSatisfiedBy(It.IsAny(), null)).Returns(Decision.Accept); - + _fail1.Setup(c => c.IsSatisfiedBy(It.IsAny(), null)).Returns(Decision.Reject("fail1")); _fail2.Setup(c => c.IsSatisfiedBy(It.IsAny(), null)).Returns(Decision.Reject("fail2")); _fail3.Setup(c => c.IsSatisfiedBy(It.IsAny(), null)).Returns(Decision.Reject("fail3")); + _failDelayed1.Setup(c => c.IsSatisfiedBy(It.IsAny(), null)).Returns(Decision.Reject("failDelayed1")); + _failDelayed1.SetupGet(c => c.Priority).Returns(SpecificationPriority.Disk); + _reports = new List { new ReleaseInfo { Title = "The.Office.S03E115.DVDRip.XviD-OSiTV" } }; _remoteEpisode = new RemoteEpisode { Series = new Series(), @@ -78,6 +85,25 @@ namespace NzbDrone.Core.Test.DecisionEngineTests _pass3.Verify(c => c.IsSatisfiedBy(_remoteEpisode, null), Times.Once()); } + [Test] + public void should_call_delayed_specifications_if_non_delayed_passed() + { + GivenSpecifications(_pass1, _failDelayed1); + + Subject.GetRssDecision(_reports).ToList(); + _failDelayed1.Verify(c => c.IsSatisfiedBy(_remoteEpisode, null), Times.Once()); + } + + [Test] + public void should_not_call_delayed_specifications_if_non_delayed_failed() + { + GivenSpecifications(_fail1, _failDelayed1); + + Subject.GetRssDecision(_reports).ToList(); + + _failDelayed1.Verify(c => c.IsSatisfiedBy(_remoteEpisode, null), Times.Never()); + } + [Test] public void should_return_rejected_if_single_specs_fail() { @@ -214,10 +240,10 @@ namespace NzbDrone.Core.Test.DecisionEngineTests var criteria = new SeasonSearchCriteria { Episodes = episodes.Take(1).ToList(), SeasonNumber = 1 }; - var reports = episodes.Select(v => - new ReleaseInfo() - { - Title = string.Format("{0}.S{1:00}E{2:00}.720p.WEB-DL-DRONE", series.Title, v.SceneSeasonNumber, v.SceneEpisodeNumber) + var reports = episodes.Select(v => + new ReleaseInfo() + { + Title = string.Format("{0}.S{1:00}E{2:00}.720p.WEB-DL-DRONE", series.Title, v.SceneSeasonNumber, v.SceneEpisodeNumber) }).ToList(); Mocker.GetMock() @@ -289,4 +315,4 @@ namespace NzbDrone.Core.Test.DecisionEngineTests ExceptionVerification.ExpectedErrors(1); } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DeletedEpisodeFileSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DeletedEpisodeFileSpecificationFixture.cs new file mode 100644 index 000000000..a1e3691a7 --- /dev/null +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DeletedEpisodeFileSpecificationFixture.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.DecisionEngine.Specifications.RssSync; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Profiles; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Tv; +using NzbDrone.Core.DecisionEngine; + +using NzbDrone.Core.Test.Framework; +using NzbDrone.Common.Disk; +using Moq; +using NzbDrone.Test.Common; +using System.IO; + +namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync +{ + [TestFixture] + public class DeletedEpisodeFileSpecificationFixture : CoreTest + { + private RemoteEpisode _parseResultMulti; + private RemoteEpisode _parseResultSingle; + private EpisodeFile _firstFile; + private EpisodeFile _secondFile; + + [SetUp] + public void Setup() + { + _firstFile = new EpisodeFile + { + Id = 1, + RelativePath = "My.Series.S01E01.mkv", + Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 1)), + DateAdded = DateTime.Now + }; + _secondFile = new EpisodeFile + { + Id = 2, + RelativePath = "My.Series.S01E02.mkv", + Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 1)), + DateAdded = DateTime.Now + }; + + var singleEpisodeList = new List { new Episode { EpisodeFile = _firstFile, EpisodeFileId = 1 } }; + var doubleEpisodeList = new List { + new Episode { EpisodeFile = _firstFile, EpisodeFileId = 1 }, + new Episode { EpisodeFile = _secondFile, EpisodeFileId = 2 } + }; + + var fakeSeries = Builder.CreateNew() + .With(c => c.Profile = new Profile { Cutoff = Quality.Bluray1080p }) + .With(c => c.Path = @"C:\Series\My.Series".AsOsAgnostic()) + .Build(); + + _parseResultMulti = new RemoteEpisode + { + Series = fakeSeries, + ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)) }, + Episodes = doubleEpisodeList + }; + + _parseResultSingle = new RemoteEpisode + { + Series = fakeSeries, + ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)) }, + Episodes = singleEpisodeList + }; + + GivenUnmonitorDeletedEpisodes(true); + } + + private void GivenUnmonitorDeletedEpisodes(bool enabled) + { + Mocker.GetMock() + .SetupGet(v => v.AutoUnmonitorPreviouslyDownloadedEpisodes) + .Returns(enabled); + } + + private void WithExistingFile(EpisodeFile episodeFile) + { + var path = Path.Combine(@"C:\Series\My.Series".AsOsAgnostic(), episodeFile.RelativePath); + + Mocker.GetMock() + .Setup(v => v.FileExists(path)) + .Returns(true); + } + + [Test] + public void should_return_true_when_unmonitor_deleted_episdes_is_off() + { + GivenUnmonitorDeletedEpisodes(false); + + Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeTrue(); + } + + [Test] + public void should_return_true_when_searching() + { + Subject.IsSatisfiedBy(_parseResultSingle, new SeasonSearchCriteria()).Accepted.Should().BeTrue(); + } + + [Test] + public void should_return_true_if_file_exists() + { + WithExistingFile(_firstFile); + + Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeTrue(); + } + + [Test] + public void should_return_false_if_file_is_missing() + { + Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse(); + } + + [Test] + public void should_return_true_if_both_of_multiple_episode_exist() + { + WithExistingFile(_firstFile); + WithExistingFile(_secondFile); + + Subject.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeTrue(); + } + + [Test] + public void should_return_false_if_one_of_multiple_episode_is_missing() + { + WithExistingFile(_firstFile); + + Subject.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeFalse(); + } + } +} diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 61dff5f73..c716524ec 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -159,6 +159,7 @@ + diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs index bb1a70873..343280208 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs @@ -122,8 +122,16 @@ namespace NzbDrone.Core.DecisionEngine private DownloadDecision GetDecisionForReport(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria = null) { - var reasons = _specifications.Select(c => EvaluateSpec(c, remoteEpisode, searchCriteria)) - .Where(c => c != null); + var reasons = new Rejection[0]; + + foreach (var specifications in _specifications.GroupBy(v => v.Priority).OrderBy(v => v.Key)) + { + reasons = specifications.Select(c => EvaluateSpec(c, remoteEpisode, searchCriteria)) + .Where(c => c != null) + .ToArray(); + + if (reasons.Any()) break; + } return new DownloadDecision(remoteEpisode, reasons.ToArray()); } diff --git a/src/NzbDrone.Core/DecisionEngine/IDecisionEngineSpecification.cs b/src/NzbDrone.Core/DecisionEngine/IDecisionEngineSpecification.cs index 199984734..08ea4c012 100644 --- a/src/NzbDrone.Core/DecisionEngine/IDecisionEngineSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/IDecisionEngineSpecification.cs @@ -7,6 +7,8 @@ namespace NzbDrone.Core.DecisionEngine { RejectionType Type { get; } + SpecificationPriority Priority { get; } + Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria); } } diff --git a/src/NzbDrone.Core/DecisionEngine/SpecificationPriority.cs b/src/NzbDrone.Core/DecisionEngine/SpecificationPriority.cs new file mode 100644 index 000000000..e3eb0b9d7 --- /dev/null +++ b/src/NzbDrone.Core/DecisionEngine/SpecificationPriority.cs @@ -0,0 +1,10 @@ +namespace NzbDrone.Core.DecisionEngine +{ + public enum SpecificationPriority + { + Default = 0, + Parsing = 0, + Database = 0, + Disk = 1 + } +} diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs index 4ab566d2e..d8af6bbce 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs @@ -22,6 +22,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/AnimeVersionUpgradeSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/AnimeVersionUpgradeSpecification.cs index c2f93f7c0..3adb65df0 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/AnimeVersionUpgradeSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/AnimeVersionUpgradeSpecification.cs @@ -18,6 +18,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs index 18b216263..1b2c3d05e 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs @@ -16,10 +16,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Database; public RejectionType Type => RejectionType.Permanent; public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) - { + { if (_blacklistService.Blacklisted(subject.Series.Id, subject.Release)) { _logger.Debug("{0} is blacklisted, rejecting.", subject.Release.Title); diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs index c5d52a48a..39102b0fc 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs @@ -16,6 +16,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) @@ -29,7 +30,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications } _logger.Debug("Comparing file quality with report. Existing file is {0}", file.Quality); - + if (!_qualityUpgradableSpecification.CutoffNotMet(subject.Series.Profile, file.Quality, subject.ParsedEpisodeInfo.Quality)) { _logger.Debug("Cutoff already met, rejecting."); diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/FullSeasonSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/FullSeasonSpecification.cs index 023b6be60..bea83b303 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/FullSeasonSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/FullSeasonSpecification.cs @@ -19,6 +19,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _episodeService = episodeService; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs index 9f7f75038..edef58757 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs @@ -13,12 +13,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { var wantedLanguage = subject.Series.Profile.Value.Language; - + _logger.Debug("Checking if report meets language requirements. {0}", subject.ParsedEpisodeInfo.Language); if (subject.ParsedEpisodeInfo.Language != wantedLanguage) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/MinimumAgeSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/MinimumAgeSpecification.cs index 449d7be76..57f8a2487 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/MinimumAgeSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/MinimumAgeSpecification.cs @@ -16,6 +16,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Temporary; public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs index 02ff7653a..091efb948 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs @@ -8,6 +8,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications { private readonly Logger _logger; + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public NotSampleSpecification(Logger logger) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/ProtocolSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/ProtocolSpecification.cs index 008e58812..ba956ecbd 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/ProtocolSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/ProtocolSpecification.cs @@ -18,6 +18,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/QualityAllowedByProfileSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/QualityAllowedByProfileSpecification.cs index 7913e0e7e..d8dcd9afb 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/QualityAllowedByProfileSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/QualityAllowedByProfileSpecification.cs @@ -13,6 +13,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs index 6f3ec1bea..838d8a80d 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs @@ -21,6 +21,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RawDiskSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RawDiskSpecification.cs index 7f278cb7e..77b9c006f 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RawDiskSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RawDiskSpecification.cs @@ -19,6 +19,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/ReleaseRestrictionsSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/ReleaseRestrictionsSpecification.cs index 9fb8c13f5..39a286af9 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/ReleaseRestrictionsSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/ReleaseRestrictionsSpecification.cs @@ -20,6 +20,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RetentionSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RetentionSpecification.cs index 97802f871..99cf93f67 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RetentionSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RetentionSpecification.cs @@ -16,6 +16,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs index 68551c66c..da6285942 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs @@ -26,6 +26,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Database; public RejectionType Type => RejectionType.Temporary; public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DeletedEpisodeFileSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DeletedEpisodeFileSpecification.cs new file mode 100644 index 000000000..bf4ab6ae5 --- /dev/null +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DeletedEpisodeFileSpecification.cs @@ -0,0 +1,71 @@ +using System.IO; +using System.Linq; +using NLog; +using NzbDrone.Common.Disk; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync +{ + public class DeletedEpisodeFileSpecification : IDecisionEngineSpecification + { + private readonly IDiskProvider _diskProvider; + private readonly IConfigService _configService; + private readonly Logger _logger; + + public DeletedEpisodeFileSpecification(IDiskProvider diskProvider, IConfigService configService, Logger logger) + { + _diskProvider = diskProvider; + _configService = configService; + _logger = logger; + } + + public SpecificationPriority Priority => SpecificationPriority.Disk; + public RejectionType Type => RejectionType.Temporary; + + public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) + { + if (!_configService.AutoUnmonitorPreviouslyDownloadedEpisodes) + { + return Decision.Accept(); + } + + if (searchCriteria != null) + { + _logger.Debug("Skipping deleted episodefile check during search"); + return Decision.Accept(); + } + + var missingEpisodeFiles = subject.Episodes + .Where(v => v.EpisodeFileId != 0) + .Select(v => v.EpisodeFile.Value) + .DistinctBy(v => v.Id) + .Where(v => IsEpisodeFileMissing(subject.Series, v)) + .ToArray(); + + if (missingEpisodeFiles.Any()) + { + foreach (var missingEpisodeFile in missingEpisodeFiles) + { + _logger.Trace("Episode file {0} is missing from disk.", missingEpisodeFile.RelativePath); + } + + _logger.Debug("Files for this episode exist in the database but not on disk, will be unmonitored on next diskscan. skipping."); + return Decision.Reject("Series is not monitored"); + } + + return Decision.Accept(); + } + + private bool IsEpisodeFileMissing(Series series, EpisodeFile episodeFile) + { + var fullPath = Path.Combine(series.Path, episodeFile.RelativePath); + + return !_diskProvider.FileExists(fullPath); + } + } +} diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs index 9aa4fabf1..8ce71848c 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs @@ -26,6 +26,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Database; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) @@ -59,7 +60,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync { if (recent) { - return Decision.Reject("Recent grab event in history already meets cutoff: {0}", mostRecent.Quality); + return Decision.Reject("Recent grab event in history already meets cutoff: {0}", mostRecent.Quality); } return Decision.Reject("CDH is disabled and grab event in history already meets cutoff: {0}", mostRecent.Quality); diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/MonitoredEpisodeSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/MonitoredEpisodeSpecification.cs index f56f26478..ca0cb5d32 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/MonitoredEpisodeSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/MonitoredEpisodeSpecification.cs @@ -14,6 +14,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs index 0c6632d25..f3f5d4e8d 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs @@ -20,6 +20,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/SameEpisodesGrabSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/SameEpisodesGrabSpecification.cs index 1a8c5db5b..0ef769d01 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/SameEpisodesGrabSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/SameEpisodesGrabSpecification.cs @@ -15,6 +15,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyEpisodeMatchSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyEpisodeMatchSpecification.cs index 50fd9b3cc..49136428f 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyEpisodeMatchSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyEpisodeMatchSpecification.cs @@ -16,6 +16,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search _episodeService = episodeService; } + public SpecificationPriority Priority => SpecificationPriority.Database; public RejectionType Type => RejectionType.Permanent; public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria) @@ -40,4 +41,4 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search return Decision.Accept(); } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/EpisodeRequestedSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/EpisodeRequestedSpecification.cs index 60640442f..e4bd7b2c6 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/EpisodeRequestedSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/EpisodeRequestedSpecification.cs @@ -15,6 +15,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeasonMatchSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeasonMatchSpecification.cs index b09d888ec..8516b08e7 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeasonMatchSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeasonMatchSpecification.cs @@ -13,6 +13,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria) @@ -34,4 +35,4 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search return Decision.Accept(); } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeriesSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeriesSpecification.cs index 7f1201b33..07afbaada 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeriesSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeriesSpecification.cs @@ -13,6 +13,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria) @@ -33,4 +34,4 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search return Decision.Accept(); } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SingleEpisodeSearchMatchSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SingleEpisodeSearchMatchSpecification.cs index fb056734f..4d4f76d05 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SingleEpisodeSearchMatchSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SingleEpisodeSearchMatchSpecification.cs @@ -14,6 +14,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria) @@ -47,4 +48,4 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search return Decision.Accept(); } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/TorrentSeedingSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/TorrentSeedingSpecification.cs index 87c244b53..65230ef12 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/TorrentSeedingSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/TorrentSeedingSpecification.cs @@ -13,6 +13,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; @@ -34,4 +35,4 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search return Decision.Accept(); } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs index 427a4cd4f..8554c0c40 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs @@ -16,6 +16,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 9e238632b..735d1a73f 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -315,6 +315,7 @@ + @@ -330,6 +331,7 @@ + From de7f68570ea4d915d99b773300869eb9bc4a2828 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Wed, 12 Apr 2017 18:08:31 +0200 Subject: [PATCH 020/156] Fixed: Regression causing nzbToMedia imports to be copied instead of moved. --- .../NzbgetTests/NzbgetFixture.cs | 16 ++++++++++++++++ .../Download/Clients/Nzbget/Nzbget.cs | 2 ++ 2 files changed, 18 insertions(+) diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetTests/NzbgetFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetTests/NzbgetFixture.cs index a79236740..35903402c 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetTests/NzbgetFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetTests/NzbgetFixture.cs @@ -195,6 +195,22 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests VerifyDownloading(result); } + [Test] + public void post_processing_item_should_have_required_properties() + { + _queued.ActiveDownloads = 1; + + GivenQueue(_queued); + GivenHistory(null); + + _queued.RemainingSizeLo = 0; + + var result = Subject.GetItems().Single(); + + result.CanBeRemoved.Should().BeTrue(); + result.CanMoveFiles.Should().BeTrue(); + } + [Test] public void completed_download_should_have_required_properties() { diff --git a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs index 197f59c23..ed9cd5cd6 100644 --- a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs +++ b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs @@ -83,6 +83,8 @@ namespace NzbDrone.Core.Download.Clients.Nzbget queueItem.TotalSize = totalSize; queueItem.Category = item.Category; queueItem.DownloadClient = Definition.Name; + queueItem.CanMoveFiles = true; + queueItem.CanBeRemoved = true; if (globalStatus.DownloadPaused || remainingSize == pausedSize && remainingSize != 0) { From f8b8fcfb8d634f65bb3b552d36bda20c4057ab5a Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Wed, 12 Apr 2017 19:53:21 +0200 Subject: [PATCH 021/156] Fixed: Handling of priority setting when queueing is disabled in qBittorrent. fixes #1841 --- .../QBittorrentTests/QBittorrentFixture.cs | 39 +++++++++++ .../Clients/QBittorrent/QBittorrent.cs | 64 +++++++++++++++++-- .../QBittorrent/QBittorrentPreferences.cs | 3 + src/UI/Form/CheckboxTemplate.hbs | 2 +- src/UI/Form/SelectTemplate.hbs | 2 +- 5 files changed, 101 insertions(+), 9 deletions(-) diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs index 3e9ca837d..d7146acbe 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs @@ -89,6 +89,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests }); } + protected void GivenHighPriority() + { + Subject.Definition.Settings.As().OlderTvPriority = (int)QBittorrentPriority.First; + Subject.Definition.Settings.As().RecentTvPriority = (int)QBittorrentPriority.First; + } + protected void GivenMaxRatio(float maxRatio, bool removeOnMaxRatio = true) { Mocker.GetMock() @@ -265,6 +271,39 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests id.Should().Be(expectedHash); } + [Test] + public void Download_should_set_top_priority() + { + GivenHighPriority(); + GivenSuccessfulDownload(); + + var remoteEpisode = CreateRemoteEpisode(); + + var id = Subject.Download(remoteEpisode); + + Mocker.GetMock() + .Verify(v => v.MoveTorrentToTopInQueue(It.IsAny(), It.IsAny()), Times.Once()); + } + + [Test] + public void Download_should_not_fail_if_top_priority_not_available() + { + GivenHighPriority(); + GivenSuccessfulDownload(); + + Mocker.GetMock() + .Setup(v => v.MoveTorrentToTopInQueue(It.IsAny(), It.IsAny())) + .Throws(new HttpException(new HttpResponse(new HttpRequest("http://me.local/"), new HttpHeader(), new byte[0], System.Net.HttpStatusCode.Forbidden))); + + var remoteEpisode = CreateRemoteEpisode(); + + var id = Subject.Download(remoteEpisode); + + id.Should().NotBeNullOrEmpty(); + + ExceptionVerification.ExpectedWarns(1); + } + [Test] public void should_return_status_with_outputdirs() { diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs index 107a85d56..148c8292a 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs @@ -55,17 +55,31 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent { _proxy.AddTorrentFromFile(filename, fileContent, Settings); - if (Settings.TvCategory.IsNotNullOrWhiteSpace()) + try { - _proxy.SetTorrentLabel(hash.ToLower(), Settings.TvCategory, Settings); + if (Settings.TvCategory.IsNotNullOrWhiteSpace()) + { + _proxy.SetTorrentLabel(hash.ToLower(), Settings.TvCategory, Settings); + } + } + catch (Exception ex) + { + _logger.Warn(ex, "Failed to set the torrent label for {0}.", filename); } - var isRecentEpisode = remoteEpisode.IsRecentEpisode(); - - if (isRecentEpisode && Settings.RecentTvPriority == (int)QBittorrentPriority.First || - !isRecentEpisode && Settings.OlderTvPriority == (int)QBittorrentPriority.First) + try { - _proxy.MoveTorrentToTopInQueue(hash.ToLower(), Settings); + var isRecentEpisode = remoteEpisode.IsRecentEpisode(); + + if (isRecentEpisode && Settings.RecentTvPriority == (int)QBittorrentPriority.First || + !isRecentEpisode && Settings.OlderTvPriority == (int)QBittorrentPriority.First) + { + _proxy.MoveTorrentToTopInQueue(hash.ToLower(), Settings); + } + } + catch (Exception ex) + { + _logger.Warn(ex, "Failed to set the torrent priority for {0}.", filename); } return hash; @@ -177,6 +191,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent { failures.AddIfNotNull(TestConnection()); if (failures.Any()) return; + failures.AddIfNotNull(TestPrioritySupport()); failures.AddIfNotNull(TestGetTorrents()); } @@ -253,6 +268,41 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent return null; } + private ValidationFailure TestPrioritySupport() + { + var recentPriorityDefault = Settings.RecentTvPriority == (int)QBittorrentPriority.Last; + var olderPriorityDefault = Settings.OlderTvPriority == (int)QBittorrentPriority.Last; + + if (olderPriorityDefault && recentPriorityDefault) + { + return null; + } + + try + { + var config = _proxy.GetConfig(Settings); + + if (!config.QueueingEnabled) + { + if (!recentPriorityDefault) + { + return new NzbDroneValidationFailure(nameof(Settings.RecentTvPriority), "Queueing not enabled") { DetailedDescription = "Torrent Queueing is not enabled in your qBittorrent settings. Enable it in qBittorrent or select 'Last' as priority." }; + } + else if (!olderPriorityDefault) + { + return new NzbDroneValidationFailure(nameof(Settings.OlderTvPriority), "Queueing not enabled") { DetailedDescription = "Torrent Queueing is not enabled in your qBittorrent settings. Enable it in qBittorrent or select 'Last' as priority." }; + } + } + } + catch (Exception ex) + { + _logger.Error(ex); + return new NzbDroneValidationFailure(String.Empty, "Unknown exception: " + ex.Message); + } + + return null; + } + private ValidationFailure TestGetTorrents() { try diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentPreferences.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentPreferences.cs index 9fddb1116..3278cbaab 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentPreferences.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentPreferences.cs @@ -16,5 +16,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent [JsonProperty(PropertyName = "max_ratio_act")] public bool RemoveOnMaxRatio { get; set; } // Action performed when a torrent reaches the maximum share ratio. [false = pause, true = remove] + + [JsonProperty(PropertyName = "queueing_enabled")] + public bool QueueingEnabled { get; set; } = true; } } diff --git a/src/UI/Form/CheckboxTemplate.hbs b/src/UI/Form/CheckboxTemplate.hbs index d3803ab70..526804714 100644 --- a/src/UI/Form/CheckboxTemplate.hbs +++ b/src/UI/Form/CheckboxTemplate.hbs @@ -4,7 +4,7 @@