From 1eb0d3b11aaf931e2406f789f9fa70b9abc618bd Mon Sep 17 00:00:00 2001 From: Qstick Date: Fri, 24 Dec 2021 19:55:32 -0600 Subject: [PATCH] Track fully imported downloads in separate history table --- .../History/HistoryController.cs | 10 +- src/Lidarr.Api.V1/History/HistoryResource.cs | 4 +- .../Datastore/DatabaseRelationshipFixture.cs | 9 +- .../AlreadyImportedSpecificationFixture.cs | 30 +-- .../HistorySpecificationFixture.cs | 40 +-- .../ImportFixture.cs | 39 ++- .../ProcessFixture.cs | 6 +- .../ProcessFailedFixture.cs | 6 +- .../ProcessFixture.cs | 10 +- .../TrackedDownloadAlreadyImportedFixture.cs | 30 ++- .../TrackedDownloadServiceFixture.cs | 4 +- .../Checks/RemotePathMappingCheckFixture.cs | 4 + .../HistoryTests/HistoryRepositoryFixture.cs | 12 +- .../HistoryTests/HistoryServiceFixture.cs | 4 +- .../CleanupOrphanedHistoryItemsFixture.cs | 13 +- .../MusicTests/RefreshAlbumServiceFixture.cs | 4 +- .../MusicTests/RefreshArtistServiceFixture.cs | 4 +- .../QueueTests/QueueServiceFixture.cs | 6 +- .../Analytics/AnalyticsService.cs | 2 +- .../Migration/052_download_history.cs | 104 ++++++++ src/NzbDrone.Core/Datastore/TableMapping.cs | 5 +- .../AlreadyImportedSpecification.cs | 4 +- .../RssSync/HistorySpecification.cs | 2 +- .../Download/AlbumGrabbedEvent.cs | 4 +- .../Download/Clients/Flood/Flood.cs | 26 ++ .../Download/Clients/rTorrent/RTorrent.cs | 26 +- .../Clients/rTorrent/RTorrentProxy.cs | 23 +- .../Clients/rTorrent/RTorrentTorrent.cs | 1 + .../Download/CompletedDownloadService.cs | 149 ++++++++--- .../Download/DownloadClientBase.cs | 2 - .../Download/DownloadClientItem.cs | 2 +- .../Download/DownloadSeedConfigProvider.cs | 90 +++++++ src/NzbDrone.Core/Download/DownloadService.cs | 2 + .../Download/FailedDownloadService.cs | 21 +- .../Download/History/DownloadHistory.cs | 36 +++ .../History/DownloadHistoryRepository.cs | 31 +++ .../History/DownloadHistoryService.cs | 248 ++++++++++++++++++ ...Service.cs => ProvideImportItemService.cs} | 0 .../TrackedDownloadAlreadyImported.cs | 24 +- .../TrackedDownloadService.cs | 86 +++--- .../Checks/RemotePathMappingCheck.cs | 2 +- .../History/{History.cs => EntityHistory.cs} | 8 +- .../History/EntityHistoryRepository.cs | 130 +++++++++ ...toryService.cs => EntityHistoryService.cs} | 91 +++---- .../History/HistoryRepository.cs | 130 --------- .../Indexers/SeedConfigProvider.cs | 66 +++-- .../DownloadedAlbumsCommandService.cs | 16 +- .../MediaFiles/Events/AlbumImportedEvent.cs | 4 +- .../Events/TrackImportFailedEvent.cs | 4 +- .../MediaFiles/Events/TrackImportedEvent.cs | 4 +- .../AlreadyImportedSpecification.cs | 6 +- .../Notifications/NotificationService.cs | 2 +- src/NzbDrone.Core/Queue/QueueService.cs | 2 +- 53 files changed, 1145 insertions(+), 443 deletions(-) create mode 100644 src/NzbDrone.Core/Datastore/Migration/052_download_history.cs create mode 100644 src/NzbDrone.Core/Download/DownloadSeedConfigProvider.cs create mode 100644 src/NzbDrone.Core/Download/History/DownloadHistory.cs create mode 100644 src/NzbDrone.Core/Download/History/DownloadHistoryRepository.cs create mode 100644 src/NzbDrone.Core/Download/History/DownloadHistoryService.cs rename src/NzbDrone.Core/Download/{PrepareImportService.cs => ProvideImportItemService.cs} (100%) rename src/NzbDrone.Core/History/{History.cs => EntityHistory.cs} (86%) create mode 100644 src/NzbDrone.Core/History/EntityHistoryRepository.cs rename src/NzbDrone.Core/History/{HistoryService.cs => EntityHistoryService.cs} (80%) delete mode 100644 src/NzbDrone.Core/History/HistoryRepository.cs diff --git a/src/Lidarr.Api.V1/History/HistoryController.cs b/src/Lidarr.Api.V1/History/HistoryController.cs index f1c5a11f9..75194f662 100644 --- a/src/Lidarr.Api.V1/History/HistoryController.cs +++ b/src/Lidarr.Api.V1/History/HistoryController.cs @@ -30,7 +30,7 @@ namespace Lidarr.Api.V1.History _failedDownloadService = failedDownloadService; } - protected HistoryResource MapToResource(NzbDrone.Core.History.History model, bool includeArtist, bool includeAlbum, bool includeTrack) + protected HistoryResource MapToResource(EntityHistory model, bool includeArtist, bool includeAlbum, bool includeTrack) { var resource = model.ToResource(); @@ -61,7 +61,7 @@ namespace Lidarr.Api.V1.History public PagingResource GetHistory(bool includeArtist = false, bool includeAlbum = false, bool includeTrack = false) { var pagingResource = Request.ReadPagingResourceFromRequest(); - var pagingSpec = pagingResource.MapToPagingSpec("date", SortDirection.Descending); + var pagingSpec = pagingResource.MapToPagingSpec("date", SortDirection.Descending); var eventTypeFilter = pagingResource.Filters.FirstOrDefault(f => f.Key == "eventType"); var albumIdFilter = pagingResource.Filters.FirstOrDefault(f => f.Key == "albumId"); @@ -69,7 +69,7 @@ namespace Lidarr.Api.V1.History if (eventTypeFilter != null) { - var filterValue = (HistoryEventType)Convert.ToInt32(eventTypeFilter.Value); + var filterValue = (EntityHistoryEventType)Convert.ToInt32(eventTypeFilter.Value); pagingSpec.FilterExpressions.Add(v => v.EventType == filterValue); } @@ -89,13 +89,13 @@ namespace Lidarr.Api.V1.History } [HttpGet("since")] - public List GetHistorySince(DateTime date, HistoryEventType? eventType = null, bool includeArtist = false, bool includeAlbum = false, bool includeTrack = false) + public List GetHistorySince(DateTime date, EntityHistoryEventType? eventType = null, bool includeArtist = false, bool includeAlbum = false, bool includeTrack = false) { return _historyService.Since(date, eventType).Select(h => MapToResource(h, includeArtist, includeAlbum, includeTrack)).ToList(); } [HttpGet("artist")] - public List GetArtistHistory(int artistId, int? albumId = null, HistoryEventType? eventType = null, bool includeArtist = false, bool includeAlbum = false, bool includeTrack = false) + public List GetArtistHistory(int artistId, int? albumId = null, EntityHistoryEventType? eventType = null, bool includeArtist = false, bool includeAlbum = false, bool includeTrack = false) { if (albumId.HasValue) { diff --git a/src/Lidarr.Api.V1/History/HistoryResource.cs b/src/Lidarr.Api.V1/History/HistoryResource.cs index 6f450ebdb..eca074ae3 100644 --- a/src/Lidarr.Api.V1/History/HistoryResource.cs +++ b/src/Lidarr.Api.V1/History/HistoryResource.cs @@ -20,7 +20,7 @@ namespace Lidarr.Api.V1.History public DateTime Date { get; set; } public string DownloadId { get; set; } - public HistoryEventType EventType { get; set; } + public EntityHistoryEventType EventType { get; set; } public Dictionary Data { get; set; } @@ -31,7 +31,7 @@ namespace Lidarr.Api.V1.History public static class HistoryResourceMapper { - public static HistoryResource ToResource(this NzbDrone.Core.History.History model) + public static HistoryResource ToResource(this EntityHistory model) { if (model == null) { diff --git a/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs b/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs index 40bc89410..ab17a1550 100644 --- a/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs @@ -3,6 +3,7 @@ using System.Linq; using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; +using NzbDrone.Core.History; using NzbDrone.Core.Music; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; @@ -58,21 +59,21 @@ namespace NzbDrone.Core.Test.Datastore { var quality = new QualityModel { Quality = Quality.MP3_320, Revision = new Revision(version: 2) }; - var history = Builder.CreateNew() + var history = Builder.CreateNew() .With(c => c.Id = 0) .With(c => c.Quality = quality) .Build(); Db.Insert(history); - var loadedQuality = Db.Single().Quality; + var loadedQuality = Db.Single().Quality; loadedQuality.Should().Be(quality); } [Test] public void embedded_list_of_document_with_json() { - var history = Builder.CreateListOfSize(2) + var history = Builder.CreateListOfSize(2) .All().With(c => c.Id = 0) .Build().ToList(); @@ -81,7 +82,7 @@ namespace NzbDrone.Core.Test.Datastore Db.InsertMany(history); - var returnedHistory = Db.All(); + var returnedHistory = Db.All(); returnedHistory[0].Quality.Quality.Should().Be(Quality.MP3_320); } diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/AlreadyImportedSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/AlreadyImportedSpecificationFixture.cs index 4d4295433..9f253b70d 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/AlreadyImportedSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/AlreadyImportedSpecificationFixture.cs @@ -26,7 +26,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests private QualityModel _mp3; private QualityModel _flac; private RemoteAlbum _remoteAlbum; - private List _history; + private List _history; private TrackFile _firstFile; [SetUp] @@ -58,7 +58,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests .Build() }; - _history = new List(); + _history = new List(); Mocker.GetMock() .SetupGet(s => s.EnableCompletedDownloadHandling) @@ -80,9 +80,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests .Returns(false); } - private void GivenHistoryItem(string downloadId, string sourceTitle, QualityModel quality, HistoryEventType eventType) + private void GivenHistoryItem(string downloadId, string sourceTitle, QualityModel quality, EntityHistoryEventType eventType) { - _history.Add(new History.History + _history.Add(new EntityHistory { DownloadId = downloadId, SourceTitle = sourceTitle, @@ -119,7 +119,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_be_accepted_if_album_does_not_have_imported_event() { - GivenHistoryItem(Guid.NewGuid().ToString().ToUpper(), TITLE, _mp3, HistoryEventType.Grabbed); + GivenHistoryItem(Guid.NewGuid().ToString().ToUpper(), TITLE, _mp3, EntityHistoryEventType.Grabbed); Subject.IsSatisfiedBy(_remoteAlbum, null).Accepted.Should().BeTrue(); } @@ -129,8 +129,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests { var downloadId = Guid.NewGuid().ToString().ToUpper(); - GivenHistoryItem(downloadId, TITLE, _mp3, HistoryEventType.Grabbed); - GivenHistoryItem(downloadId, TITLE, _mp3, HistoryEventType.DownloadImported); + GivenHistoryItem(downloadId, TITLE, _mp3, EntityHistoryEventType.Grabbed); + GivenHistoryItem(downloadId, TITLE, _mp3, EntityHistoryEventType.DownloadImported); Subject.IsSatisfiedBy(_remoteAlbum, null).Accepted.Should().BeTrue(); } @@ -140,8 +140,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests { var downloadId = Guid.NewGuid().ToString().ToUpper(); - GivenHistoryItem(downloadId, TITLE, _mp3, HistoryEventType.Grabbed); - GivenHistoryItem(downloadId, TITLE, _flac, HistoryEventType.DownloadImported); + GivenHistoryItem(downloadId, TITLE, _mp3, EntityHistoryEventType.Grabbed); + GivenHistoryItem(downloadId, TITLE, _flac, EntityHistoryEventType.DownloadImported); _remoteAlbum.Release = Builder.CreateNew() .With(t => t.DownloadProtocol = DownloadProtocol.Torrent) @@ -156,8 +156,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests { var downloadId = Guid.NewGuid().ToString().ToUpper(); - GivenHistoryItem(downloadId, TITLE, _mp3, HistoryEventType.Grabbed); - GivenHistoryItem(downloadId, TITLE, _flac, HistoryEventType.DownloadImported); + GivenHistoryItem(downloadId, TITLE, _mp3, EntityHistoryEventType.Grabbed); + GivenHistoryItem(downloadId, TITLE, _flac, EntityHistoryEventType.DownloadImported); _remoteAlbum.Release = Builder.CreateNew() .With(t => t.DownloadProtocol = DownloadProtocol.Torrent) @@ -170,8 +170,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_be_accepted_if_release_torrent_hash_is_null_and_downloadId_is_null() { - GivenHistoryItem(null, TITLE, _mp3, HistoryEventType.Grabbed); - GivenHistoryItem(null, TITLE, _flac, HistoryEventType.DownloadImported); + GivenHistoryItem(null, TITLE, _mp3, EntityHistoryEventType.Grabbed); + GivenHistoryItem(null, TITLE, _flac, EntityHistoryEventType.DownloadImported); _remoteAlbum.Release = Builder.CreateNew() .With(t => t.DownloadProtocol = DownloadProtocol.Torrent) @@ -186,8 +186,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests { var downloadId = Guid.NewGuid().ToString().ToUpper(); - GivenHistoryItem(downloadId, TITLE, _mp3, HistoryEventType.Grabbed); - GivenHistoryItem(downloadId, TITLE, _flac, HistoryEventType.DownloadImported); + GivenHistoryItem(downloadId, TITLE, _mp3, EntityHistoryEventType.Grabbed); + GivenHistoryItem(downloadId, TITLE, _flac, EntityHistoryEventType.DownloadImported); _remoteAlbum.Release = Builder.CreateNew() .With(t => t.DownloadProtocol = DownloadProtocol.Torrent) diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/HistorySpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/HistorySpecificationFixture.cs index b9bfe8b51..42c9dc1a5 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/HistorySpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/HistorySpecificationFixture.cs @@ -76,10 +76,10 @@ namespace NzbDrone.Core.Test.DecisionEngineTests .Returns(true); } - private void GivenMostRecentForAlbum(int albumId, string downloadId, QualityModel quality, DateTime date, HistoryEventType eventType) + private void GivenMostRecentForAlbum(int albumId, string downloadId, QualityModel quality, DateTime date, EntityHistoryEventType eventType) { Mocker.GetMock().Setup(s => s.MostRecentForAlbum(albumId)) - .Returns(new History.History { DownloadId = downloadId, Quality = quality, Date = date, EventType = eventType }); + .Returns(new EntityHistory { DownloadId = downloadId, Quality = quality, Date = date, EventType = eventType }); } private void GivenCdhDisabled() @@ -98,14 +98,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_return_true_if_latest_history_item_is_null() { - Mocker.GetMock().Setup(s => s.MostRecentForAlbum(It.IsAny())).Returns((History.History)null); + Mocker.GetMock().Setup(s => s.MostRecentForAlbum(It.IsAny())).Returns((EntityHistory)null); _upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeTrue(); } [Test] public void should_return_true_if_latest_history_item_is_not_grabbed() { - GivenMostRecentForAlbum(FIRST_ALBUM_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow, HistoryEventType.DownloadFailed); + GivenMostRecentForAlbum(FIRST_ALBUM_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow, EntityHistoryEventType.DownloadFailed); _upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeTrue(); } @@ -118,46 +118,46 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_return_true_if_latest_history_item_is_older_than_twelve_hours() { - GivenMostRecentForAlbum(FIRST_ALBUM_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow.AddHours(-12).AddMilliseconds(-1), HistoryEventType.Grabbed); + GivenMostRecentForAlbum(FIRST_ALBUM_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow.AddHours(-12).AddMilliseconds(-1), EntityHistoryEventType.Grabbed); _upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeTrue(); } [Test] public void should_be_upgradable_if_only_album_is_upgradable() { - GivenMostRecentForAlbum(FIRST_ALBUM_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed); + GivenMostRecentForAlbum(FIRST_ALBUM_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, EntityHistoryEventType.Grabbed); _upgradeHistory.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeTrue(); } [Test] public void should_be_upgradable_if_both_albums_are_upgradable() { - GivenMostRecentForAlbum(FIRST_ALBUM_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed); - GivenMostRecentForAlbum(SECOND_ALBUM_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed); + GivenMostRecentForAlbum(FIRST_ALBUM_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, EntityHistoryEventType.Grabbed); + GivenMostRecentForAlbum(SECOND_ALBUM_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, EntityHistoryEventType.Grabbed); _upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeTrue(); } [Test] public void should_not_be_upgradable_if_both_albums_are_not_upgradable() { - GivenMostRecentForAlbum(FIRST_ALBUM_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed); - GivenMostRecentForAlbum(SECOND_ALBUM_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed); + GivenMostRecentForAlbum(FIRST_ALBUM_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow, EntityHistoryEventType.Grabbed); + GivenMostRecentForAlbum(SECOND_ALBUM_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow, EntityHistoryEventType.Grabbed); _upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeFalse(); } [Test] public void should_be_not_upgradable_if_only_first_albums_is_upgradable() { - GivenMostRecentForAlbum(FIRST_ALBUM_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed); - GivenMostRecentForAlbum(FIRST_ALBUM_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed); + GivenMostRecentForAlbum(FIRST_ALBUM_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, EntityHistoryEventType.Grabbed); + GivenMostRecentForAlbum(FIRST_ALBUM_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow, EntityHistoryEventType.Grabbed); _upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeFalse(); } [Test] public void should_be_not_upgradable_if_only_second_albums_is_upgradable() { - GivenMostRecentForAlbum(FIRST_ALBUM_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed); - GivenMostRecentForAlbum(SECOND_ALBUM_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed); + GivenMostRecentForAlbum(FIRST_ALBUM_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow, EntityHistoryEventType.Grabbed); + GivenMostRecentForAlbum(SECOND_ALBUM_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, EntityHistoryEventType.Grabbed); _upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeFalse(); } @@ -168,7 +168,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests _parseResultSingle.ParsedAlbumInfo.Quality = new QualityModel(Quality.MP3_320, new Revision(version: 1)); _upgradableQuality = new QualityModel(Quality.MP3_320, new Revision(version: 1)); - GivenMostRecentForAlbum(FIRST_ALBUM_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed); + GivenMostRecentForAlbum(FIRST_ALBUM_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, EntityHistoryEventType.Grabbed); _upgradeHistory.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse(); } @@ -180,7 +180,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests _parseResultSingle.ParsedAlbumInfo.Quality = new QualityModel(Quality.MP3_320, new Revision(version: 1)); _upgradableQuality = new QualityModel(Quality.MP3_320, new Revision(version: 1)); - GivenMostRecentForAlbum(FIRST_ALBUM_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed); + GivenMostRecentForAlbum(FIRST_ALBUM_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, EntityHistoryEventType.Grabbed); _upgradeHistory.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse(); } @@ -188,7 +188,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_return_false_if_latest_history_item_is_only_one_hour_old() { - GivenMostRecentForAlbum(FIRST_ALBUM_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow.AddHours(-1), HistoryEventType.Grabbed); + GivenMostRecentForAlbum(FIRST_ALBUM_ID, string.Empty, _notupgradableQuality, DateTime.UtcNow.AddHours(-1), EntityHistoryEventType.Grabbed); _upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeFalse(); } @@ -196,7 +196,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void should_return_false_if_latest_history_has_a_download_id_and_cdh_is_disabled() { GivenCdhDisabled(); - GivenMostRecentForAlbum(FIRST_ALBUM_ID, "test", _upgradableQuality, DateTime.UtcNow.AddDays(-100), HistoryEventType.Grabbed); + GivenMostRecentForAlbum(FIRST_ALBUM_ID, "test", _upgradableQuality, DateTime.UtcNow.AddDays(-100), EntityHistoryEventType.Grabbed); _upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeTrue(); } @@ -208,7 +208,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests _parseResultSingle.ParsedAlbumInfo.Quality = new QualityModel(Quality.MP3_320, new Revision(version: 1)); _upgradableQuality = new QualityModel(Quality.MP3_320, new Revision(version: 1)); - GivenMostRecentForAlbum(FIRST_ALBUM_ID, "test", _upgradableQuality, DateTime.UtcNow.AddDays(-100), HistoryEventType.Grabbed); + GivenMostRecentForAlbum(FIRST_ALBUM_ID, "test", _upgradableQuality, DateTime.UtcNow.AddDays(-100), EntityHistoryEventType.Grabbed); _upgradeHistory.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse(); } @@ -217,7 +217,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void should_return_false_if_only_album_is_not_upgradable_and_cdh_is_disabled() { GivenCdhDisabled(); - GivenMostRecentForAlbum(FIRST_ALBUM_ID, "test", _notupgradableQuality, DateTime.UtcNow.AddDays(-100), HistoryEventType.Grabbed); + GivenMostRecentForAlbum(FIRST_ALBUM_ID, "test", _notupgradableQuality, DateTime.UtcNow.AddDays(-100), EntityHistoryEventType.Grabbed); _upgradeHistory.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse(); } } diff --git a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ImportFixture.cs b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ImportFixture.cs index ede6d3e4b..16f6cfd1a 100644 --- a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ImportFixture.cs +++ b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ImportFixture.cs @@ -51,12 +51,16 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests Mocker.GetMock() .Setup(s => s.MostRecentForDownloadId(_trackedDownload.DownloadItem.DownloadId)) - .Returns(new History.History()); + .Returns(new EntityHistory()); Mocker.GetMock() .Setup(s => s.GetArtist("Drone.S01E01.HDTV")) .Returns(remoteAlbum.Artist); + Mocker.GetMock() + .Setup(s => s.FindByDownloadId(It.IsAny())) + .Returns(new List()); + Mocker.GetMock() .Setup(s => s.ProvideImportItem(It.IsAny(), It.IsAny())) .Returns((i, p) => i); @@ -93,7 +97,7 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests _trackedDownload.DownloadItem.Title = "Droned Pilot"; // Set a badly named download Mocker.GetMock() .Setup(s => s.MostRecentForDownloadId(It.Is(i => i == "1234"))) - .Returns(new History.History() { SourceTitle = "Droned S01E01" }); + .Returns(new EntityHistory() { SourceTitle = "Droned S01E01" }); Mocker.GetMock() .Setup(s => s.GetArtist(It.IsAny())) @@ -158,18 +162,6 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests AssertNotImported(); } - [Test] - public void should_not_mark_as_failed_if_nothing_found_to_import() - { - Mocker.GetMock() - .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(new List()); - - Subject.Import(_trackedDownload); - - _trackedDownload.State.Should().Be(TrackedDownloadState.ImportPending); - } - [Test] public void should_not_mark_as_imported_if_all_files_were_skipped() { @@ -206,6 +198,10 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests new ImportResult(new ImportDecision(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }), "Test Failure") }); + Mocker.GetMock() + .Setup(s => s.FindByDownloadId(It.IsAny())) + .Returns(new List()); + Subject.Import(_trackedDownload); AssertImported(); @@ -232,7 +228,7 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests new ImportResult(new ImportDecision(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }), "Test Failure") }); - var history = Builder.CreateListOfSize(2) + var history = Builder.CreateListOfSize(2) .BuildList(); Mocker.GetMock() @@ -275,7 +271,7 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests new ImportResult(new ImportDecision(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv" }), "Test Failure") }); - var history = Builder.CreateListOfSize(2) + var history = Builder.CreateListOfSize(2) .BuildList(); Mocker.GetMock() @@ -283,7 +279,7 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests .Returns(history); Mocker.GetMock() - .Setup(s => s.IsImported(It.IsAny(), It.IsAny>())) + .Setup(s => s.IsImported(It.IsAny(), It.IsAny>())) .Returns(false); Subject.Import(_trackedDownload); @@ -347,15 +343,18 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests new LocalTrack { Path = @"C:\TestPath\Droned.S01E02.mkv", Tracks = new List { track2 } }), "Test Failure") }); - var history = Builder.CreateListOfSize(2) - .BuildList(); + var history = Builder.CreateListOfSize(2) + .All() + .With(x => x.EventType = EntityHistoryEventType.TrackFileImported) + .With(x => x.ArtistId = 1) + .BuildList(); Mocker.GetMock() .Setup(s => s.FindByDownloadId(It.IsAny())) .Returns(history); Mocker.GetMock() - .Setup(s => s.IsImported(It.IsAny(), It.IsAny>())) + .Setup(s => s.IsImported(It.IsAny(), It.IsAny>())) .Returns(true); Subject.Import(_trackedDownload); diff --git a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ProcessFixture.cs b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ProcessFixture.cs index 9b7ebebc2..593fd7dd0 100644 --- a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ProcessFixture.cs +++ b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ProcessFixture.cs @@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests Mocker.GetMock() .Setup(s => s.MostRecentForDownloadId(_trackedDownload.DownloadItem.DownloadId)) - .Returns(new History.History()); + .Returns(new EntityHistory()); Mocker.GetMock() .Setup(s => s.GetArtist("Drone.S01E01.HDTV")) @@ -71,7 +71,7 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests { Mocker.GetMock() .Setup(s => s.MostRecentForDownloadId(_trackedDownload.DownloadItem.DownloadId)) - .Returns((History.History)null); + .Returns((EntityHistory)null); } private void GivenArtistMatch() @@ -87,7 +87,7 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests _trackedDownload.DownloadItem.Title = "Droned Pilot"; // Set a badly named download Mocker.GetMock() .Setup(s => s.MostRecentForDownloadId(It.Is(i => i == "1234"))) - .Returns(new History.History() { SourceTitle = "Droned S01E01" }); + .Returns(new EntityHistory() { SourceTitle = "Droned S01E01" }); Mocker.GetMock() .Setup(s => s.GetArtist(It.IsAny())) diff --git a/src/NzbDrone.Core.Test/Download/FailedDownloadServiceTests/ProcessFailedFixture.cs b/src/NzbDrone.Core.Test/Download/FailedDownloadServiceTests/ProcessFailedFixture.cs index b18c78a26..f7f08894b 100644 --- a/src/NzbDrone.Core.Test/Download/FailedDownloadServiceTests/ProcessFailedFixture.cs +++ b/src/NzbDrone.Core.Test/Download/FailedDownloadServiceTests/ProcessFailedFixture.cs @@ -19,7 +19,7 @@ namespace NzbDrone.Core.Test.Download.FailedDownloadServiceTests public class ProcessFailedFixture : CoreTest { private TrackedDownload _trackedDownload; - private List _grabHistory; + private List _grabHistory; [SetUp] public void Setup() @@ -30,7 +30,7 @@ namespace NzbDrone.Core.Test.Download.FailedDownloadServiceTests .With(h => h.Title = "Drone.S01E01.HDTV") .Build(); - _grabHistory = Builder.CreateListOfSize(2).BuildList(); + _grabHistory = Builder.CreateListOfSize(2).BuildList(); var remoteAlbum = new RemoteAlbum { @@ -45,7 +45,7 @@ namespace NzbDrone.Core.Test.Download.FailedDownloadServiceTests .Build(); Mocker.GetMock() - .Setup(s => s.Find(_trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed)) + .Setup(s => s.Find(_trackedDownload.DownloadItem.DownloadId, EntityHistoryEventType.Grabbed)) .Returns(_grabHistory); } diff --git a/src/NzbDrone.Core.Test/Download/FailedDownloadServiceTests/ProcessFixture.cs b/src/NzbDrone.Core.Test/Download/FailedDownloadServiceTests/ProcessFixture.cs index e728a80ee..0a189b546 100644 --- a/src/NzbDrone.Core.Test/Download/FailedDownloadServiceTests/ProcessFixture.cs +++ b/src/NzbDrone.Core.Test/Download/FailedDownloadServiceTests/ProcessFixture.cs @@ -19,7 +19,7 @@ namespace NzbDrone.Core.Test.Download.FailedDownloadServiceTests public class ProcessFixture : CoreTest { private TrackedDownload _trackedDownload; - private List _grabHistory; + private List _grabHistory; [SetUp] public void Setup() @@ -30,7 +30,7 @@ namespace NzbDrone.Core.Test.Download.FailedDownloadServiceTests .With(h => h.Title = "Drone.DroneTheAlbum.FLAC") .Build(); - _grabHistory = Builder.CreateListOfSize(2).BuildList(); + _grabHistory = Builder.CreateListOfSize(2).BuildList(); var remoteAlbum = new RemoteAlbum { @@ -45,15 +45,15 @@ namespace NzbDrone.Core.Test.Download.FailedDownloadServiceTests .Build(); Mocker.GetMock() - .Setup(s => s.Find(_trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed)) + .Setup(s => s.Find(_trackedDownload.DownloadItem.DownloadId, EntityHistoryEventType.Grabbed)) .Returns(_grabHistory); } private void GivenNoGrabbedHistory() { Mocker.GetMock() - .Setup(s => s.Find(_trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed)) - .Returns(new List()); + .Setup(s => s.Find(_trackedDownload.DownloadItem.DownloadId, EntityHistoryEventType.Grabbed)) + .Returns(new List()); } [Test] diff --git a/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadAlreadyImportedFixture.cs b/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadAlreadyImportedFixture.cs index 0d6567585..04f043541 100644 --- a/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadAlreadyImportedFixture.cs +++ b/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadAlreadyImportedFixture.cs @@ -1,7 +1,8 @@ -using System.Collections.Generic; +using System.Collections.Generic; using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; +using NzbDrone.Core.Download; using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.History; using NzbDrone.Core.Music; @@ -15,7 +16,7 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads { private List _albums; private TrackedDownload _trackedDownload; - private List _historyItems; + private List _historyItems; [SetUp] public void Setup() @@ -26,11 +27,14 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads .With(r => r.Albums = _albums) .Build(); + var downloadItem = Builder.CreateNew().Build(); + _trackedDownload = Builder.CreateNew() .With(t => t.RemoteAlbum = remoteAlbum) + .With(t => t.DownloadItem = downloadItem) .Build(); - _historyItems = new List(); + _historyItems = new List(); } public void GivenEpisodes(int count) @@ -39,12 +43,12 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads .BuildList()); } - public void GivenHistoryForEpisode(Album episode, params HistoryEventType[] eventTypes) + public void GivenHistoryForEpisode(Album episode, params EntityHistoryEventType[] eventTypes) { foreach (var eventType in eventTypes) { _historyItems.Add( - Builder.CreateNew() + Builder.CreateNew() .With(h => h.AlbumId = episode.Id) .With(h => h.EventType = eventType) .Build()); @@ -66,7 +70,7 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads { GivenEpisodes(1); - GivenHistoryForEpisode(_albums[0], HistoryEventType.Grabbed); + GivenHistoryForEpisode(_albums[0], EntityHistoryEventType.Grabbed); Subject.IsImported(_trackedDownload, _historyItems) .Should() @@ -78,8 +82,8 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads { GivenEpisodes(2); - GivenHistoryForEpisode(_albums[0], HistoryEventType.Grabbed); - GivenHistoryForEpisode(_albums[1], HistoryEventType.Grabbed); + GivenHistoryForEpisode(_albums[0], EntityHistoryEventType.Grabbed); + GivenHistoryForEpisode(_albums[1], EntityHistoryEventType.Grabbed); Subject.IsImported(_trackedDownload, _historyItems) .Should() @@ -91,8 +95,8 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads { GivenEpisodes(2); - GivenHistoryForEpisode(_albums[0], HistoryEventType.DownloadImported, HistoryEventType.Grabbed); - GivenHistoryForEpisode(_albums[1], HistoryEventType.Grabbed); + GivenHistoryForEpisode(_albums[0], EntityHistoryEventType.DownloadImported, EntityHistoryEventType.Grabbed); + GivenHistoryForEpisode(_albums[1], EntityHistoryEventType.Grabbed); Subject.IsImported(_trackedDownload, _historyItems) .Should() @@ -104,7 +108,7 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads { GivenEpisodes(1); - GivenHistoryForEpisode(_albums[0], HistoryEventType.DownloadImported, HistoryEventType.Grabbed); + GivenHistoryForEpisode(_albums[0], EntityHistoryEventType.DownloadImported, EntityHistoryEventType.Grabbed); Subject.IsImported(_trackedDownload, _historyItems) .Should() @@ -116,8 +120,8 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads { GivenEpisodes(2); - GivenHistoryForEpisode(_albums[0], HistoryEventType.DownloadImported, HistoryEventType.Grabbed); - GivenHistoryForEpisode(_albums[1], HistoryEventType.DownloadImported, HistoryEventType.Grabbed); + GivenHistoryForEpisode(_albums[0], EntityHistoryEventType.DownloadImported, EntityHistoryEventType.Grabbed); + GivenHistoryForEpisode(_albums[1], EntityHistoryEventType.DownloadImported, EntityHistoryEventType.Grabbed); Subject.IsImported(_trackedDownload, _historyItems) .Should() diff --git a/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadServiceFixture.cs b/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadServiceFixture.cs index 559152fdc..4072e0e9e 100644 --- a/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadServiceFixture.cs +++ b/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadServiceFixture.cs @@ -22,9 +22,9 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads { Mocker.GetMock() .Setup(s => s.FindByDownloadId(It.Is(sr => sr == "35238"))) - .Returns(new List() + .Returns(new List() { - new History.History() + new EntityHistory() { DownloadId = "35238", SourceTitle = "Audio Artist - Audio Album [2018 - FLAC]", diff --git a/src/NzbDrone.Core.Test/HealthCheck/Checks/RemotePathMappingCheckFixture.cs b/src/NzbDrone.Core.Test/HealthCheck/Checks/RemotePathMappingCheckFixture.cs index d1b0d989d..0f32f6ddd 100644 --- a/src/NzbDrone.Core.Test/HealthCheck/Checks/RemotePathMappingCheckFixture.cs +++ b/src/NzbDrone.Core.Test/HealthCheck/Checks/RemotePathMappingCheckFixture.cs @@ -71,6 +71,10 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks .Setup(s => s.GetDownloadClients()) .Returns(new IDownloadClient[] { _downloadClient.Object }); + Mocker.GetMock() + .Setup(s => s.Get(It.IsAny())) + .Returns(_downloadClient.Object); + Mocker.GetMock() .Setup(s => s.EnableCompletedDownloadHandling) .Returns(true); diff --git a/src/NzbDrone.Core.Test/HistoryTests/HistoryRepositoryFixture.cs b/src/NzbDrone.Core.Test/HistoryTests/HistoryRepositoryFixture.cs index bf149409d..7e9071fea 100644 --- a/src/NzbDrone.Core.Test/HistoryTests/HistoryRepositoryFixture.cs +++ b/src/NzbDrone.Core.Test/HistoryTests/HistoryRepositoryFixture.cs @@ -8,12 +8,12 @@ using NzbDrone.Core.Test.Framework; namespace NzbDrone.Core.Test.HistoryTests { [TestFixture] - public class HistoryRepositoryFixture : DbTest + public class HistoryRepositoryFixture : DbTest { [Test] public void should_read_write_dictionary() { - var history = Builder.CreateNew() + var history = Builder.CreateNew() .With(c => c.Quality = new QualityModel()) .BuildNew(); @@ -28,16 +28,16 @@ namespace NzbDrone.Core.Test.HistoryTests [Test] public void should_get_download_history() { - var historyBluray = Builder.CreateNew() + var historyBluray = Builder.CreateNew() .With(c => c.Quality = new QualityModel(Quality.MP3_320)) .With(c => c.ArtistId = 12) - .With(c => c.EventType = HistoryEventType.Grabbed) + .With(c => c.EventType = EntityHistoryEventType.Grabbed) .BuildNew(); - var historyDvd = Builder.CreateNew() + var historyDvd = Builder.CreateNew() .With(c => c.Quality = new QualityModel(Quality.MP3_192)) .With(c => c.ArtistId = 12) - .With(c => c.EventType = HistoryEventType.Grabbed) + .With(c => c.EventType = EntityHistoryEventType.Grabbed) .BuildNew(); Subject.Insert(historyBluray); diff --git a/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs b/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs index a029efeeb..22e8388cf 100644 --- a/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs +++ b/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs @@ -18,7 +18,7 @@ using NzbDrone.Core.Test.Qualities; namespace NzbDrone.Core.Test.HistoryTests { - public class HistoryServiceFixture : CoreTest + public class HistoryServiceFixture : CoreTest { private QualityProfile _profile; private QualityProfile _profileCustom; @@ -71,7 +71,7 @@ namespace NzbDrone.Core.Test.HistoryTests Subject.Handle(new TrackImportedEvent(localTrack, trackFile, new List(), true, downloadClientItem)); Mocker.GetMock() - .Verify(v => v.Insert(It.Is(h => h.SourceTitle == Path.GetFileNameWithoutExtension(localTrack.Path)))); + .Verify(v => v.Insert(It.Is(h => h.SourceTitle == Path.GetFileNameWithoutExtension(localTrack.Path)))); } } } diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedHistoryItemsFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedHistoryItemsFixture.cs index 5994b0792..1828ac653 100644 --- a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedHistoryItemsFixture.cs +++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedHistoryItemsFixture.cs @@ -1,6 +1,7 @@ -using FizzWare.NBuilder; +using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; +using NzbDrone.Core.History; using NzbDrone.Core.Housekeeping.Housekeepers; using NzbDrone.Core.Music; using NzbDrone.Core.Qualities; @@ -9,7 +10,7 @@ using NzbDrone.Core.Test.Framework; namespace NzbDrone.Core.Test.Housekeeping.Housekeepers { [TestFixture] - public class CleanupOrphanedHistoryItemsFixture : DbTest + public class CleanupOrphanedHistoryItemsFixture : DbTest { private Artist _artist; private Album _album; @@ -39,7 +40,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers { GivenAlbum(); - var history = Builder.CreateNew() + var history = Builder.CreateNew() .With(h => h.Quality = new QualityModel()) .With(h => h.AlbumId = _album.Id) .BuildNew(); @@ -54,7 +55,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers { GivenArtist(); - var history = Builder.CreateNew() + var history = Builder.CreateNew() .With(h => h.Quality = new QualityModel()) .With(h => h.ArtistId = _artist.Id) .BuildNew(); @@ -70,7 +71,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers GivenArtist(); GivenAlbum(); - var history = Builder.CreateListOfSize(2) + var history = Builder.CreateListOfSize(2) .All() .With(h => h.Quality = new QualityModel()) .With(h => h.AlbumId = _album.Id) @@ -91,7 +92,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers GivenArtist(); GivenAlbum(); - var history = Builder.CreateListOfSize(2) + var history = Builder.CreateListOfSize(2) .All() .With(h => h.Quality = new QualityModel()) .With(h => h.ArtistId = _artist.Id) diff --git a/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumServiceFixture.cs b/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumServiceFixture.cs index 55393fa4c..56b98c2b7 100644 --- a/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumServiceFixture.cs @@ -79,8 +79,8 @@ namespace NzbDrone.Core.Test.MusicTests .Returns(new List()); Mocker.GetMock() - .Setup(x => x.GetByAlbum(It.IsAny(), It.IsAny())) - .Returns(new List()); + .Setup(x => x.GetByAlbum(It.IsAny(), It.IsAny())) + .Returns(new List()); } private void GivenNewAlbumInfo(Album album) diff --git a/src/NzbDrone.Core.Test/MusicTests/RefreshArtistServiceFixture.cs b/src/NzbDrone.Core.Test/MusicTests/RefreshArtistServiceFixture.cs index 59a2fb129..18d3fe716 100644 --- a/src/NzbDrone.Core.Test/MusicTests/RefreshArtistServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/RefreshArtistServiceFixture.cs @@ -64,8 +64,8 @@ namespace NzbDrone.Core.Test.MusicTests .Returns(new List()); Mocker.GetMock() - .Setup(x => x.GetByArtist(It.IsAny(), It.IsAny())) - .Returns(new List()); + .Setup(x => x.GetByArtist(It.IsAny(), It.IsAny())) + .Returns(new List()); Mocker.GetMock() .Setup(x => x.FindByForeignId(It.IsAny>())) diff --git a/src/NzbDrone.Core.Test/QueueTests/QueueServiceFixture.cs b/src/NzbDrone.Core.Test/QueueTests/QueueServiceFixture.cs index f3f14cdc8..174e0fb1b 100644 --- a/src/NzbDrone.Core.Test/QueueTests/QueueServiceFixture.cs +++ b/src/NzbDrone.Core.Test/QueueTests/QueueServiceFixture.cs @@ -51,12 +51,12 @@ namespace NzbDrone.Core.Test.QueueTests .Build() .ToList(); - var historyItem = Builder.CreateNew() + var historyItem = Builder.CreateNew() .Build(); Mocker.GetMock() - .Setup(c => c.Find(It.IsAny(), HistoryEventType.Grabbed)).Returns( - new List { historyItem }); + .Setup(c => c.Find(It.IsAny(), EntityHistoryEventType.Grabbed)).Returns( + new List { historyItem }); } [Test] diff --git a/src/NzbDrone.Core/Analytics/AnalyticsService.cs b/src/NzbDrone.Core/Analytics/AnalyticsService.cs index c622bc088..f22d3d908 100644 --- a/src/NzbDrone.Core/Analytics/AnalyticsService.cs +++ b/src/NzbDrone.Core/Analytics/AnalyticsService.cs @@ -30,7 +30,7 @@ namespace NzbDrone.Core.Analytics { get { - var lastRecord = _historyService.Paged(new PagingSpec() { Page = 0, PageSize = 1, SortKey = "date", SortDirection = SortDirection.Descending }); + var lastRecord = _historyService.Paged(new PagingSpec() { Page = 0, PageSize = 1, SortKey = "date", SortDirection = SortDirection.Descending }); var monthAgo = DateTime.UtcNow.AddMonths(-1); return lastRecord.Records.Any(v => v.Date > monthAgo); diff --git a/src/NzbDrone.Core/Datastore/Migration/052_download_history.cs b/src/NzbDrone.Core/Datastore/Migration/052_download_history.cs new file mode 100644 index 000000000..b3c06ad77 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/052_download_history.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Data; +using FluentMigrator; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(052)] + public class download_history : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Create.TableForModel("DownloadHistory") + .WithColumn("EventType").AsInt32().NotNullable() + .WithColumn("ArtistId").AsInt32().NotNullable() + .WithColumn("DownloadId").AsString().NotNullable() + .WithColumn("SourceTitle").AsString().NotNullable() + .WithColumn("Date").AsDateTime().NotNullable() + .WithColumn("Protocol").AsInt32().Nullable() + .WithColumn("IndexerId").AsInt32().Nullable() + .WithColumn("DownloadClientId").AsInt32().Nullable() + .WithColumn("Release").AsString().Nullable() + .WithColumn("Data").AsString().Nullable(); + + Create.Index().OnTable("DownloadHistory").OnColumn("EventType"); + Create.Index().OnTable("DownloadHistory").OnColumn("ArtistId"); + Create.Index().OnTable("DownloadHistory").OnColumn("DownloadId"); + + Execute.WithConnection(InitialImportedDownloadHistory); + } + + private static readonly Dictionary EventTypeMap = new Dictionary() + { + // EntityHistoryType.Grabbed -> DownloadHistoryType.Grabbed + { 1, 1 }, + + // EntityHistoryType.DownloadFolderImported -> DownloadHistoryType.DownloadImported + { 8, 2 }, + + // EntityHistoryType.DownloadFailed -> DownloadHistoryType.DownloadFailed + { 4, 3 }, + + // EntityHistoryType.DownloadIgnored -> DownloadHistoryType.DownloadIgnored + { 10, 4 }, + + // EntityHistoryType.DownloadImportIncomplete -> DownloadHistoryType.DownloadImportIncomplete + { 7, 6 } + }; + + private void InitialImportedDownloadHistory(IDbConnection conn, IDbTransaction tran) + { + using (var cmd = conn.CreateCommand()) + { + cmd.Transaction = tran; + cmd.CommandText = "SELECT ArtistId, DownloadId, EventType, SourceTitle, Date, Data FROM History WHERE DownloadId IS NOT NULL AND EventType IN (1, 8, 4, 10, 7) GROUP BY EventType, DownloadId"; + + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + var artistId = reader.GetInt32(0); + var downloadId = reader.GetString(1); + var eventType = reader.GetInt32(2); + var sourceTitle = reader.GetString(3); + var date = reader.GetDateTime(4); + var rawData = reader.GetString(5); + var data = Json.Deserialize>(rawData); + + var downloadHistoryEventType = EventTypeMap[eventType]; + var protocol = data.ContainsKey("protocol") ? Convert.ToInt32(data["protocol"]) : (int?)null; + var downloadHistoryData = new Dictionary(); + + if (data.ContainsKey("indexer")) + { + downloadHistoryData.Add("indexer", data["indexer"]); + } + + if (data.ContainsKey("downloadClient")) + { + downloadHistoryData.Add("downloadClient", data["downloadClient"]); + } + + using (var updateCmd = conn.CreateCommand()) + { + updateCmd.Transaction = tran; + updateCmd.CommandText = @"INSERT INTO DownloadHistory (EventType, ArtistId, DownloadId, SourceTitle, Date, Protocol, Data) VALUES (?, ?, ?, ?, ?, ?, ?)"; + updateCmd.AddParameter(downloadHistoryEventType); + updateCmd.AddParameter(artistId); + updateCmd.AddParameter(downloadId); + updateCmd.AddParameter(sourceTitle); + updateCmd.AddParameter(date); + updateCmd.AddParameter(protocol); + updateCmd.AddParameter(downloadHistoryData.ToJson()); + + updateCmd.ExecuteNonQuery(); + } + } + } + } + } + } +} diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index 356175534..2177b353b 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -9,11 +9,13 @@ using NzbDrone.Core.Configuration; using NzbDrone.Core.CustomFilters; using NzbDrone.Core.Datastore.Converters; using NzbDrone.Core.Download; +using NzbDrone.Core.Download.History; using NzbDrone.Core.Download.Pending; using NzbDrone.Core.Extras.Lyrics; using NzbDrone.Core.Extras.Metadata; using NzbDrone.Core.Extras.Metadata.Files; using NzbDrone.Core.Extras.Others; +using NzbDrone.Core.History; using NzbDrone.Core.ImportLists; using NzbDrone.Core.ImportLists.Exclusions; using NzbDrone.Core.Indexers; @@ -93,7 +95,7 @@ namespace NzbDrone.Core.Datastore .Ignore(d => d.Protocol) .Ignore(d => d.Tags); - Mapper.Entity("History").RegisterModel(); + Mapper.Entity("History").RegisterModel(); Mapper.Entity("Artists") .Ignore(s => s.RootFolderPath) @@ -188,6 +190,7 @@ namespace NzbDrone.Core.Datastore Mapper.Entity("CustomFilters").RegisterModel(); Mapper.Entity("ImportListExclusions").RegisterModel(); + Mapper.Entity("DownloadHistory").RegisterModel(); } private static void RegisterMappers() diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/AlreadyImportedSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/AlreadyImportedSpecification.cs index 027f599e6..afb69c1ec 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/AlreadyImportedSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/AlreadyImportedSpecification.cs @@ -53,7 +53,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications } var historyForAlbum = _historyService.GetByAlbum(album.Id, null); - var lastGrabbed = historyForAlbum.FirstOrDefault(h => h.EventType == HistoryEventType.Grabbed); + var lastGrabbed = historyForAlbum.FirstOrDefault(h => h.EventType == EntityHistoryEventType.Grabbed); if (lastGrabbed == null) { @@ -61,7 +61,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications } var imported = historyForAlbum.FirstOrDefault(h => - h.EventType == HistoryEventType.DownloadImported && + h.EventType == EntityHistoryEventType.DownloadImported && h.DownloadId == lastGrabbed.DownloadId); if (imported == null) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs index 907f3efdf..655a12c9b 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs @@ -51,7 +51,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync _logger.Debug("Checking current status of album [{0}] in history", album.Id); var mostRecent = _historyService.MostRecentForAlbum(album.Id); - if (mostRecent != null && mostRecent.EventType == HistoryEventType.Grabbed) + if (mostRecent != null && mostRecent.EventType == EntityHistoryEventType.Grabbed) { var recent = mostRecent.Date.After(DateTime.UtcNow.AddHours(-12)); diff --git a/src/NzbDrone.Core/Download/AlbumGrabbedEvent.cs b/src/NzbDrone.Core/Download/AlbumGrabbedEvent.cs index 7876edec3..6c21f8373 100644 --- a/src/NzbDrone.Core/Download/AlbumGrabbedEvent.cs +++ b/src/NzbDrone.Core/Download/AlbumGrabbedEvent.cs @@ -1,4 +1,4 @@ -using NzbDrone.Common.Messaging; +using NzbDrone.Common.Messaging; using NzbDrone.Core.Parser.Model; namespace NzbDrone.Core.Download @@ -6,7 +6,9 @@ namespace NzbDrone.Core.Download public class AlbumGrabbedEvent : IEvent { public RemoteAlbum Album { get; private set; } + public int DownloadClientId { get; set; } public string DownloadClient { get; set; } + public string DownloadClientName { get; set; } public string DownloadId { get; set; } public AlbumGrabbedEvent(RemoteAlbum album) diff --git a/src/NzbDrone.Core/Download/Clients/Flood/Flood.cs b/src/NzbDrone.Core/Download/Clients/Flood/Flood.cs index 725514525..d3c492aa8 100644 --- a/src/NzbDrone.Core/Download/Clients/Flood/Flood.cs +++ b/src/NzbDrone.Core/Download/Clients/Flood/Flood.cs @@ -17,8 +17,10 @@ namespace NzbDrone.Core.Download.Clients.Flood public class Flood : TorrentClientBase { private readonly IFloodProxy _proxy; + private readonly IDownloadSeedConfigProvider _downloadSeedConfigProvider; public Flood(IFloodProxy proxy, + IDownloadSeedConfigProvider downloadSeedConfigProvider, ITorrentFileInfoReader torrentFileInfoReader, IHttpClient httpClient, IConfigService configService, @@ -28,6 +30,7 @@ namespace NzbDrone.Core.Download.Clients.Flood : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) { _proxy = proxy; + _downloadSeedConfigProvider = downloadSeedConfigProvider; } private static IEnumerable HandleTags(RemoteAlbum remoteAlbum, FloodSettings settings) @@ -137,6 +140,29 @@ namespace NzbDrone.Core.Download.Clients.Flood item.Status = DownloadItemStatus.Paused; } + if (item.Status == DownloadItemStatus.Completed) + { + // Grab cached seedConfig + var seedConfig = _downloadSeedConfigProvider.GetSeedConfiguration(item.DownloadId); + + if (seedConfig != null) + { + if (item.SeedRatio >= seedConfig.Ratio) + { + // Check if seed ratio reached + item.CanMoveFiles = item.CanBeRemoved = true; + } + else if (properties.DateFinished != null && properties.DateFinished > 0) + { + // Check if seed time reached + if ((DateTimeOffset.Now - DateTimeOffset.FromUnixTimeSeconds((long)properties.DateFinished)) >= seedConfig.SeedTime) + { + item.CanMoveFiles = item.CanBeRemoved = true; + } + } + } + } + items.Add(item); } diff --git a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs index b752d2cb2..6d440e57a 100644 --- a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs @@ -5,6 +5,7 @@ using System.Threading; using FluentValidation.Results; using NLog; using NzbDrone.Common.Disk; +using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; @@ -22,6 +23,8 @@ namespace NzbDrone.Core.Download.Clients.RTorrent { private readonly IRTorrentProxy _proxy; private readonly IRTorrentDirectoryValidator _rTorrentDirectoryValidator; + private readonly IDownloadSeedConfigProvider _downloadSeedConfigProvider; + private readonly string _imported_view = string.Concat(BuildInfo.AppName.ToLower(), "_imported"); public RTorrent(IRTorrentProxy proxy, ITorrentFileInfoReader torrentFileInfoReader, @@ -29,12 +32,14 @@ namespace NzbDrone.Core.Download.Clients.RTorrent IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, + IDownloadSeedConfigProvider downloadSeedConfigProvider, IRTorrentDirectoryValidator rTorrentDirectoryValidator, Logger logger) : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) { _proxy = proxy; _rTorrentDirectoryValidator = rTorrentDirectoryValidator; + _downloadSeedConfigProvider = downloadSeedConfigProvider; } public override void MarkItemAsImported(DownloadClientItem downloadClientItem) @@ -55,6 +60,16 @@ namespace NzbDrone.Core.Download.Clients.RTorrent downloadClientItem.Title); } } + + // Set post-import view + try + { + _proxy.PushTorrentUniqueView(downloadClientItem.DownloadId.ToLower(), _imported_view, Settings); + } + catch (Exception ex) + { + _logger.Warn(ex, "Failed to set torrent post-import view \"{0}\" for {1} in rTorrent.", _imported_view, downloadClientItem.Title); + } } protected override string AddFromMagnetLink(RemoteAlbum remoteAlbum, string hash, string magnetLink) @@ -152,8 +167,15 @@ namespace NzbDrone.Core.Download.Clients.RTorrent item.Status = DownloadItemStatus.Paused; } - // No stop ratio data is present, so do not delete - item.CanMoveFiles = item.CanBeRemoved = false; + // Grab cached seedConfig + var seedConfig = _downloadSeedConfigProvider.GetSeedConfiguration(torrent.Hash); + + // Check if torrent is finished and if it exceeds cached seedConfig + item.CanMoveFiles = item.CanBeRemoved = + torrent.IsFinished && seedConfig != null && + ( + (torrent.Ratio / 1000.0) >= seedConfig.Ratio || + (DateTimeOffset.Now - DateTimeOffset.FromUnixTimeSeconds(torrent.FinishedTime)) >= seedConfig.SeedTime); items.Add(item); } diff --git a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentProxy.cs b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentProxy.cs index 04380acf2..1e5191e54 100644 --- a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentProxy.cs +++ b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentProxy.cs @@ -4,6 +4,7 @@ using System.Net; using CookComputing.XmlRpc; using NLog; using NzbDrone.Common.Extensions; +using NzbDrone.Common.Serializer; namespace NzbDrone.Core.Download.Clients.RTorrent { @@ -17,6 +18,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent void RemoveTorrent(string hash, RTorrentSettings settings); void SetTorrentLabel(string hash, string label, RTorrentSettings settings); bool HasHashTorrent(string hash, RTorrentSettings settings); + void PushTorrentUniqueView(string hash, string view, RTorrentSettings settings); } public interface IRTorrent : IXmlRpcProxy @@ -45,6 +47,9 @@ namespace NzbDrone.Core.Download.Clients.RTorrent [XmlRpcMethod("d.custom1.set")] string SetLabel(string hash, string label); + [XmlRpcMethod("d.views.push_back_unique")] + int PushUniqueView(string hash, string view); + [XmlRpcMethod("system.client_version")] string GetVersion(); } @@ -86,7 +91,10 @@ namespace NzbDrone.Core.Download.Clients.RTorrent "d.ratio=", // long "d.is_open=", // long "d.is_active=", // long - "d.complete=")); //long + "d.complete=", //long + "d.timestamp.finished=")); // long (unix timestamp) + + _logger.Trace(ret.ToJson()); var items = new List(); @@ -106,6 +114,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent item.IsOpen = Convert.ToBoolean((long)torrent[8]); item.IsActive = Convert.ToBoolean((long)torrent[9]); item.IsFinished = Convert.ToBoolean((long)torrent[10]); + item.FinishedTime = (long)torrent[11]; items.Add(item); } @@ -172,6 +181,18 @@ namespace NzbDrone.Core.Download.Clients.RTorrent } } + public void PushTorrentUniqueView(string hash, string view, RTorrentSettings settings) + { + _logger.Debug("Executing remote method: d.views.push_back_unique"); + + var client = BuildClient(settings); + var response = ExecuteRequest(() => client.PushUniqueView(hash, view)); + if (response != 0) + { + throw new DownloadClientException("Could not push unique view {0} for torrent: {1}.", view, hash); + } + } + public void RemoveTorrent(string hash, RTorrentSettings settings) { _logger.Debug("Executing remote method: d.erase"); diff --git a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentTorrent.cs b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentTorrent.cs index d00df188f..14cd0b346 100644 --- a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentTorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentTorrent.cs @@ -10,6 +10,7 @@ public long RemainingSize { get; set; } public long DownRate { get; set; } public long Ratio { get; set; } + public long FinishedTime { get; set; } public bool IsFinished { get; set; } public bool IsOpen { get; set; } public bool IsActive { get; set; } diff --git a/src/NzbDrone.Core/Download/CompletedDownloadService.cs b/src/NzbDrone.Core/Download/CompletedDownloadService.cs index 83a0ae31c..988bb7dc5 100644 --- a/src/NzbDrone.Core/Download/CompletedDownloadService.cs +++ b/src/NzbDrone.Core/Download/CompletedDownloadService.cs @@ -2,8 +2,11 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using NLog; +using NLog.Fluent; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; +using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.History; using NzbDrone.Core.MediaFiles; @@ -11,6 +14,7 @@ using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.MediaFiles.TrackImport; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Music; +using NzbDrone.Core.Parser; namespace NzbDrone.Core.Download { @@ -18,6 +22,7 @@ namespace NzbDrone.Core.Download { void Check(TrackedDownload trackedDownload); void Import(TrackedDownload trackedDownload); + bool VerifyImport(TrackedDownload trackedDownload, List importResults); } public class CompletedDownloadService : ICompletedDownloadService @@ -27,27 +32,32 @@ namespace NzbDrone.Core.Download private readonly IDownloadedTracksImportService _downloadedTracksImportService; private readonly IArtistService _artistService; private readonly IProvideImportItemService _provideImportItemService; + private readonly IParsingService _parsingService; private readonly ITrackedDownloadAlreadyImported _trackedDownloadAlreadyImported; + private readonly Logger _logger; public CompletedDownloadService(IEventAggregator eventAggregator, IHistoryService historyService, IProvideImportItemService provideImportItemService, IDownloadedTracksImportService downloadedTracksImportService, IArtistService artistService, - ITrackedDownloadAlreadyImported trackedDownloadAlreadyImported) + IParsingService parsingService, + ITrackedDownloadAlreadyImported trackedDownloadAlreadyImported, + Logger logger) { _eventAggregator = eventAggregator; _historyService = historyService; _provideImportItemService = provideImportItemService; _downloadedTracksImportService = downloadedTracksImportService; _artistService = artistService; + _parsingService = parsingService; _trackedDownloadAlreadyImported = trackedDownloadAlreadyImported; + _logger = logger; } public void Check(TrackedDownload trackedDownload) { - if (trackedDownload.DownloadItem.Status != DownloadItemStatus.Completed || - trackedDownload.RemoteAlbum == null) + if (trackedDownload.DownloadItem.Status != DownloadItemStatus.Completed) { return; } @@ -73,7 +83,7 @@ namespace NzbDrone.Core.Download return; } - var artist = trackedDownload.RemoteAlbum.Artist; + var artist = _parsingService.GetArtist(trackedDownload.DownloadItem.Title); if (artist == null) { @@ -101,62 +111,119 @@ namespace NzbDrone.Core.Download return; } + if (trackedDownload.RemoteAlbum == null) + { + trackedDownload.Warn("Unable to parse download, automatic import is not possible."); + return; + } + trackedDownload.State = TrackedDownloadState.Importing; var outputPath = trackedDownload.ImportItem.OutputPath.FullPath; var importResults = _downloadedTracksImportService.ProcessPath(outputPath, ImportMode.Auto, trackedDownload.RemoteAlbum.Artist, trackedDownload.DownloadItem); - if (importResults.Empty()) + if (VerifyImport(trackedDownload, importResults)) { - trackedDownload.Warn("No files found are eligible for import in {0}", outputPath); - trackedDownload.State = TrackedDownloadState.ImportPending; return; } + trackedDownload.State = TrackedDownloadState.ImportPending; + + if (importResults.Empty()) + { + trackedDownload.Warn("No files found are eligible for import in {0}", outputPath); + + return; + } + + var statusMessages = new List + { + new TrackedDownloadStatusMessage("One or more albums expected in this release were not imported or missing", new List()) + }; + + if (importResults.Any(c => c.Result != ImportResultType.Imported)) + { + //Mark as failed to prevent further attempts at processing + trackedDownload.State = TrackedDownloadState.ImportFailed; + + statusMessages.AddRange( + importResults + .Where(v => v.Result != ImportResultType.Imported && v.ImportDecision.Item != null) + .OrderBy(v => v.ImportDecision.Item.Path) + .Select(v => + new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.Item.Path), + v.Errors))); + + if (statusMessages.Any()) + { + trackedDownload.Warn(statusMessages.ToArray()); + } + + //Publish event to notify Album was imported incompelte + _eventAggregator.PublishEvent(new AlbumImportIncompleteEvent(trackedDownload)); + return; + } + } + + public bool VerifyImport(TrackedDownload trackedDownload, List importResults) + { var allTracksImported = importResults.All(c => c.Result == ImportResultType.Imported) || importResults.Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteAlbum.Albums.Sum(x => x.AlbumReleases.Value.Where(y => y.Monitored).Sum(z => z.TrackCount))); if (allTracksImported) { + _logger.Debug("All albums were imported for {0}", trackedDownload.DownloadItem.Title); + trackedDownload.State = TrackedDownloadState.Imported; + + _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload, trackedDownload.RemoteAlbum.Artist.Id)); + return true; + } + + // Double check if all episodes were imported by checking the history if at least one + // file was imported. This will allow the decision engine to reject already imported + // episode files and still mark the download complete when all files are imported. + + // EDGE CASE: This process relies on EpisodeIds being consistent between executions, if a series is updated + // and an episode is removed, but later comes back with a different ID then Sonarr will treat it as incomplete. + // Since imports should be relatively fast and these types of data changes are infrequent this should be quite + // safe, but commenting for future benefit. + var atLeastOneEpisodeImported = importResults.Any(c => c.Result == ImportResultType.Imported); + + var historyItems = _historyService.FindByDownloadId(trackedDownload.DownloadItem.DownloadId) + .OrderByDescending(h => h.Date) + .ToList(); + + var allTracksImportedInHistory = _trackedDownloadAlreadyImported.IsImported(trackedDownload, historyItems); + + if (allTracksImportedInHistory) + { + // Log different error messages depending on the circumstances, but treat both as fully imported, because that's the reality. + // The second message shouldn't be logged in most cases, but continued reporting would indicate an ongoing issue. + if (atLeastOneEpisodeImported) + { + _logger.Debug("All albums were imported in history for {0}", trackedDownload.DownloadItem.Title); + } + else + { + _logger.Debug() + .Message("No albums were just imported, but all albums were previously imported, possible issue with download history.") + .Property("ArtistId", trackedDownload.RemoteAlbum.Artist.Id) + .Property("DownloadId", trackedDownload.DownloadItem.DownloadId) + .Property("Title", trackedDownload.DownloadItem.Title) + .Property("Path", trackedDownload.DownloadItem.OutputPath.ToString()) + .WriteSentryWarn("DownloadHistoryIncomplete") + .Write(); + } + trackedDownload.State = TrackedDownloadState.Imported; _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload, trackedDownload.RemoteAlbum.Artist.Id)); - return; + + return true; } - // Double check if all albums were imported by checking the history if at least one - // file was imported. This will allow the decision engine to reject already imported - // albums and still mark the download complete when all files are imported. - if (importResults.Any(c => c.Result == ImportResultType.Imported)) - { - var historyItems = _historyService.FindByDownloadId(trackedDownload.DownloadItem.DownloadId) - .OrderByDescending(h => h.Date) - .ToList(); - - var allTracksImportedInHistory = _trackedDownloadAlreadyImported.IsImported(trackedDownload, historyItems); - - if (allTracksImportedInHistory) - { - trackedDownload.State = TrackedDownloadState.Imported; - _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload, trackedDownload.RemoteAlbum.Artist.Id)); - return; - } - } - - trackedDownload.State = TrackedDownloadState.ImportPending; - - if (importResults.Any(c => c.Result != ImportResultType.Imported)) - { - trackedDownload.State = TrackedDownloadState.ImportFailed; - var statusMessages = importResults - .Where(v => v.Result != ImportResultType.Imported) - .Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.Item.Path), v.Errors)) - .ToArray(); - - trackedDownload.Warn(statusMessages); - _eventAggregator.PublishEvent(new AlbumImportIncompleteEvent(trackedDownload)); - return; - } + _logger.Debug("Not all albums have been imported for {0}", trackedDownload.DownloadItem.Title); + return false; } private void SetImportItem(TrackedDownload trackedDownload) diff --git a/src/NzbDrone.Core/Download/DownloadClientBase.cs b/src/NzbDrone.Core/Download/DownloadClientBase.cs index d2b5b0e7f..cd06abfdb 100644 --- a/src/NzbDrone.Core/Download/DownloadClientBase.cs +++ b/src/NzbDrone.Core/Download/DownloadClientBase.cs @@ -4,7 +4,6 @@ using System.Linq; using FluentValidation.Results; using NLog; using NzbDrone.Common.Disk; -using NzbDrone.Common.Extensions; using NzbDrone.Core.Configuration; using NzbDrone.Core.Indexers; using NzbDrone.Core.Parser.Model; @@ -75,7 +74,6 @@ namespace NzbDrone.Core.Download { if (item == null) { - _logger.Trace("DownloadItem {0} in {1} history not found, skipping delete data.", item.DownloadId, Name); return; } diff --git a/src/NzbDrone.Core/Download/DownloadClientItem.cs b/src/NzbDrone.Core/Download/DownloadClientItem.cs index 88b04e0fe..aa44a3acf 100644 --- a/src/NzbDrone.Core/Download/DownloadClientItem.cs +++ b/src/NzbDrone.Core/Download/DownloadClientItem.cs @@ -6,7 +6,7 @@ using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Download { - [DebuggerDisplay("{DownloadClientName}:{Title}")] + [DebuggerDisplay("{DownloadClientInfo?.Name}:{Title}")] public class DownloadClientItem { public DownloadClientItemClientInfo DownloadClientInfo { get; set; } diff --git a/src/NzbDrone.Core/Download/DownloadSeedConfigProvider.cs b/src/NzbDrone.Core/Download/DownloadSeedConfigProvider.cs new file mode 100644 index 000000000..8ca9cf284 --- /dev/null +++ b/src/NzbDrone.Core/Download/DownloadSeedConfigProvider.cs @@ -0,0 +1,90 @@ +using System; +using NLog; +using NzbDrone.Common.Cache; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Download.Clients; +using NzbDrone.Core.Download.History; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.Download +{ + public interface IDownloadSeedConfigProvider + { + TorrentSeedConfiguration GetSeedConfiguration(string infoHash); + } + + public class DownloadSeedConfigProvider : IDownloadSeedConfigProvider + { + private readonly Logger _logger; + private readonly ISeedConfigProvider _indexerSeedConfigProvider; + private readonly IDownloadHistoryService _downloadHistoryService; + + private class CachedSeedConfiguration + { + public int IndexerId { get; set; } + public bool Discography { get; set; } + } + + private readonly ICached _cacheDownloads; + + public DownloadSeedConfigProvider(IDownloadHistoryService downloadHistoryService, ISeedConfigProvider indexerSeedConfigProvider, ICacheManager cacheManager, Logger logger) + { + _logger = logger; + _indexerSeedConfigProvider = indexerSeedConfigProvider; + _downloadHistoryService = downloadHistoryService; + + _cacheDownloads = cacheManager.GetRollingCache(GetType(), "indexerByHash", TimeSpan.FromHours(1)); + } + + public TorrentSeedConfiguration GetSeedConfiguration(string infoHash) + { + if (infoHash.IsNullOrWhiteSpace()) + { + return null; + } + + infoHash = infoHash.ToUpper(); + + var cachedConfig = _cacheDownloads.Get(infoHash, () => FetchIndexer(infoHash)); + + if (cachedConfig == null) + { + return null; + } + + var seedConfig = _indexerSeedConfigProvider.GetSeedConfiguration(cachedConfig.IndexerId, cachedConfig.Discography); + + return seedConfig; + } + + private CachedSeedConfiguration FetchIndexer(string infoHash) + { + var historyItem = _downloadHistoryService.GetLatestGrab(infoHash); + + if (historyItem == null) + { + _logger.Debug("No download history item for infohash {0}, unable to provide seed configuration", infoHash); + return null; + } + + ParsedAlbumInfo parsedAlbumInfo = null; + if (historyItem.Release != null) + { + parsedAlbumInfo = Parser.Parser.ParseAlbumTitle(historyItem.Release.Title); + } + + if (parsedAlbumInfo == null) + { + _logger.Debug("No parsed title in download history item for infohash {0}, unable to provide seed configuration", infoHash); + return null; + } + + return new CachedSeedConfiguration + { + IndexerId = historyItem.IndexerId, + Discography = parsedAlbumInfo.Discography + }; + } + } +} diff --git a/src/NzbDrone.Core/Download/DownloadService.cs b/src/NzbDrone.Core/Download/DownloadService.cs index 58e57fd1a..8872aef7c 100644 --- a/src/NzbDrone.Core/Download/DownloadService.cs +++ b/src/NzbDrone.Core/Download/DownloadService.cs @@ -102,6 +102,8 @@ namespace NzbDrone.Core.Download var albumGrabbedEvent = new AlbumGrabbedEvent(remoteAlbum); albumGrabbedEvent.DownloadClient = downloadClient.Name; + albumGrabbedEvent.DownloadClientId = downloadClient.Definition.Id; + albumGrabbedEvent.DownloadClientName = downloadClient.Definition.Name; if (!string.IsNullOrWhiteSpace(downloadClientId)) { diff --git a/src/NzbDrone.Core/Download/FailedDownloadService.cs b/src/NzbDrone.Core/Download/FailedDownloadService.cs index 83f65935f..bf4c244d7 100644 --- a/src/NzbDrone.Core/Download/FailedDownloadService.cs +++ b/src/NzbDrone.Core/Download/FailedDownloadService.cs @@ -18,12 +18,15 @@ namespace NzbDrone.Core.Download public class FailedDownloadService : IFailedDownloadService { private readonly IHistoryService _historyService; + private readonly ITrackedDownloadService _trackedDownloadService; private readonly IEventAggregator _eventAggregator; public FailedDownloadService(IHistoryService historyService, + ITrackedDownloadService trackedDownloadService, IEventAggregator eventAggregator) { _historyService = historyService; + _trackedDownloadService = trackedDownloadService; _eventAggregator = eventAggregator; } @@ -34,22 +37,24 @@ namespace NzbDrone.Core.Download var downloadId = history.DownloadId; if (downloadId.IsNullOrWhiteSpace()) { - PublishDownloadFailedEvent(new List { history }, "Manually marked as failed", skipReDownload: skipReDownload); + PublishDownloadFailedEvent(new List { history }, "Manually marked as failed", skipReDownload: skipReDownload); } else { - var grabbedHistory = _historyService.Find(downloadId, HistoryEventType.Grabbed).ToList(); + var grabbedHistory = _historyService.Find(downloadId, EntityHistoryEventType.Grabbed).ToList(); PublishDownloadFailedEvent(grabbedHistory, "Manually marked as failed"); } } public void MarkAsFailed(string downloadId, bool skipReDownload = false) { - var history = _historyService.Find(downloadId, HistoryEventType.Grabbed); + var history = _historyService.Find(downloadId, EntityHistoryEventType.Grabbed); if (history.Any()) { - PublishDownloadFailedEvent(history, "Manually marked as failed", skipReDownload: skipReDownload); + var trackedDownload = _trackedDownloadService.Find(downloadId); + + PublishDownloadFailedEvent(history, "Manually marked as failed", trackedDownload, skipReDownload); } } @@ -65,7 +70,7 @@ namespace NzbDrone.Core.Download trackedDownload.DownloadItem.Status == DownloadItemStatus.Failed) { var grabbedItems = _historyService - .Find(trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed) + .Find(trackedDownload.DownloadItem.DownloadId, EntityHistoryEventType.Grabbed) .ToList(); if (grabbedItems.Empty()) @@ -86,7 +91,7 @@ namespace NzbDrone.Core.Download } var grabbedItems = _historyService - .Find(trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed) + .Find(trackedDownload.DownloadItem.DownloadId, EntityHistoryEventType.Grabbed) .ToList(); if (grabbedItems.Empty()) @@ -109,7 +114,7 @@ namespace NzbDrone.Core.Download PublishDownloadFailedEvent(grabbedItems, failure, trackedDownload); } - private void PublishDownloadFailedEvent(List historyItems, string message, TrackedDownload trackedDownload = null, bool skipReDownload = false) + private void PublishDownloadFailedEvent(List historyItems, string message, TrackedDownload trackedDownload = null, bool skipReDownload = false) { var historyItem = historyItems.First(); @@ -119,7 +124,7 @@ namespace NzbDrone.Core.Download AlbumIds = historyItems.Select(h => h.AlbumId).ToList(), Quality = historyItem.Quality, SourceTitle = historyItem.SourceTitle, - DownloadClient = historyItem.Data.GetValueOrDefault(History.History.DOWNLOAD_CLIENT), + DownloadClient = historyItem.Data.GetValueOrDefault(EntityHistory.DOWNLOAD_CLIENT), DownloadId = historyItem.DownloadId, Message = message, Data = historyItem.Data, diff --git a/src/NzbDrone.Core/Download/History/DownloadHistory.cs b/src/NzbDrone.Core/Download/History/DownloadHistory.cs new file mode 100644 index 000000000..3cb6a3e5a --- /dev/null +++ b/src/NzbDrone.Core/Download/History/DownloadHistory.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.Download.History +{ + public class DownloadHistory : ModelBase + { + public DownloadHistoryEventType EventType { get; set; } + public int ArtistId { get; set; } + public string DownloadId { get; set; } + public string SourceTitle { get; set; } + public DateTime Date { get; set; } + public DownloadProtocol Protocol { get; set; } + public int IndexerId { get; set; } + public int DownloadClientId { get; set; } + public ReleaseInfo Release { get; set; } + public Dictionary Data { get; set; } + public DownloadHistory() + { + Data = new Dictionary(); + } + } + + public enum DownloadHistoryEventType + { + DownloadGrabbed = 1, + DownloadImported = 2, + DownloadFailed = 3, + DownloadIgnored = 4, + FileImported = 5, + DownloadImportIncomplete = 6 + } +} diff --git a/src/NzbDrone.Core/Download/History/DownloadHistoryRepository.cs b/src/NzbDrone.Core/Download/History/DownloadHistoryRepository.cs new file mode 100644 index 000000000..94e136a5d --- /dev/null +++ b/src/NzbDrone.Core/Download/History/DownloadHistoryRepository.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging.Events; + +namespace NzbDrone.Core.Download.History +{ + public interface IDownloadHistoryRepository : IBasicRepository + { + List FindByDownloadId(string downloadId); + void DeleteByArtistIds(List artistIds); + } + + public class DownloadHistoryRepository : BasicRepository, IDownloadHistoryRepository + { + public DownloadHistoryRepository(IMainDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) + { + } + + public List FindByDownloadId(string downloadId) + { + return Query(x => x.DownloadId == downloadId).OrderByDescending(h => h.Date).ToList(); + } + + public void DeleteByArtistIds(List artistIds) + { + Delete(r => artistIds.Contains(r.ArtistId)); + } + } +} diff --git a/src/NzbDrone.Core/Download/History/DownloadHistoryService.cs b/src/NzbDrone.Core/Download/History/DownloadHistoryService.cs new file mode 100644 index 000000000..ce07124de --- /dev/null +++ b/src/NzbDrone.Core/Download/History/DownloadHistoryService.cs @@ -0,0 +1,248 @@ +using System; +using System.IO; +using System.Linq; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.History; +using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Music.Events; + +namespace NzbDrone.Core.Download.History +{ + public interface IDownloadHistoryService + { + bool DownloadAlreadyImported(string downloadId); + DownloadHistory GetLatestDownloadHistoryItem(string downloadId); + DownloadHistory GetLatestGrab(string downloadId); + } + + public class DownloadHistoryService : IDownloadHistoryService, + IHandle, + IHandle, + IHandle, + IHandle, + IHandle, + IHandle, + IHandle + { + private readonly IDownloadHistoryRepository _repository; + private readonly IHistoryService _historyService; + + public DownloadHistoryService(IDownloadHistoryRepository repository, IHistoryService historyService) + { + _repository = repository; + _historyService = historyService; + } + + public bool DownloadAlreadyImported(string downloadId) + { + var events = _repository.FindByDownloadId(downloadId); + + // Events are ordered by date descending, if a grabbed event comes before an imported event then it was never imported + // or grabbed again after importing and should be reprocessed. + foreach (var e in events) + { + if (e.EventType == DownloadHistoryEventType.DownloadGrabbed) + { + return false; + } + + if (e.EventType == DownloadHistoryEventType.DownloadImported) + { + return true; + } + } + + return false; + } + + public DownloadHistory GetLatestDownloadHistoryItem(string downloadId) + { + var events = _repository.FindByDownloadId(downloadId); + + // Events are ordered by date descending. We'll return the most recent expected event. + foreach (var e in events) + { + if (e.EventType == DownloadHistoryEventType.DownloadGrabbed) + { + return e; + } + + if (e.EventType == DownloadHistoryEventType.DownloadImported) + { + return e; + } + + if (e.EventType == DownloadHistoryEventType.DownloadFailed) + { + return e; + } + + if (e.EventType == DownloadHistoryEventType.DownloadImportIncomplete) + { + return e; + } + } + + return null; + } + + public DownloadHistory GetLatestGrab(string downloadId) + { + return _repository.FindByDownloadId(downloadId) + .FirstOrDefault(d => d.EventType == DownloadHistoryEventType.DownloadGrabbed); + } + + public void Handle(AlbumGrabbedEvent message) + { + var history = new DownloadHistory + { + EventType = DownloadHistoryEventType.DownloadGrabbed, + ArtistId = message.Album.Artist.Id, + DownloadId = message.DownloadId, + SourceTitle = message.Album.Release.Title, + Date = DateTime.UtcNow, + Protocol = message.Album.Release.DownloadProtocol, + IndexerId = message.Album.Release.IndexerId, + DownloadClientId = message.DownloadClientId, + Release = message.Album.Release + }; + + history.Data.Add("Indexer", message.Album.Release.Indexer); + history.Data.Add("DownloadClient", message.DownloadClient); + history.Data.Add("DownloadClientName", message.DownloadClientName); + history.Data.Add("PreferredWordScore", message.Album.PreferredWordScore.ToString()); + + _repository.Insert(history); + } + + public void Handle(TrackImportedEvent message) + { + if (!message.NewDownload) + { + return; + } + + var downloadId = message.DownloadId; + + // Try to find the downloadId if the user used manual import (from wanted: missing) or the + // API to import and downloadId wasn't provided. + if (downloadId.IsNullOrWhiteSpace()) + { + downloadId = _historyService.FindDownloadId(message); + } + + if (downloadId.IsNullOrWhiteSpace()) + { + return; + } + + var history = new DownloadHistory + { + EventType = DownloadHistoryEventType.FileImported, + ArtistId = message.TrackInfo.Artist.Id, + DownloadId = downloadId, + SourceTitle = message.TrackInfo.Path, + Date = DateTime.UtcNow, + Protocol = message.DownloadClientInfo.Protocol, + DownloadClientId = message.DownloadClientInfo.Id + }; + + history.Data.Add("DownloadClient", message.DownloadClientInfo.Type); + history.Data.Add("DownloadClientName", message.DownloadClientInfo.Name); + history.Data.Add("SourcePath", message.TrackInfo.Path); + history.Data.Add("DestinationPath", message.ImportedTrack.Path); + + _repository.Insert(history); + } + + public void Handle(AlbumImportIncompleteEvent message) + { + var history = new DownloadHistory + { + EventType = DownloadHistoryEventType.DownloadImportIncomplete, + ArtistId = message.TrackedDownload.RemoteAlbum?.Artist.Id ?? 0, + DownloadId = message.TrackedDownload.DownloadItem.DownloadId, + SourceTitle = message.TrackedDownload.DownloadItem.OutputPath.ToString(), + Date = DateTime.UtcNow, + Protocol = message.TrackedDownload.Protocol, + DownloadClientId = message.TrackedDownload.DownloadClient + }; + + history.Data.Add("DownloadClient", message.TrackedDownload.DownloadItem.DownloadClientInfo.Type); + history.Data.Add("DownloadClientName", message.TrackedDownload.DownloadItem.DownloadClientInfo.Name); + history.Data.Add("StatusMessages", message.TrackedDownload.StatusMessages.ToJson()); + + _repository.Insert(history); + } + + public void Handle(DownloadCompletedEvent message) + { + var history = new DownloadHistory + { + EventType = DownloadHistoryEventType.DownloadImported, + ArtistId = message.TrackedDownload.RemoteAlbum.Artist.Id, + DownloadId = message.TrackedDownload.DownloadItem.DownloadId, + SourceTitle = message.TrackedDownload.DownloadItem.OutputPath.ToString(), + Date = DateTime.UtcNow, + Protocol = message.TrackedDownload.Protocol, + DownloadClientId = message.TrackedDownload.DownloadClient + }; + + history.Data.Add("DownloadClient", message.TrackedDownload.DownloadItem.DownloadClientInfo.Type); + history.Data.Add("DownloadClientName", message.TrackedDownload.DownloadItem.DownloadClientInfo.Name); + + _repository.Insert(history); + } + + public void Handle(DownloadFailedEvent message) + { + // Don't track failed download for an unknown download + if (message.TrackedDownload == null) + { + return; + } + + var history = new DownloadHistory + { + EventType = DownloadHistoryEventType.DownloadFailed, + ArtistId = message.ArtistId, + DownloadId = message.DownloadId, + SourceTitle = message.SourceTitle, + Date = DateTime.UtcNow, + Protocol = message.TrackedDownload.Protocol, + DownloadClientId = message.TrackedDownload.DownloadClient + }; + + history.Data.Add("DownloadClient", message.TrackedDownload.DownloadItem.DownloadClientInfo.Type); + history.Data.Add("DownloadClientName", message.TrackedDownload.DownloadItem.DownloadClientInfo.Name); + + _repository.Insert(history); + } + + public void Handle(DownloadIgnoredEvent message) + { + var history = new DownloadHistory + { + EventType = DownloadHistoryEventType.DownloadIgnored, + ArtistId = message.ArtistId, + DownloadId = message.DownloadId, + SourceTitle = message.SourceTitle, + Date = DateTime.UtcNow, + Protocol = message.DownloadClientInfo.Protocol, + DownloadClientId = message.DownloadClientInfo.Id + }; + + history.Data.Add("DownloadClient", message.DownloadClientInfo.Type); + history.Data.Add("DownloadClientName", message.DownloadClientInfo.Name); + + _repository.Insert(history); + } + + public void Handle(ArtistsDeletedEvent message) + { + _repository.DeleteByArtistIds(message.Artists.Select(a => a.Id).ToList()); + } + } +} diff --git a/src/NzbDrone.Core/Download/PrepareImportService.cs b/src/NzbDrone.Core/Download/ProvideImportItemService.cs similarity index 100% rename from src/NzbDrone.Core/Download/PrepareImportService.cs rename to src/NzbDrone.Core/Download/ProvideImportItemService.cs diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadAlreadyImported.cs b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadAlreadyImported.cs index 3a91ae657..f911c9b9e 100644 --- a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadAlreadyImported.cs +++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadAlreadyImported.cs @@ -1,5 +1,6 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using NLog; using NzbDrone.Common.Extensions; using NzbDrone.Core.History; @@ -7,15 +8,25 @@ namespace NzbDrone.Core.Download.TrackedDownloads { public interface ITrackedDownloadAlreadyImported { - bool IsImported(TrackedDownload trackedDownload, List historyItems); + bool IsImported(TrackedDownload trackedDownload, List historyItems); } public class TrackedDownloadAlreadyImported : ITrackedDownloadAlreadyImported { - public bool IsImported(TrackedDownload trackedDownload, List historyItems) + private readonly Logger _logger; + + public TrackedDownloadAlreadyImported(Logger logger) { + _logger = logger; + } + + public bool IsImported(TrackedDownload trackedDownload, List historyItems) + { + _logger.Trace("Checking if all items for '{0}' have been imported", trackedDownload.DownloadItem.Title); + if (historyItems.Empty()) { + _logger.Trace("No history for {0}", trackedDownload.DownloadItem.Title); return false; } @@ -30,12 +41,17 @@ namespace NzbDrone.Core.Download.TrackedDownloads if (lastHistoryItem == null) { + _logger.Trace($"No history for album: {album}"); return false; } - return new[] { HistoryEventType.DownloadImported, HistoryEventType.TrackFileImported }.Contains(lastHistoryItem.EventType); + _logger.Trace($"Last event for album: {album} is: {lastHistoryItem.EventType}"); + + return new[] { EntityHistoryEventType.DownloadImported, EntityHistoryEventType.TrackFileImported }.Contains(lastHistoryItem.EventType); }); + _logger.Trace("All albums for '{0}' have been imported: {1}", trackedDownload.DownloadItem.Title, allAlbumsImportedInHistory); + return allAlbumsImportedInHistory; } } diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs index 0874068af..277616e2f 100644 --- a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs +++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs @@ -5,6 +5,7 @@ using NLog; using NzbDrone.Common.Cache; using NzbDrone.Common.Extensions; using NzbDrone.Common.Serializer; +using NzbDrone.Core.Download.History; using NzbDrone.Core.History; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Music; @@ -28,6 +29,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads private readonly IParsingService _parsingService; private readonly IHistoryService _historyService; private readonly IEventAggregator _eventAggregator; + private readonly IDownloadHistoryService _downloadHistoryService; private readonly ITrackedDownloadAlreadyImported _trackedDownloadAlreadyImported; private readonly Logger _logger; private readonly ICached _cache; @@ -35,15 +37,17 @@ namespace NzbDrone.Core.Download.TrackedDownloads public TrackedDownloadService(IParsingService parsingService, ICacheManager cacheManager, IHistoryService historyService, + IDownloadHistoryService downloadHistoryService, IEventAggregator eventAggregator, ITrackedDownloadAlreadyImported trackedDownloadAlreadyImported, Logger logger) { _parsingService = parsingService; _historyService = historyService; + _cache = cacheManager.GetCache(GetType()); _eventAggregator = eventAggregator; _trackedDownloadAlreadyImported = trackedDownloadAlreadyImported; - _cache = cacheManager.GetCache(GetType()); + _downloadHistoryService = downloadHistoryService; _logger = logger; } @@ -116,41 +120,31 @@ namespace NzbDrone.Core.Download.TrackedDownloads try { - var parsedAlbumInfo = Parser.Parser.ParseAlbumTitle(trackedDownload.DownloadItem.Title); var historyItems = _historyService.FindByDownloadId(downloadItem.DownloadId) .OrderByDescending(h => h.Date) .ToList(); + //TODO: Create release info from history and use that here, so we don't loose indexer flags! + var parsedAlbumInfo = Parser.Parser.ParseAlbumTitle(trackedDownload.DownloadItem.Title); + if (parsedAlbumInfo != null) { trackedDownload.RemoteAlbum = _parsingService.Map(parsedAlbumInfo); } + var downloadHistory = _downloadHistoryService.GetLatestDownloadHistoryItem(downloadItem.DownloadId); + + if (downloadHistory != null) + { + var state = GetStateFromHistory(downloadHistory.EventType); + trackedDownload.State = state; + } + if (historyItems.Any()) { var firstHistoryItem = historyItems.First(); - var state = GetStateFromHistory(firstHistoryItem); + var grabbedEvent = historyItems.FirstOrDefault(v => v.EventType == EntityHistoryEventType.Grabbed); - // One potential issue here is if the latest is imported, but other episodes are ignored or never imported. - // It's unlikely that will happen, but could happen if additional episodes are added to season after it's already imported. - if (state == TrackedDownloadState.Imported) - { - var allImported = _trackedDownloadAlreadyImported.IsImported(trackedDownload, historyItems); - - trackedDownload.State = allImported ? TrackedDownloadState.Imported : TrackedDownloadState.Downloading; - } - else - { - trackedDownload.State = state; - } - - if (firstHistoryItem.EventType == HistoryEventType.AlbumImportIncomplete) - { - var messages = Json.Deserialize>(firstHistoryItem?.Data["statusMessages"]).ToArray(); - trackedDownload.Warn(messages); - } - - var grabbedEvent = historyItems.FirstOrDefault(v => v.EventType == HistoryEventType.Grabbed); trackedDownload.Indexer = grabbedEvent?.Data["indexer"]; if (parsedAlbumInfo == null || @@ -159,7 +153,6 @@ namespace NzbDrone.Core.Download.TrackedDownloads trackedDownload.RemoteAlbum.Albums.Empty()) { // Try parsing the original source title and if that fails, try parsing it as a special - // TODO: Pass the TVDB ID and TVRage IDs in as well so we have a better chance for finding the item var historyArtist = firstHistoryItem.Artist; var historyAlbums = new List { firstHistoryItem.Album }; @@ -169,7 +162,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads { trackedDownload.RemoteAlbum = _parsingService.Map(parsedAlbumInfo, firstHistoryItem.ArtistId, - historyItems.Where(v => v.EventType == HistoryEventType.Grabbed).Select(h => h.AlbumId) + historyItems.Where(v => v.EventType == EntityHistoryEventType.Grabbed).Select(h => h.AlbumId) .Distinct()); } else @@ -183,7 +176,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads { trackedDownload.RemoteAlbum = _parsingService.Map(parsedAlbumInfo, firstHistoryItem.ArtistId, - historyItems.Where(v => v.EventType == HistoryEventType.Grabbed).Select(h => h.AlbumId) + historyItems.Where(v => v.EventType == EntityHistoryEventType.Grabbed).Select(h => h.AlbumId) .Distinct()); } } @@ -194,7 +187,6 @@ namespace NzbDrone.Core.Download.TrackedDownloads if (trackedDownload.RemoteAlbum == null) { _logger.Trace("No Album found for download '{0}'", trackedDownload.DownloadItem.Title); - trackedDownload.Warn("No Album found for download '{0}'", trackedDownload.DownloadItem.Title); } } catch (Exception e) @@ -224,6 +216,23 @@ namespace NzbDrone.Core.Download.TrackedDownloads } } + private static TrackedDownloadState GetStateFromHistory(DownloadHistoryEventType eventType) + { + switch (eventType) + { + case DownloadHistoryEventType.DownloadImportIncomplete: + return TrackedDownloadState.ImportFailed; + case DownloadHistoryEventType.DownloadImported: + return TrackedDownloadState.Imported; + case DownloadHistoryEventType.DownloadFailed: + return TrackedDownloadState.DownloadFailed; + case DownloadHistoryEventType.DownloadIgnored: + return TrackedDownloadState.Ignored; + default: + return TrackedDownloadState.Downloading; + } + } + private void LogItemChange(TrackedDownload trackedDownload, DownloadClientItem existingItem, DownloadClientItem downloadItem) { if (existingItem == null || @@ -242,29 +251,6 @@ namespace NzbDrone.Core.Download.TrackedDownloads } } - private static TrackedDownloadState GetStateFromHistory(NzbDrone.Core.History.History history) - { - switch (history.EventType) - { - case HistoryEventType.AlbumImportIncomplete: - return TrackedDownloadState.ImportFailed; - case HistoryEventType.DownloadImported: - return TrackedDownloadState.Imported; - case HistoryEventType.DownloadFailed: - return TrackedDownloadState.DownloadFailed; - case HistoryEventType.DownloadIgnored: - return TrackedDownloadState.Ignored; - } - - // Since DownloadComplete is a new event type, we can't assume it exists for old downloads - if (history.EventType == HistoryEventType.TrackFileImported) - { - return DateTime.UtcNow.Subtract(history.Date).TotalSeconds < 60 ? TrackedDownloadState.Importing : TrackedDownloadState.Imported; - } - - return TrackedDownloadState.Downloading; - } - public void Handle(AlbumDeletedEvent message) { UpdateAlbumCache(message.Album.Id); diff --git a/src/NzbDrone.Core/HealthCheck/Checks/RemotePathMappingCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/RemotePathMappingCheck.cs index 35560203b..385d1e934 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/RemotePathMappingCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/RemotePathMappingCheck.cs @@ -136,7 +136,7 @@ namespace NzbDrone.Core.HealthCheck.Checks // If the previous case did not match then the failure occured in DownloadedTracksImportService, // while trying to locate the files reported by the download client - var client = _downloadClientProvider.GetDownloadClients().FirstOrDefault(x => x.Definition.Name == failureMessage.DownloadClient); + var client = _downloadClientProvider.Get(failureMessage.DownloadClientInfo.Id); try { var status = client.GetStatus(); diff --git a/src/NzbDrone.Core/History/History.cs b/src/NzbDrone.Core/History/EntityHistory.cs similarity index 86% rename from src/NzbDrone.Core/History/History.cs rename to src/NzbDrone.Core/History/EntityHistory.cs index 8d9592361..c9aada55e 100644 --- a/src/NzbDrone.Core/History/History.cs +++ b/src/NzbDrone.Core/History/EntityHistory.cs @@ -6,11 +6,11 @@ using NzbDrone.Core.Qualities; namespace NzbDrone.Core.History { - public class History : ModelBase + public class EntityHistory : ModelBase { public const string DOWNLOAD_CLIENT = "downloadClient"; - public History() + public EntityHistory() { Data = new Dictionary(); } @@ -24,13 +24,13 @@ namespace NzbDrone.Core.History public Album Album { get; set; } public Artist Artist { get; set; } public Track Track { get; set; } - public HistoryEventType EventType { get; set; } + public EntityHistoryEventType EventType { get; set; } public Dictionary Data { get; set; } public string DownloadId { get; set; } } - public enum HistoryEventType + public enum EntityHistoryEventType { Unknown = 0, Grabbed = 1, diff --git a/src/NzbDrone.Core/History/EntityHistoryRepository.cs b/src/NzbDrone.Core/History/EntityHistoryRepository.cs new file mode 100644 index 000000000..07eded0fa --- /dev/null +++ b/src/NzbDrone.Core/History/EntityHistoryRepository.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Music; +using NzbDrone.Core.Qualities; + +namespace NzbDrone.Core.History +{ + public interface IHistoryRepository : IBasicRepository + { + EntityHistory MostRecentForAlbum(int albumId); + EntityHistory MostRecentForDownloadId(string downloadId); + List FindByDownloadId(string downloadId); + List GetByArtist(int artistId, EntityHistoryEventType? eventType); + List GetByAlbum(int albumId, EntityHistoryEventType? eventType); + List FindDownloadHistory(int idArtistId, QualityModel quality); + void DeleteForArtists(List artistIds); + List Since(DateTime date, EntityHistoryEventType? eventType); + } + + public class EntityHistoryRepository : BasicRepository, IHistoryRepository + { + public EntityHistoryRepository(IMainDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) + { + } + + public EntityHistory MostRecentForAlbum(int albumId) + { + return Query(h => h.AlbumId == albumId) + .OrderByDescending(h => h.Date) + .FirstOrDefault(); + } + + public EntityHistory MostRecentForDownloadId(string downloadId) + { + return Query(h => h.DownloadId == downloadId) + .OrderByDescending(h => h.Date) + .FirstOrDefault(); + } + + public List FindByDownloadId(string downloadId) + { + return _database.QueryJoined( + Builder() + .Join((h, a) => h.ArtistId == a.Id) + .Join((h, a) => h.AlbumId == a.Id) + .Where(h => h.DownloadId == downloadId), + (history, artist, album) => + { + history.Artist = artist; + history.Album = album; + return history; + }).ToList(); + } + + public List GetByArtist(int artistId, EntityHistoryEventType? eventType) + { + var builder = Builder().Where(h => h.ArtistId == artistId); + + if (eventType.HasValue) + { + builder.Where(h => h.EventType == eventType); + } + + return Query(builder).OrderByDescending(h => h.Date).ToList(); + } + + public List GetByAlbum(int albumId, EntityHistoryEventType? eventType) + { + var builder = Builder() + .Join((h, a) => h.AlbumId == a.Id) + .Where(h => h.AlbumId == albumId); + + if (eventType.HasValue) + { + builder.Where(h => h.EventType == eventType); + } + + return _database.QueryJoined( + builder, + (history, album) => + { + history.Album = album; + return history; + }).OrderByDescending(h => h.Date).ToList(); + } + + public List FindDownloadHistory(int idArtistId, QualityModel quality) + { + var allowed = new[] { EntityHistoryEventType.Grabbed, EntityHistoryEventType.DownloadFailed, EntityHistoryEventType.TrackFileImported }; + + return Query(h => h.ArtistId == idArtistId && + h.Quality == quality && + allowed.Contains(h.EventType)); + } + + public void DeleteForArtists(List artistIds) + { + Delete(c => artistIds.Contains(c.ArtistId)); + } + + protected override SqlBuilder PagedBuilder() => new SqlBuilder() + .Join((h, a) => h.ArtistId == a.Id) + .Join((h, a) => h.AlbumId == a.Id) + .LeftJoin((h, t) => h.TrackId == t.Id); + protected override IEnumerable PagedQuery(SqlBuilder builder) => + _database.QueryJoined(builder, (history, artist, album, track) => + { + history.Artist = artist; + history.Album = album; + history.Track = track; + return history; + }); + + public List Since(DateTime date, EntityHistoryEventType? eventType) + { + var builder = Builder().Where(x => x.Date >= date); + + if (eventType.HasValue) + { + builder.Where(h => h.EventType == eventType); + } + + return Query(builder).OrderBy(h => h.Date).ToList(); + } + } +} diff --git a/src/NzbDrone.Core/History/HistoryService.cs b/src/NzbDrone.Core/History/EntityHistoryService.cs similarity index 80% rename from src/NzbDrone.Core/History/HistoryService.cs rename to src/NzbDrone.Core/History/EntityHistoryService.cs index ca6e19f20..302f23801 100644 --- a/src/NzbDrone.Core/History/HistoryService.cs +++ b/src/NzbDrone.Core/History/EntityHistoryService.cs @@ -18,19 +18,20 @@ namespace NzbDrone.Core.History { public interface IHistoryService { - PagingSpec Paged(PagingSpec pagingSpec); - History MostRecentForAlbum(int albumId); - History MostRecentForDownloadId(string downloadId); - History Get(int historyId); - List GetByArtist(int artistId, HistoryEventType? eventType); - List GetByAlbum(int albumId, HistoryEventType? eventType); - List Find(string downloadId, HistoryEventType eventType); - List FindByDownloadId(string downloadId); - List Since(DateTime date, HistoryEventType? eventType); - void UpdateMany(IList items); + PagingSpec Paged(PagingSpec pagingSpec); + EntityHistory MostRecentForAlbum(int albumId); + EntityHistory MostRecentForDownloadId(string downloadId); + EntityHistory Get(int historyId); + List GetByArtist(int artistId, EntityHistoryEventType? eventType); + List GetByAlbum(int albumId, EntityHistoryEventType? eventType); + List Find(string downloadId, EntityHistoryEventType eventType); + List FindByDownloadId(string downloadId); + string FindDownloadId(TrackImportedEvent trackedDownload); + List Since(DateTime date, EntityHistoryEventType? eventType); + void UpdateMany(IList items); } - public class HistoryService : IHistoryService, + public class EntityHistoryService : IHistoryService, IHandle, IHandle, IHandle, @@ -45,53 +46,53 @@ namespace NzbDrone.Core.History private readonly IHistoryRepository _historyRepository; private readonly Logger _logger; - public HistoryService(IHistoryRepository historyRepository, Logger logger) + public EntityHistoryService(IHistoryRepository historyRepository, Logger logger) { _historyRepository = historyRepository; _logger = logger; } - public PagingSpec Paged(PagingSpec pagingSpec) + public PagingSpec Paged(PagingSpec pagingSpec) { return _historyRepository.GetPaged(pagingSpec); } - public History MostRecentForAlbum(int albumId) + public EntityHistory MostRecentForAlbum(int albumId) { return _historyRepository.MostRecentForAlbum(albumId); } - public History MostRecentForDownloadId(string downloadId) + public EntityHistory MostRecentForDownloadId(string downloadId) { return _historyRepository.MostRecentForDownloadId(downloadId); } - public History Get(int historyId) + public EntityHistory Get(int historyId) { return _historyRepository.Get(historyId); } - public List GetByArtist(int artistId, HistoryEventType? eventType) + public List GetByArtist(int artistId, EntityHistoryEventType? eventType) { return _historyRepository.GetByArtist(artistId, eventType); } - public List GetByAlbum(int albumId, HistoryEventType? eventType) + public List GetByAlbum(int albumId, EntityHistoryEventType? eventType) { return _historyRepository.GetByAlbum(albumId, eventType); } - public List Find(string downloadId, HistoryEventType eventType) + public List Find(string downloadId, EntityHistoryEventType eventType) { return _historyRepository.FindByDownloadId(downloadId).Where(c => c.EventType == eventType).ToList(); } - public List FindByDownloadId(string downloadId) + public List FindByDownloadId(string downloadId) { return _historyRepository.FindByDownloadId(downloadId); } - private string FindDownloadId(TrackImportedEvent trackedDownload) + public string FindDownloadId(TrackImportedEvent trackedDownload) { _logger.Debug("Trying to find downloadId for {0} from history", trackedDownload.ImportedTrack.Path); @@ -103,10 +104,10 @@ namespace NzbDrone.Core.History var albumsHistory = allHistory.Where(h => albumIds.Contains(h.AlbumId)).ToList(); var processedDownloadId = albumsHistory - .Where(c => c.EventType != HistoryEventType.Grabbed && c.DownloadId != null) + .Where(c => c.EventType != EntityHistoryEventType.Grabbed && c.DownloadId != null) .Select(c => c.DownloadId); - var stillDownloading = albumsHistory.Where(c => c.EventType == HistoryEventType.Grabbed && !processedDownloadId.Contains(c.DownloadId)).ToList(); + var stillDownloading = albumsHistory.Where(c => c.EventType == EntityHistoryEventType.Grabbed && !processedDownloadId.Contains(c.DownloadId)).ToList(); string downloadId = null; @@ -139,9 +140,9 @@ namespace NzbDrone.Core.History { foreach (var album in message.Album.Albums) { - var history = new History + var history = new EntityHistory { - EventType = HistoryEventType.Grabbed, + EventType = EntityHistoryEventType.Grabbed, Date = DateTime.UtcNow, Quality = message.Album.ParsedAlbumInfo.Quality, SourceTitle = message.Album.Release.Title, @@ -184,9 +185,9 @@ namespace NzbDrone.Core.History { foreach (var album in message.TrackedDownload.RemoteAlbum.Albums) { - var history = new History + var history = new EntityHistory { - EventType = HistoryEventType.AlbumImportIncomplete, + EventType = EntityHistoryEventType.AlbumImportIncomplete, Date = DateTime.UtcNow, Quality = message.TrackedDownload.RemoteAlbum.ParsedAlbumInfo?.Quality ?? new QualityModel(), SourceTitle = message.TrackedDownload.DownloadItem.Title, @@ -216,9 +217,9 @@ namespace NzbDrone.Core.History foreach (var track in message.TrackInfo.Tracks) { - var history = new History + var history = new EntityHistory { - EventType = HistoryEventType.TrackFileImported, + EventType = EntityHistoryEventType.TrackFileImported, Date = DateTime.UtcNow, Quality = message.TrackInfo.Quality, SourceTitle = message.ImportedTrack.SceneName ?? Path.GetFileNameWithoutExtension(message.TrackInfo.Path), @@ -232,7 +233,7 @@ namespace NzbDrone.Core.History //history.Data.Add("FileId", message.ImportedEpisode.Id.ToString()); history.Data.Add("DroppedPath", message.TrackInfo.Path); history.Data.Add("ImportedPath", message.ImportedTrack.Path); - history.Data.Add("DownloadClient", message.DownloadClient); + history.Data.Add("DownloadClient", message.DownloadClientInfo.Name); _historyRepository.Insert(history); } @@ -242,9 +243,9 @@ namespace NzbDrone.Core.History { foreach (var albumId in message.AlbumIds) { - var history = new History + var history = new EntityHistory { - EventType = HistoryEventType.DownloadFailed, + EventType = EntityHistoryEventType.DownloadFailed, Date = DateTime.UtcNow, Quality = message.Quality, SourceTitle = message.SourceTitle, @@ -264,9 +265,9 @@ namespace NzbDrone.Core.History { foreach (var album in message.TrackedDownload.RemoteAlbum.Albums) { - var history = new History + var history = new EntityHistory { - EventType = HistoryEventType.DownloadImported, + EventType = EntityHistoryEventType.DownloadImported, Date = DateTime.UtcNow, Quality = message.TrackedDownload.RemoteAlbum.ParsedAlbumInfo?.Quality ?? new QualityModel(), SourceTitle = message.TrackedDownload.DownloadItem.Title, @@ -294,9 +295,9 @@ namespace NzbDrone.Core.History foreach (var track in message.TrackFile.Tracks.Value) { - var history = new History + var history = new EntityHistory { - EventType = HistoryEventType.TrackFileDeleted, + EventType = EntityHistoryEventType.TrackFileDeleted, Date = DateTime.UtcNow, Quality = message.TrackFile.Quality, SourceTitle = message.TrackFile.Path, @@ -318,9 +319,9 @@ namespace NzbDrone.Core.History foreach (var track in message.TrackFile.Tracks.Value) { - var history = new History + var history = new EntityHistory { - EventType = HistoryEventType.TrackFileRenamed, + EventType = EntityHistoryEventType.TrackFileRenamed, Date = DateTime.UtcNow, Quality = message.TrackFile.Quality, SourceTitle = message.OriginalPath, @@ -342,9 +343,9 @@ namespace NzbDrone.Core.History foreach (var track in message.TrackFile.Tracks.Value) { - var history = new History + var history = new EntityHistory { - EventType = HistoryEventType.TrackFileRetagged, + EventType = EntityHistoryEventType.TrackFileRetagged, Date = DateTime.UtcNow, Quality = message.TrackFile.Quality, SourceTitle = path, @@ -372,12 +373,12 @@ namespace NzbDrone.Core.History public void Handle(DownloadIgnoredEvent message) { - var historyToAdd = new List(); + var historyToAdd = new List(); foreach (var albumId in message.AlbumIds) { - var history = new History + var history = new EntityHistory { - EventType = HistoryEventType.DownloadIgnored, + EventType = EntityHistoryEventType.DownloadIgnored, Date = DateTime.UtcNow, Quality = message.Quality, SourceTitle = message.SourceTitle, @@ -395,12 +396,12 @@ namespace NzbDrone.Core.History _historyRepository.InsertMany(historyToAdd); } - public List Since(DateTime date, HistoryEventType? eventType) + public List Since(DateTime date, EntityHistoryEventType? eventType) { return _historyRepository.Since(date, eventType); } - public void UpdateMany(IList items) + public void UpdateMany(IList items) { _historyRepository.UpdateMany(items); } diff --git a/src/NzbDrone.Core/History/HistoryRepository.cs b/src/NzbDrone.Core/History/HistoryRepository.cs deleted file mode 100644 index 71c6c4892..000000000 --- a/src/NzbDrone.Core/History/HistoryRepository.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NzbDrone.Core.Datastore; -using NzbDrone.Core.Messaging.Events; -using NzbDrone.Core.Music; -using NzbDrone.Core.Qualities; - -namespace NzbDrone.Core.History -{ - public interface IHistoryRepository : IBasicRepository - { - History MostRecentForAlbum(int albumId); - History MostRecentForDownloadId(string downloadId); - List FindByDownloadId(string downloadId); - List GetByArtist(int artistId, HistoryEventType? eventType); - List GetByAlbum(int albumId, HistoryEventType? eventType); - List FindDownloadHistory(int idArtistId, QualityModel quality); - void DeleteForArtists(List artistIds); - List Since(DateTime date, HistoryEventType? eventType); - } - - public class HistoryRepository : BasicRepository, IHistoryRepository - { - public HistoryRepository(IMainDatabase database, IEventAggregator eventAggregator) - : base(database, eventAggregator) - { - } - - public History MostRecentForAlbum(int albumId) - { - return Query(h => h.AlbumId == albumId) - .OrderByDescending(h => h.Date) - .FirstOrDefault(); - } - - public History MostRecentForDownloadId(string downloadId) - { - return Query(h => h.DownloadId == downloadId) - .OrderByDescending(h => h.Date) - .FirstOrDefault(); - } - - public List FindByDownloadId(string downloadId) - { - return _database.QueryJoined( - Builder() - .Join((h, a) => h.ArtistId == a.Id) - .Join((h, a) => h.AlbumId == a.Id) - .Where(h => h.DownloadId == downloadId), - (history, artist, album) => - { - history.Artist = artist; - history.Album = album; - return history; - }).ToList(); - } - - public List GetByArtist(int artistId, HistoryEventType? eventType) - { - var builder = Builder().Where(h => h.ArtistId == artistId); - - if (eventType.HasValue) - { - builder.Where(h => h.EventType == eventType); - } - - return Query(builder).OrderByDescending(h => h.Date).ToList(); - } - - public List GetByAlbum(int albumId, HistoryEventType? eventType) - { - var builder = Builder() - .Join((h, a) => h.AlbumId == a.Id) - .Where(h => h.AlbumId == albumId); - - if (eventType.HasValue) - { - builder.Where(h => h.EventType == eventType); - } - - return _database.QueryJoined( - builder, - (history, album) => - { - history.Album = album; - return history; - }).OrderByDescending(h => h.Date).ToList(); - } - - public List FindDownloadHistory(int idArtistId, QualityModel quality) - { - var allowed = new[] { HistoryEventType.Grabbed, HistoryEventType.DownloadFailed, HistoryEventType.TrackFileImported }; - - return Query(h => h.ArtistId == idArtistId && - h.Quality == quality && - allowed.Contains(h.EventType)); - } - - public void DeleteForArtists(List artistIds) - { - Delete(c => artistIds.Contains(c.ArtistId)); - } - - protected override SqlBuilder PagedBuilder() => new SqlBuilder() - .Join((h, a) => h.ArtistId == a.Id) - .Join((h, a) => h.AlbumId == a.Id) - .LeftJoin((h, t) => h.TrackId == t.Id); - protected override IEnumerable PagedQuery(SqlBuilder builder) => - _database.QueryJoined(builder, (history, artist, album, track) => - { - history.Artist = artist; - history.Album = album; - history.Track = track; - return history; - }); - - public List Since(DateTime date, HistoryEventType? eventType) - { - var builder = Builder().Where(x => x.Date >= date); - - if (eventType.HasValue) - { - builder.Where(h => h.EventType == eventType); - } - - return Query(builder).OrderBy(h => h.Date).ToList(); - } - } -} diff --git a/src/NzbDrone.Core/Indexers/SeedConfigProvider.cs b/src/NzbDrone.Core/Indexers/SeedConfigProvider.cs index f41d65ea6..f05ce544a 100644 --- a/src/NzbDrone.Core/Indexers/SeedConfigProvider.cs +++ b/src/NzbDrone.Core/Indexers/SeedConfigProvider.cs @@ -1,22 +1,28 @@ using System; +using NzbDrone.Common.Cache; using NzbDrone.Core.Datastore; using NzbDrone.Core.Download.Clients; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.ThingiProvider.Events; namespace NzbDrone.Core.Indexers { public interface ISeedConfigProvider { TorrentSeedConfiguration GetSeedConfiguration(RemoteAlbum release); + TorrentSeedConfiguration GetSeedConfiguration(int indexerId, bool discography); } - public class SeedConfigProvider : ISeedConfigProvider + public class SeedConfigProvider : ISeedConfigProvider, IHandle> { private readonly IIndexerFactory _indexerFactory; + private readonly ICached _cache; - public SeedConfigProvider(IIndexerFactory indexerFactory) + public SeedConfigProvider(IIndexerFactory indexerFactory, ICacheManager cacheManager) { _indexerFactory = indexerFactory; + _cache = cacheManager.GetRollingCache(GetType(), "criteriaByIndexer", TimeSpan.FromHours(1)); } public TorrentSeedConfiguration GetSeedConfiguration(RemoteAlbum remoteAlbum) @@ -31,33 +37,55 @@ namespace NzbDrone.Core.Indexers return null; } + return GetSeedConfiguration(remoteAlbum.Release.IndexerId, remoteAlbum.ParsedAlbumInfo.Discography); + } + + public TorrentSeedConfiguration GetSeedConfiguration(int indexerId, bool fullSeason) + { + if (indexerId == 0) + { + return null; + } + + var seedCriteria = _cache.Get(indexerId.ToString(), () => FetchSeedCriteria(indexerId)); + + if (seedCriteria == null) + { + return null; + } + + var seedConfig = new TorrentSeedConfiguration + { + Ratio = seedCriteria.SeedRatio + }; + + var seedTime = fullSeason ? seedCriteria.DiscographySeedTime : seedCriteria.SeedTime; + if (seedTime.HasValue) + { + seedConfig.SeedTime = TimeSpan.FromMinutes(seedTime.Value); + } + + return seedConfig; + } + + private SeedCriteriaSettings FetchSeedCriteria(int indexerId) + { try { - var indexer = _indexerFactory.Get(remoteAlbum.Release.IndexerId); + var indexer = _indexerFactory.Get(indexerId); var torrentIndexerSettings = indexer.Settings as ITorrentIndexerSettings; - if (torrentIndexerSettings != null && torrentIndexerSettings.SeedCriteria != null) - { - var seedConfig = new TorrentSeedConfiguration - { - Ratio = torrentIndexerSettings.SeedCriteria.SeedRatio - }; - - var seedTime = remoteAlbum.ParsedAlbumInfo.Discography ? torrentIndexerSettings.SeedCriteria.DiscographySeedTime : torrentIndexerSettings.SeedCriteria.SeedTime; - if (seedTime.HasValue) - { - seedConfig.SeedTime = TimeSpan.FromMinutes(seedTime.Value); - } - - return seedConfig; - } + return torrentIndexerSettings?.SeedCriteria; } catch (ModelNotFoundException) { return null; } + } - return null; + public void Handle(ProviderUpdatedEvent message) + { + _cache.Clear(); } } } diff --git a/src/NzbDrone.Core/MediaFiles/DownloadedAlbumsCommandService.cs b/src/NzbDrone.Core/MediaFiles/DownloadedAlbumsCommandService.cs index 0fa97b9fa..470061340 100644 --- a/src/NzbDrone.Core/MediaFiles/DownloadedAlbumsCommandService.cs +++ b/src/NzbDrone.Core/MediaFiles/DownloadedAlbumsCommandService.cs @@ -4,6 +4,7 @@ using System.Linq; using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; +using NzbDrone.Core.Download; using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.MediaFiles.Commands; using NzbDrone.Core.MediaFiles.TrackImport; @@ -16,16 +17,19 @@ namespace NzbDrone.Core.MediaFiles private readonly IDownloadedTracksImportService _downloadedTracksImportService; private readonly ITrackedDownloadService _trackedDownloadService; private readonly IDiskProvider _diskProvider; + private readonly ICompletedDownloadService _completedDownloadService; private readonly Logger _logger; public DownloadedAlbumsCommandService(IDownloadedTracksImportService downloadedTracksImportService, ITrackedDownloadService trackedDownloadService, IDiskProvider diskProvider, + ICompletedDownloadService completedDownloadService, Logger logger) { _downloadedTracksImportService = downloadedTracksImportService; _trackedDownloadService = trackedDownloadService; _diskProvider = diskProvider; + _completedDownloadService = completedDownloadService; _logger = logger; } @@ -45,14 +49,14 @@ namespace NzbDrone.Core.MediaFiles { _logger.Debug("External directory scan request for known download {0}. [{1}]", message.DownloadClientId, message.Path); - return _downloadedTracksImportService.ProcessPath(message.Path, message.ImportMode, trackedDownload.RemoteAlbum.Artist, trackedDownload.DownloadItem); - } - else - { - _logger.Warn("External directory scan request for unknown download {0}, attempting normal import. [{1}]", message.DownloadClientId, message.Path); + var importResults = _downloadedTracksImportService.ProcessPath(message.Path, message.ImportMode, trackedDownload.RemoteAlbum.Artist, trackedDownload.DownloadItem); - return _downloadedTracksImportService.ProcessPath(message.Path, message.ImportMode); + _completedDownloadService.VerifyImport(trackedDownload, importResults); + + return importResults; } + + _logger.Warn("External directory scan request for unknown download {0}, attempting normal import. [{1}]", message.DownloadClientId, message.Path); } return _downloadedTracksImportService.ProcessPath(message.Path, message.ImportMode); diff --git a/src/NzbDrone.Core/MediaFiles/Events/AlbumImportedEvent.cs b/src/NzbDrone.Core/MediaFiles/Events/AlbumImportedEvent.cs index 02978c2c9..84e0635e9 100644 --- a/src/NzbDrone.Core/MediaFiles/Events/AlbumImportedEvent.cs +++ b/src/NzbDrone.Core/MediaFiles/Events/AlbumImportedEvent.cs @@ -13,7 +13,7 @@ namespace NzbDrone.Core.MediaFiles.Events public List ImportedTracks { get; private set; } public List OldFiles { get; private set; } public bool NewDownload { get; private set; } - public string DownloadClient { get; private set; } + public DownloadClientItemClientInfo DownloadClientInfo { get; set; } public string DownloadId { get; private set; } public AlbumImportedEvent(Artist artist, Album album, AlbumRelease release, List importedTracks, List oldFiles, bool newDownload, DownloadClientItem downloadClientItem) @@ -27,7 +27,7 @@ namespace NzbDrone.Core.MediaFiles.Events if (downloadClientItem != null) { - DownloadClient = downloadClientItem.DownloadClientInfo.Name; + DownloadClientInfo = downloadClientItem.DownloadClientInfo; DownloadId = downloadClientItem.DownloadId; } } diff --git a/src/NzbDrone.Core/MediaFiles/Events/TrackImportFailedEvent.cs b/src/NzbDrone.Core/MediaFiles/Events/TrackImportFailedEvent.cs index 6f8e27dfa..eed1efebe 100644 --- a/src/NzbDrone.Core/MediaFiles/Events/TrackImportFailedEvent.cs +++ b/src/NzbDrone.Core/MediaFiles/Events/TrackImportFailedEvent.cs @@ -10,7 +10,7 @@ namespace NzbDrone.Core.MediaFiles.Events public Exception Exception { get; set; } public LocalTrack TrackInfo { get; } public bool NewDownload { get; } - public string DownloadClient { get; } + public DownloadClientItemClientInfo DownloadClientInfo { get; set; } public string DownloadId { get; } public TrackImportFailedEvent(Exception exception, LocalTrack trackInfo, bool newDownload, DownloadClientItem downloadClientItem) @@ -21,7 +21,7 @@ namespace NzbDrone.Core.MediaFiles.Events if (downloadClientItem != null) { - DownloadClient = downloadClientItem.DownloadClientInfo.Name; + DownloadClientInfo = downloadClientItem.DownloadClientInfo; DownloadId = downloadClientItem.DownloadId; } } diff --git a/src/NzbDrone.Core/MediaFiles/Events/TrackImportedEvent.cs b/src/NzbDrone.Core/MediaFiles/Events/TrackImportedEvent.cs index 845788685..fc12ee48c 100644 --- a/src/NzbDrone.Core/MediaFiles/Events/TrackImportedEvent.cs +++ b/src/NzbDrone.Core/MediaFiles/Events/TrackImportedEvent.cs @@ -11,7 +11,7 @@ namespace NzbDrone.Core.MediaFiles.Events public TrackFile ImportedTrack { get; private set; } public List OldFiles { get; private set; } public bool NewDownload { get; private set; } - public string DownloadClient { get; private set; } + public DownloadClientItemClientInfo DownloadClientInfo { get; set; } public string DownloadId { get; private set; } public TrackImportedEvent(LocalTrack trackInfo, TrackFile importedTrack, List oldFiles, bool newDownload, DownloadClientItem downloadClientItem) @@ -23,7 +23,7 @@ namespace NzbDrone.Core.MediaFiles.Events if (downloadClientItem != null) { - DownloadClient = downloadClientItem.DownloadClientInfo.Name; + DownloadClientInfo = downloadClientItem.DownloadClientInfo; DownloadId = downloadClientItem.DownloadId; } } diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/Specifications/AlreadyImportedSpecification.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/Specifications/AlreadyImportedSpecification.cs index 3f564ea7f..f31c94497 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/Specifications/AlreadyImportedSpecification.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/Specifications/AlreadyImportedSpecification.cs @@ -40,16 +40,18 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications } var albumHistory = _historyService.GetByAlbum(albumRelease.AlbumId, null); - var lastImported = albumHistory.FirstOrDefault(h => h.EventType == HistoryEventType.DownloadImported); - var lastGrabbed = albumHistory.FirstOrDefault(h => h.EventType == HistoryEventType.Grabbed); + var lastImported = albumHistory.FirstOrDefault(h => h.EventType == EntityHistoryEventType.DownloadImported); + var lastGrabbed = albumHistory.FirstOrDefault(h => h.EventType == EntityHistoryEventType.Grabbed); if (lastImported == null) { + _logger.Trace("Track file has not been imported"); return Decision.Accept(); } if (lastGrabbed != null && lastGrabbed.Date.After(lastImported.Date)) { + _logger.Trace("Track file was grabbed again after importing"); return Decision.Accept(); } diff --git a/src/NzbDrone.Core/Notifications/NotificationService.cs b/src/NzbDrone.Core/Notifications/NotificationService.cs index c71a8376b..2c98de1b3 100644 --- a/src/NzbDrone.Core/Notifications/NotificationService.cs +++ b/src/NzbDrone.Core/Notifications/NotificationService.cs @@ -155,7 +155,7 @@ namespace NzbDrone.Core.Notifications Artist = message.Artist, Album = message.Album, Release = message.AlbumRelease, - DownloadClient = message.DownloadClient, + DownloadClient = message.DownloadClientInfo?.Name, DownloadId = message.DownloadId, TrackFiles = message.ImportedTracks, OldFiles = message.OldFiles, diff --git a/src/NzbDrone.Core/Queue/QueueService.cs b/src/NzbDrone.Core/Queue/QueueService.cs index 159168800..27dd31dbd 100644 --- a/src/NzbDrone.Core/Queue/QueueService.cs +++ b/src/NzbDrone.Core/Queue/QueueService.cs @@ -63,7 +63,7 @@ namespace NzbDrone.Core.Queue private Queue MapQueueItem(TrackedDownload trackedDownload, Album album) { bool downloadForced = false; - var history = _historyService.Find(trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed).FirstOrDefault(); + var history = _historyService.Find(trackedDownload.DownloadItem.DownloadId, EntityHistoryEventType.Grabbed).FirstOrDefault(); if (history != null && history.Data.ContainsKey("downloadForced")) { downloadForced = bool.Parse(history.Data["downloadForced"]);