diff --git a/src/Lidarr.Api.V1/Albums/AlbumLookupModule.cs b/src/Lidarr.Api.V1/Albums/AlbumLookupModule.cs new file mode 100644 index 000000000..8dc77fd5b --- /dev/null +++ b/src/Lidarr.Api.V1/Albums/AlbumLookupModule.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Nancy; +using NzbDrone.Core.MediaCover; +using NzbDrone.Core.MetadataSource; +using Lidarr.Http; +using Lidarr.Http.Extensions; + +namespace Lidarr.Api.V1.Albums +{ + public class AlbumLookupModule : LidarrRestModule + { + private readonly ISearchForNewAlbum _searchProxy; + + public AlbumLookupModule(ISearchForNewAlbum searchProxy) + : base("/album/lookup") + { + _searchProxy = searchProxy; + Get["/"] = x => Search(); + } + + + private Response Search() + { + var searchResults = _searchProxy.SearchForNewAlbum((string)Request.Query.term, null); + return MapToResource(searchResults).AsResponse(); + } + + + private static IEnumerable MapToResource(IEnumerable albums) + { + foreach (var currentAlbum in albums) + { + var resource = currentAlbum.ToResource(); + var cover = currentAlbum.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Cover); + if (cover != null) + { + resource.RemoteCover = cover.Url; + } + + yield return resource; + } + } + } +} diff --git a/src/Lidarr.Api.V1/Albums/AlbumResource.cs b/src/Lidarr.Api.V1/Albums/AlbumResource.cs index a858dec1b..215ec41ee 100644 --- a/src/Lidarr.Api.V1/Albums/AlbumResource.cs +++ b/src/Lidarr.Api.V1/Albums/AlbumResource.cs @@ -42,6 +42,8 @@ namespace Lidarr.Api.V1.Albums public List Images { get; set; } public AlbumStatisticsResource Statistics { get; set; } + public string RemoteCover { get; set; } + //Hiding this so people don't think its usable (only used to set the initial state) [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public bool Grabbed { get; set; } @@ -72,6 +74,7 @@ namespace Lidarr.Api.V1.Albums Media = model.Media.ToResource(), CurrentRelease = model.CurrentRelease, Releases = model.Releases.ToResource(), + Artist = model.Artist.ToResource() }; } diff --git a/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj b/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj index 746a65e18..954b6e39f 100644 --- a/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj +++ b/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj @@ -86,6 +86,7 @@ + diff --git a/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxyFixture.cs b/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxyFixture.cs index 4c94d22ff..3be71bbb9 100644 --- a/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxyFixture.cs +++ b/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxyFixture.cs @@ -4,7 +4,6 @@ using System.Linq; using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Exceptions; -using NzbDrone.Core.MediaCover; using NzbDrone.Core.MetadataSource.SkyHook; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Music; @@ -47,6 +46,10 @@ namespace NzbDrone.Core.Test.MetadataSource.SkyHook Mocker.GetMock() .Setup(s => s.Get(It.IsAny())) .Returns(_metadataProfile); + + Mocker.GetMock() + .Setup(s => s.Exists(It.IsAny())) + .Returns(true); } [TestCase("f59c5520-5f46-4d2c-b2c4-822eabf53419", "Linkin Park")] @@ -60,11 +63,53 @@ namespace NzbDrone.Core.Test.MetadataSource.SkyHook details.Item1.Name.Should().Be(name); } + + [TestCase("12fa3845-7c62-36e5-a8da-8be137155a72", null, "Hysteria")] + public void should_be_able_to_get_album_detail(string mbId, string release, string name) + { + var details = Subject.GetAlbumInfo(mbId, release); + + ValidateAlbums(new List {details.Item1}); + + details.Item1.Title.Should().Be(name); + } + + [TestCase("12fa3845-7c62-36e5-a8da-8be137155a72", "3c186b52-ca73-46a3-a8e6-04559bfbb581",1, 13, "Hysteria")] + [TestCase("12fa3845-7c62-36e5-a8da-8be137155a72", "dee9ca6f-4f84-4359-82a9-b75a37ffc316",2, 27,"Hysteria")] + public void should_be_able_to_get_album_detail_with_release(string mbId, string release, int mediaCount, int trackCount, string name) + { + var details = Subject.GetAlbumInfo(mbId, release); + + ValidateAlbums(new List { details.Item1 }); + + details.Item1.Media.Count.Should().Be(mediaCount); + details.Item2.Count.Should().Be(trackCount); + + details.Item1.Title.Should().Be(name); + } [Test] public void getting_details_of_invalid_artist() { - Assert.Throws(() => Subject.GetArtistInfo("aaaaaa-aaa-aaaa-aaaa", 1)); + Assert.Throws(() => Subject.GetArtistInfo("66c66aaa-6e2f-4930-8610-912e24c63ed1", 1)); + } + + [Test] + public void getting_details_of_invalid_guid_for_artist() + { + Assert.Throws(() => Subject.GetArtistInfo("66c66aaa-6e2f-4930-aaaaaa", 1)); + } + + [Test] + public void getting_details_of_invalid_album() + { + Assert.Throws(() => Subject.GetAlbumInfo("66c66aaa-6e2f-4930-8610-912e24c63ed1",null)); + } + + [Test] + public void getting_details_of_invalid_guid_for_album() + { + Assert.Throws(() => Subject.GetAlbumInfo("66c66aaa-6e2f-4930-aaaaaa", null)); } private void ValidateArtist(Artist artist) @@ -75,7 +120,6 @@ namespace NzbDrone.Core.Test.MetadataSource.SkyHook artist.SortName.Should().Be(Parser.Parser.NormalizeTitle(artist.Name)); artist.Overview.Should().NotBeNullOrWhiteSpace(); artist.Images.Should().NotBeEmpty(); - //series.TvRageId.Should().BeGreaterThan(0); artist.ForeignArtistId.Should().NotBeNullOrWhiteSpace(); } @@ -83,9 +127,9 @@ namespace NzbDrone.Core.Test.MetadataSource.SkyHook { albums.Should().NotBeEmpty(); - foreach (var episode in albums) + foreach (var album in albums) { - ValidateAlbum(episode); + ValidateAlbum(album); //if atleast one album has title it means parse it working. albums.Should().Contain(c => !string.IsNullOrWhiteSpace(c.Title)); diff --git a/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxySearchFixture.cs b/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxySearchFixture.cs index 22254e48a..a6863d59b 100644 --- a/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxySearchFixture.cs +++ b/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxySearchFixture.cs @@ -59,7 +59,7 @@ namespace NzbDrone.Core.Test.MetadataSource.SkyHook [TestCase("lidarr:f59c5520-5f46-4d2c-b2c4-822eabf53419", "Linkin Park")] [TestCase("lidarrid:f59c5520-5f46-4d2c-b2c4-822eabf53419", "Linkin Park")] [TestCase("lidarrid: f59c5520-5f46-4d2c-b2c4-822eabf53419 ", "Linkin Park")] - public void successful_search(string title, string expected) + public void successful_artist_search(string title, string expected) { var result = Subject.SearchForNewArtist(title); @@ -70,13 +70,30 @@ namespace NzbDrone.Core.Test.MetadataSource.SkyHook ExceptionVerification.IgnoreWarns(); } + + [TestCase("Evolve", "Imagine Dragons", "Evolve")] + [TestCase("Hysteria", null, "Hysteria")] + [TestCase("lidarr:d77df681-b779-3d6d-b66a-3bfd15985e3e", null, "Pyromania")] + [TestCase("lidarr: d77df681-b779-3d6d-b66a-3bfd15985e3e", null, "Pyromania")] + [TestCase("lidarrid:d77df681-b779-3d6d-b66a-3bfd15985e3e", null, "Pyromania")] + public void successful_album_search(string title, string artist, string expected) + { + var result = Subject.SearchForNewAlbum(title, artist); + + result.Should().NotBeEmpty(); + + result[0].Title.Should().Be(expected); + + ExceptionVerification.IgnoreWarns(); + } + [TestCase("lidarrid:")] [TestCase("lidarrid: 99999999999999999999")] [TestCase("lidarrid: 0")] [TestCase("lidarrid: -12")] [TestCase("lidarrid:289578")] [TestCase("adjalkwdjkalwdjklawjdlKAJD;EF")] - public void no_search_result(string term) + public void no_artist_search_result(string term) { var result = Subject.SearchForNewArtist(term); result.Should().BeEmpty(); diff --git a/src/NzbDrone.Core.Test/MusicTests/AddAlbumFixture.cs b/src/NzbDrone.Core.Test/MusicTests/AddAlbumFixture.cs new file mode 100644 index 000000000..b19f1f9d2 --- /dev/null +++ b/src/NzbDrone.Core.Test/MusicTests/AddAlbumFixture.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.IO; +using FizzWare.NBuilder; +using FluentAssertions; +using FluentValidation; +using FluentValidation.Results; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Exceptions; +using NzbDrone.Core.MetadataSource; +using NzbDrone.Core.Music; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.MusicTests +{ + [TestFixture] + public class AddAlbumFixture : CoreTest + { + private Album _fakeAlbum; + + [SetUp] + public void Setup() + { + _fakeAlbum = Builder + .CreateNew() + .Build(); + } + + private void GivenValidAlbum(string lidarrId) + { + Mocker.GetMock() + .Setup(s => s.GetAlbumInfo(lidarrId, It.IsAny())) + .Returns(new Tuple>(_fakeAlbum, new List())); + } + + [Test] + public void should_be_able_to_add_an_album_without_passing_in_title() + { + var newAlbum = new Album + { + ForeignAlbumId = "ce09ea31-3d4a-4487-a797-e315175457a0" + }; + + GivenValidAlbum(newAlbum.ForeignAlbumId); + + var album = Subject.AddAlbum(newAlbum); + + album.Title.Should().Be(_fakeAlbum.Title); + } + + [Test] + public void should_throw_if_album_cannot_be_found() + { + var newAlbum = new Album + { + ForeignAlbumId = "ce09ea31-3d4a-4487-a797-e315175457a0" + }; + + Mocker.GetMock() + .Setup(s => s.GetAlbumInfo(newAlbum.ForeignAlbumId, It.IsAny())) + .Throws(new AlbumNotFoundException(newAlbum.ForeignAlbumId)); + + Assert.Throws(() => Subject.AddAlbum(newAlbum)); + + ExceptionVerification.ExpectedErrors(1); + } + } +} diff --git a/src/NzbDrone.Core.Test/MusicTests/AlbumMonitoredServiceTests/AlbumMonitoredServiceFixture.cs b/src/NzbDrone.Core.Test/MusicTests/AlbumMonitoredServiceTests/AlbumMonitoredServiceFixture.cs new file mode 100644 index 000000000..efd2c040b --- /dev/null +++ b/src/NzbDrone.Core.Test/MusicTests/AlbumMonitoredServiceTests/AlbumMonitoredServiceFixture.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FizzWare.NBuilder; +using Moq; +using NUnit.Framework; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Music; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.MusicTests.AlbumMonitoredServiceTests +{ + [TestFixture] + public class SetAlbumMontitoredFixture : CoreTest + { + private Artist _artist; + private List _albums; + + [SetUp] + public void Setup() + { + const int albums = 4; + + _artist = Builder.CreateNew() + .Build(); + + _albums = Builder.CreateListOfSize(albums) + .All() + .With(e => e.Monitored = true) + .With(e => e.ReleaseDate = DateTime.UtcNow.AddDays(-7)) + //Future + .TheFirst(1) + .With(e => e.ReleaseDate = DateTime.UtcNow.AddDays(7)) + //Future/TBA + .TheNext(1) + .With(e => e.ReleaseDate = null) + .Build() + .ToList(); + + Mocker.GetMock() + .Setup(s => s.GetAlbumsByArtist(It.IsAny())) + .Returns(_albums); + + Mocker.GetMock() + .Setup(s => s.GetTracksByAlbum(It.IsAny())) + .Returns(new List()); + } + + [Test] + public void should_be_able_to_monitor_artist_without_changing_albums() + { + Subject.SetAlbumMonitoredStatus(_artist, null); + + Mocker.GetMock() + .Verify(v => v.UpdateArtist(It.IsAny()), Times.Once()); + + Mocker.GetMock() + .Verify(v => v.UpdateAlbums(It.IsAny>()), Times.Never()); + } + + [Test] + public void should_be_able_to_monitor_albums_when_passed_in_artist() + { + _artist.Albums.Add(_albums.First()); + + Subject.SetAlbumMonitoredStatus(_artist, new MonitoringOptions { Monitored = true }); + + Mocker.GetMock() + .Verify(v => v.UpdateArtist(It.IsAny()), Times.Once()); + + VerifyMonitored(e => e.ForeignAlbumId == _albums.First().ForeignAlbumId); + VerifyNotMonitored(e => e.ForeignAlbumId != _albums.First().ForeignAlbumId); + } + + [Test] + public void should_be_able_to_monitor_all_albums() + { + Subject.SetAlbumMonitoredStatus(_artist, new MonitoringOptions{Monitored = true}); + + Mocker.GetMock() + .Verify(v => v.UpdateAlbums(It.Is>(l => l.All(e => e.Monitored)))); + } + + [Test] + [Ignore("Not Implemented Yet")] + public void should_be_able_to_monitor_new_albums_only() + { + var monitoringOptions = new MonitoringOptions + { + IgnoreAlbumsWithFiles = true, + IgnoreAlbumsWithoutFiles = true + }; + + Subject.SetAlbumMonitoredStatus(_artist, monitoringOptions); + + VerifyMonitored(e => e.ReleaseDate.HasValue && e.ReleaseDate.Value.After(DateTime.UtcNow)); + VerifyMonitored(e => !e.ReleaseDate.HasValue); + VerifyNotMonitored(e => e.ReleaseDate.HasValue && e.ReleaseDate.Value.Before(DateTime.UtcNow)); + } + + private void VerifyMonitored(Func predicate) + { + Mocker.GetMock() + .Verify(v => v.UpdateAlbums(It.Is>(l => l.Where(predicate).All(e => e.Monitored)))); + } + + private void VerifyNotMonitored(Func predicate) + { + Mocker.GetMock() + .Verify(v => v.UpdateAlbums(It.Is>(l => l.Where(predicate).All(e => !e.Monitored)))); + } + } +} diff --git a/src/NzbDrone.Core.Test/MusicTests/ArtistRepositoryTests/ArtistRepositoryFixture.cs b/src/NzbDrone.Core.Test/MusicTests/ArtistRepositoryTests/ArtistRepositoryFixture.cs index 2f01d0f92..58a9fbefb 100644 --- a/src/NzbDrone.Core.Test/MusicTests/ArtistRepositoryTests/ArtistRepositoryFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/ArtistRepositoryTests/ArtistRepositoryFixture.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; @@ -8,6 +9,7 @@ using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Music; using NzbDrone.Core.Profiles.Languages; +using NzbDrone.Core.Profiles.Metadata; namespace NzbDrone.Core.Test.MusicTests.ArtistRepositoryTests { @@ -16,7 +18,7 @@ namespace NzbDrone.Core.Test.MusicTests.ArtistRepositoryTests public class ArtistRepositoryFixture : DbTest { [Test] - public void should_lazyload_quality_profile() + public void should_lazyload_profiles() { var profile = new Profile { @@ -33,20 +35,29 @@ namespace NzbDrone.Core.Test.MusicTests.ArtistRepositoryTests Cutoff = Language.English }; + var metaProfile = new MetadataProfile + { + Name = "TestProfile", + PrimaryAlbumTypes = new List(), + SecondaryAlbumTypes = new List() + }; + Mocker.Resolve().Insert(profile); Mocker.Resolve().Insert(langProfile); + Mocker.Resolve().Insert(metaProfile); - var series = Builder.CreateNew().BuildNew(); - series.ProfileId = profile.Id; - series.LanguageProfileId = langProfile.Id; + var artist = Builder.CreateNew().BuildNew(); + artist.ProfileId = profile.Id; + artist.LanguageProfileId = langProfile.Id; + artist.MetadataProfileId = metaProfile.Id; - Subject.Insert(series); + Subject.Insert(artist); StoredModel.Profile.Should().NotBeNull(); StoredModel.LanguageProfile.Should().NotBeNull(); - + StoredModel.MetadataProfile.Should().NotBeNull(); } } diff --git a/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumServiceFixture.cs b/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumServiceFixture.cs new file mode 100644 index 000000000..4629987fc --- /dev/null +++ b/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumServiceFixture.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FizzWare.NBuilder; +using Moq; +using NUnit.Framework; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Exceptions; +using NzbDrone.Core.MetadataSource; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Music; +using NzbDrone.Core.Music.Commands; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.MusicTests +{ + [TestFixture] + public class RefreshAlbumServiceFixture : CoreTest + { + private Artist _artist; + private List _albums; + + [SetUp] + public void Setup() + { + var album1 = Builder.CreateNew() + .With(s => s.ForeignAlbumId = "1") + .Build(); + + _albums = new List{album1}; + + _artist = Builder.CreateNew() + .With(s => s.Albums = new List + { + album1 + }) + .Build(); + + Mocker.GetMock() + .Setup(s => s.GetArtist(_artist.Id)) + .Returns(_artist); + + Mocker.GetMock() + .Setup(s => s.GetAlbumInfo(It.IsAny(), It.IsAny())) + .Callback(() => { throw new AlbumNotFoundException(album1.ForeignAlbumId); }); + } + + private void GivenNewAlbumInfo(Album album) + { + Mocker.GetMock() + .Setup(s => s.GetAlbumInfo(_albums.First().ForeignAlbumId, It.IsAny())) + .Returns(new Tuple>(album, new List())); + } + + [Test] + public void should_log_error_if_musicbrainz_id_not_found() + { + Subject.RefreshAlbumInfo(_albums); + + Mocker.GetMock() + .Verify(v => v.UpdateMany(It.IsAny>()), Times.Never()); + + ExceptionVerification.ExpectedErrors(1); + } + + [Test] + public void should_update_if_musicbrainz_id_changed() + { + var newAlbumInfo = _albums.FirstOrDefault().JsonClone(); + newAlbumInfo.ForeignAlbumId = _albums.First().ForeignAlbumId + 1; + + GivenNewAlbumInfo(newAlbumInfo); + + Subject.RefreshAlbumInfo(_albums); + + Mocker.GetMock() + .Verify(v => v.UpdateMany(It.Is>(s => s.First().ForeignAlbumId == newAlbumInfo.ForeignAlbumId))); + + ExceptionVerification.ExpectedWarns(1); + } + } +} diff --git a/src/NzbDrone.Core.Test/MusicTests/RefreshArtistServiceFixture.cs b/src/NzbDrone.Core.Test/MusicTests/RefreshArtistServiceFixture.cs index be22ea634..c456c83a4 100644 --- a/src/NzbDrone.Core.Test/MusicTests/RefreshArtistServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/RefreshArtistServiceFixture.cs @@ -18,24 +18,33 @@ namespace NzbDrone.Core.Test.MusicTests public class RefreshArtistServiceFixture : CoreTest { private Artist _artist; + private Album _album1; + private Album _album2; + private List _albums; [SetUp] public void Setup() { - var season1 = Builder.CreateNew() - .With(s => s.ForeignAlbumId = "1") - .Build(); + _album1 = Builder.CreateNew() + .With(s => s.ForeignAlbumId = "1") + .Build(); + + _album2 = Builder.CreateNew() + .With(s => s.ForeignAlbumId = "2") + .Build(); + + _albums = new List {_album1, _album2}; _artist = Builder.CreateNew() - .With(s => s.Albums = new List - { - season1 - }) .Build(); Mocker.GetMock() .Setup(s => s.GetArtist(_artist.Id)) .Returns(_artist); + + Mocker.GetMock() + .Setup(s => s.GetAlbumsByArtist(It.IsAny())) + .Returns(new List()); Mocker.GetMock() .Setup(s => s.GetArtistInfo(It.IsAny(), It.IsAny())) @@ -46,25 +55,7 @@ namespace NzbDrone.Core.Test.MusicTests { Mocker.GetMock() .Setup(s => s.GetArtistInfo(_artist.ForeignArtistId, _artist.MetadataProfileId)) - .Returns(new Tuple>(artist, new List())); - } - - // TODO: Re-Write album verification tests - [Test] - [Ignore("This test needs to be re-written as we no longer store albums in artist table or object")] - public void should_monitor_new_albums_automatically() - { - var newArtistInfo = _artist.JsonClone(); - newArtistInfo.Albums.Add(Builder.CreateNew() - .With(s => s.ForeignAlbumId = "2") - .Build()); - - GivenNewArtistInfo(newArtistInfo); - - Subject.Execute(new RefreshArtistCommand(_artist.Id)); - - Mocker.GetMock() - .Verify(v => v.UpdateArtist(It.Is(s => s.Albums.Count == 2 && s.Albums.Single(season => season.ForeignAlbumId == "2").Monitored == true))); + .Returns(new Tuple>(artist, _albums)); } [Test] diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index cc9023d6d..fa1d16aca 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -284,10 +284,13 @@ + + + diff --git a/src/NzbDrone.Core/MetadataSource/ISearchForNewAlbum.cs b/src/NzbDrone.Core/MetadataSource/ISearchForNewAlbum.cs index 803c09cec..a3e6ebc11 100644 --- a/src/NzbDrone.Core/MetadataSource/ISearchForNewAlbum.cs +++ b/src/NzbDrone.Core/MetadataSource/ISearchForNewAlbum.cs @@ -6,6 +6,6 @@ namespace NzbDrone.Core.MetadataSource { public interface ISearchForNewAlbum { - List SearchForNewAlbum(string title, string artist, DateTime releaseDate); + List SearchForNewAlbum(string title, string artist); } } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index 2537e5db1..acfaef35e 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -188,7 +188,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook } } - public List SearchForNewAlbum(string title, string artist, DateTime releaseDate) + public List SearchForNewAlbum(string title, string artist) { try { @@ -223,7 +223,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook .SetSegment("route", "search") .AddQueryParam("type", "album") .AddQueryParam("query", title.ToLower().Trim()) - .AddQueryParam("artist", artist.ToLower().Trim()) + .AddQueryParam("artist", artist.IsNotNullOrWhiteSpace() ? artist.ToLower().Trim() : string.Empty) .Build(); diff --git a/src/NzbDrone.Core/Music/AddAlbumService.cs b/src/NzbDrone.Core/Music/AddAlbumService.cs new file mode 100644 index 000000000..e4c831c11 --- /dev/null +++ b/src/NzbDrone.Core/Music/AddAlbumService.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FluentValidation; +using FluentValidation.Results; +using NLog; +using NzbDrone.Common.EnsureThat; +using NzbDrone.Core.Exceptions; +using NzbDrone.Core.MetadataSource; + +namespace NzbDrone.Core.Music +{ + public interface IAddAlbumService + { + Album AddAlbum(Album newAlbum); + List AddAlbums(List newAlbums); + } + + public class AddAlbumService : IAddAlbumService + { + private readonly IAlbumService _albumService; + private readonly IProvideAlbumInfo _albumInfo; + private readonly IRefreshTrackService _refreshTrackService; + private readonly Logger _logger; + + public AddAlbumService(IAlbumService albumService, + IProvideAlbumInfo albumInfo, + IRefreshTrackService refreshTrackService, + Logger logger) + { + _albumService = albumService; + _albumInfo = albumInfo; + _refreshTrackService = refreshTrackService; + _logger = logger; + } + + public Album AddAlbum(Album newAlbum) + { + Ensure.That(newAlbum, () => newAlbum).IsNotNull(); + + var tuple = AddSkyhookData(newAlbum); + newAlbum = tuple.Item1; + _refreshTrackService.RefreshTrackInfo(newAlbum, tuple.Item2); + _logger.Info("Adding Album {0}", newAlbum); + _albumService.AddAlbum(newAlbum); + + return newAlbum; + } + + public List AddAlbums(List newAlbums) + { + var added = DateTime.UtcNow; + var albumsToAdd = new List(); + + foreach (var newAlbum in newAlbums) + { + var tuple = AddSkyhookData(newAlbum); + var album = tuple.Item1; + album.Added = added; + album.LastInfoSync = added; + album = _albumService.AddAlbum(album); + _refreshTrackService.RefreshTrackInfo(album,tuple.Item2); + albumsToAdd.Add(album); + } + + return albumsToAdd; + } + + private Tuple> AddSkyhookData(Album newAlbum) + { + Tuple> tuple; + + try + { + tuple = _albumInfo.GetAlbumInfo(newAlbum.ForeignAlbumId, null); + } + catch (AlbumNotFoundException) + { + _logger.Error("LidarrId {1} was not found, it may have been removed from Lidarr.", newAlbum.ForeignAlbumId); + + throw new ValidationException(new List + { + new ValidationFailure("MusicBrainzId", "An album with this ID was not found", newAlbum.ForeignAlbumId) + }); + } + + tuple.Item1.ArtistId = newAlbum.ArtistId; + tuple.Item1.Monitored = newAlbum.Monitored; + tuple.Item1.ProfileId = newAlbum.ProfileId; + tuple.Item1.Duration = tuple.Item2.Sum(track => track.Duration); + + return tuple; + } + } +} diff --git a/src/NzbDrone.Core/Music/Album.cs b/src/NzbDrone.Core/Music/Album.cs index 13ea3f1c6..c29daf2b8 100644 --- a/src/NzbDrone.Core/Music/Album.cs +++ b/src/NzbDrone.Core/Music/Album.cs @@ -13,6 +13,7 @@ namespace NzbDrone.Core.Music Images = new List(); Media = new List(); Releases = new List(); + CurrentRelease = new AlbumRelease(); } public const string RELEASE_DATE_FORMAT = "yyyy-MM-dd"; diff --git a/src/NzbDrone.Core/Music/AlbumMonitoredService.cs b/src/NzbDrone.Core/Music/AlbumMonitoredService.cs index dea43f810..a33c78ac2 100644 --- a/src/NzbDrone.Core/Music/AlbumMonitoredService.cs +++ b/src/NzbDrone.Core/Music/AlbumMonitoredService.cs @@ -8,7 +8,7 @@ namespace NzbDrone.Core.Music { public interface IAlbumMonitoredService { - void SetAlbumMonitoredStatus(Artist album, MonitoringOptions monitoringOptions); + void SetAlbumMonitoredStatus(Artist artist, MonitoringOptions monitoringOptions); } public class AlbumMonitoredService : IAlbumMonitoredService @@ -34,7 +34,19 @@ namespace NzbDrone.Core.Music var albums = _albumService.GetAlbumsByArtist(artist.Id); - ToggleAlbumsMonitoredState(albums, monitoringOptions.Monitored); + var monitoredAlbums = artist.Albums; + + if (monitoredAlbums != null) + { + ToggleAlbumsMonitoredState( + albums.Where(s => monitoredAlbums.Any(t => t.ForeignAlbumId == s.ForeignAlbumId)), true); + ToggleAlbumsMonitoredState( + albums.Where(s => monitoredAlbums.Any(t => t.ForeignAlbumId != s.ForeignAlbumId)), false); + } + else + { + ToggleAlbumsMonitoredState(albums, monitoringOptions.Monitored); + } //TODO Add Other Options for Future/Exisitng/Missing Once we have a good way to check for Album Related Files. diff --git a/src/NzbDrone.Core/Music/AlbumService.cs b/src/NzbDrone.Core/Music/AlbumService.cs index a7384da70..244b32928 100644 --- a/src/NzbDrone.Core/Music/AlbumService.cs +++ b/src/NzbDrone.Core/Music/AlbumService.cs @@ -197,7 +197,7 @@ namespace NzbDrone.Core.Music public List UpdateAlbums(List album) { - _logger.Debug("Updating {0} album", album.Count); + _logger.Debug("Updating {0} albums", album.Count); _albumRepository.UpdateMany(album); _logger.Debug("{0} albums updated", album.Count); diff --git a/src/NzbDrone.Core/Music/Events/AlbumsAddedEvent.cs b/src/NzbDrone.Core/Music/Events/AlbumsAddedEvent.cs deleted file mode 100644 index b782bfe82..000000000 --- a/src/NzbDrone.Core/Music/Events/AlbumsAddedEvent.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; -using NzbDrone.Common.Messaging; - -namespace NzbDrone.Core.Music.Events -{ - public class AlbumsAddedEvent : IEvent - { - public List AlbumIds { get; private set; } - - public AlbumsAddedEvent(List albumIds) - { - AlbumIds = albumIds; - } - } -} diff --git a/src/NzbDrone.Core/Music/RefreshAlbumService.cs b/src/NzbDrone.Core/Music/RefreshAlbumService.cs index f03ff6cc2..25a95eeac 100644 --- a/src/NzbDrone.Core/Music/RefreshAlbumService.cs +++ b/src/NzbDrone.Core/Music/RefreshAlbumService.cs @@ -8,13 +8,9 @@ using NzbDrone.Core.Organizer; using System.Linq; using System.Text; using NzbDrone.Core.MetadataSource; - using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Core.Exceptions; - - using NzbDrone.Core.Messaging.Commands; - using NzbDrone.Core.Music.Commands; @@ -23,8 +19,8 @@ namespace NzbDrone.Core.Music { public interface IRefreshAlbumService { - void RefreshAlbumInfo(Artist artist, IEnumerable remoteAlbums); void RefreshAlbumInfo(Album album); + void RefreshAlbumInfo(List albums); } public class RefreshAlbumService : IRefreshAlbumService, IExecute @@ -33,8 +29,6 @@ namespace NzbDrone.Core.Music private readonly IArtistService _artistService; private readonly IProvideAlbumInfo _albumInfo; private readonly IRefreshTrackService _refreshTrackService; - private readonly ITrackService _trackService; - private readonly IBuildFileNames _fileNameBuilder; private readonly IEventAggregator _eventAggregator; private readonly Logger _logger; @@ -42,8 +36,6 @@ namespace NzbDrone.Core.Music IArtistService artistService, IProvideAlbumInfo albumInfo, IRefreshTrackService refreshTrackService, - ITrackService trackService, - IBuildFileNames fileNameBuilder, IEventAggregator eventAggregator, Logger logger) { @@ -51,12 +43,18 @@ namespace NzbDrone.Core.Music _artistService = artistService; _albumInfo = albumInfo; _refreshTrackService = refreshTrackService; - _trackService = trackService; - _fileNameBuilder = fileNameBuilder; _eventAggregator = eventAggregator; _logger = logger; } + public void RefreshAlbumInfo(List albums) + { + foreach (var album in albums) + { + RefreshAlbumInfo(album); + } + } + public void RefreshAlbumInfo(Album album) { _logger.ProgressInfo("Updating Info for {0}", album.Title); @@ -65,7 +63,7 @@ namespace NzbDrone.Core.Music try { - tuple = _albumInfo.GetAlbumInfo(album.ForeignAlbumId, album.CurrentRelease.Id); + tuple = _albumInfo.GetAlbumInfo(album.ForeignAlbumId, album.CurrentRelease?.Id); } catch (AlbumNotFoundException) { @@ -88,7 +86,6 @@ namespace NzbDrone.Core.Music album.LastInfoSync = DateTime.UtcNow; album.CleanTitle = albumInfo.CleanTitle; album.Title = albumInfo.Title ?? "Unknown"; - album.CleanTitle = Parser.Parser.CleanArtistName(album.Title); album.AlbumType = albumInfo.AlbumType; album.SecondaryTypes = albumInfo.SecondaryTypes; album.Genres = albumInfo.Genres; @@ -98,129 +95,14 @@ namespace NzbDrone.Core.Music album.ReleaseDate = albumInfo.ReleaseDate; album.Duration = tuple.Item2.Sum(track => track.Duration); album.Releases = albumInfo.Releases; + album.CurrentRelease = albumInfo.CurrentRelease; _refreshTrackService.RefreshTrackInfo(album, tuple.Item2); - _albumService.UpdateAlbum(album); + _albumService.UpdateMany(new List{album}); } - public void RefreshAlbumInfo(Artist artist, IEnumerable remoteAlbums) - { - _logger.Info("Starting album info refresh for: {0}", artist); - var successCount = 0; - var failCount = 0; - - var existingAlbums = _albumService.GetAlbumsByArtist(artist.Id); - - var updateList = new List(); - var newList = new List(); - var dupeFreeRemoteAlbums = remoteAlbums.DistinctBy(m => new {m.ForeignAlbumId, m.ReleaseDate}).ToList(); - - foreach (var album in OrderAlbums(artist, dupeFreeRemoteAlbums)) - { - - try - { - var albumToUpdate = GetAlbumToUpdate(artist, album, existingAlbums); - - Tuple> tuple; - var albumInfo = new Album(); - - if (albumToUpdate != null) - { - tuple = _albumInfo.GetAlbumInfo(album.ForeignAlbumId, albumToUpdate.CurrentRelease?.Id); - albumInfo = tuple.Item1; - existingAlbums.Remove(albumToUpdate); - updateList.Add(albumToUpdate); - } - else - { - tuple = _albumInfo.GetAlbumInfo(album.ForeignAlbumId, null); - albumInfo = tuple.Item1; - albumToUpdate = new Album - { - Monitored = artist.Monitored, - ProfileId = artist.ProfileId, - Added = DateTime.UtcNow - }; - - albumToUpdate.ArtistId = artist.Id; - albumToUpdate.CleanTitle = albumInfo.CleanTitle; - albumToUpdate.ForeignAlbumId = albumInfo.ForeignAlbumId; - albumToUpdate.Title = albumInfo.Title ?? "Unknown"; - albumToUpdate.AlbumType = albumInfo.AlbumType; - albumToUpdate.Releases = albumInfo.Releases; - albumToUpdate.CurrentRelease = albumInfo.CurrentRelease; - - _albumService.AddAlbum(albumToUpdate); - - newList.Add(albumToUpdate); - } - - - albumToUpdate.LastInfoSync = DateTime.UtcNow; - albumToUpdate.CleanTitle = albumInfo.CleanTitle; - albumToUpdate.Title = albumInfo.Title ?? "Unknown"; - albumToUpdate.CleanTitle = Parser.Parser.CleanArtistName(albumToUpdate.Title); - albumToUpdate.AlbumType = albumInfo.AlbumType; - albumToUpdate.SecondaryTypes = albumInfo.SecondaryTypes; - albumToUpdate.Genres = albumInfo.Genres; - albumToUpdate.Media = albumInfo.Media; - albumToUpdate.Label = albumInfo.Label; - albumToUpdate.Images = albumInfo.Images; - albumToUpdate.ReleaseDate = albumInfo.ReleaseDate; - albumToUpdate.Duration = tuple.Item2.Sum(track => track.Duration); - albumToUpdate.Releases = albumInfo.Releases; - albumToUpdate.CurrentRelease = albumInfo.CurrentRelease; - - _refreshTrackService.RefreshTrackInfo(albumToUpdate, tuple.Item2); - - successCount++; - } - catch (Exception e) - { - _logger.Fatal(e, "An error has occurred while updating album info for artist {0}. {1}", artist, - album); - failCount++; - } - } - - _albumService.DeleteMany(existingAlbums); - _albumService.UpdateMany(updateList); - _albumService.UpdateMany(newList); - - _eventAggregator.PublishEvent(new AlbumInfoRefreshedEvent(artist, newList, updateList)); - - if (failCount != 0) - { - _logger.Info("Finished album refresh for artist: {0}. Successful: {1} - Failed: {2} ", - artist.Name, successCount, failCount); - } - else - { - _logger.Info("Finished album refresh for artist: {0}.", artist); - } - } - - private bool GetMonitoredStatus(Album album, IEnumerable artists) - { - var artist = artists.SingleOrDefault(c => c.Id == album.ArtistId); - return album == null || album.Monitored; - } - - - private Album GetAlbumToUpdate(Artist artist, Album album, List existingAlbums) - { - return existingAlbums.FirstOrDefault( - e => e.ForeignAlbumId == album.ForeignAlbumId /* && e.ReleaseDate == album.ReleaseDate*/); - } - - private IEnumerable OrderAlbums(Artist artist, List albums) - { - return albums.OrderBy(e => e.ForeignAlbumId).ThenBy(e => e.ReleaseDate); - } - public void Execute(RefreshAlbumCommand message) { if (message.AlbumId.HasValue) diff --git a/src/NzbDrone.Core/Music/RefreshArtistService.cs b/src/NzbDrone.Core/Music/RefreshArtistService.cs index bf2947e84..04664a4a3 100644 --- a/src/NzbDrone.Core/Music/RefreshArtistService.cs +++ b/src/NzbDrone.Core/Music/RefreshArtistService.cs @@ -20,6 +20,7 @@ namespace NzbDrone.Core.Music { private readonly IProvideArtistInfo _artistInfo; private readonly IArtistService _artistService; + private readonly IAddAlbumService _addAlbumService; private readonly IAlbumService _albumService; private readonly IRefreshAlbumService _refreshAlbumService; private readonly IRefreshTrackService _refreshTrackService; @@ -30,6 +31,7 @@ namespace NzbDrone.Core.Music public RefreshArtistService(IProvideArtistInfo artistInfo, IArtistService artistService, + IAddAlbumService addAlbumService, IAlbumService albumService, IRefreshAlbumService refreshAlbumService, IRefreshTrackService refreshTrackService, @@ -40,6 +42,7 @@ namespace NzbDrone.Core.Music { _artistInfo = artistInfo; _artistService = artistService; + _addAlbumService = addAlbumService; _albumService = albumService; _refreshAlbumService = refreshAlbumService; _refreshTrackService = refreshTrackService; @@ -94,13 +97,59 @@ namespace NzbDrone.Core.Music { _logger.Warn(e, "Couldn't update artist path for " + artist.Path); } - - _refreshAlbumService.RefreshAlbumInfo(artist, tuple.Item2); + + var remoteAlbums = tuple.Item2.DistinctBy(m => new { m.ForeignAlbumId, m.ReleaseDate }).ToList(); + + // Get list of DB current db albums for artist + var existingAlbums = _albumService.GetAlbumsByArtist(artist.Id); + + var newAlbumsList = new List(); + var updateAlbumsList = new List(); + + // Cycle thru albums + foreach (var album in remoteAlbums) + { + // Check for album in existing albums, if not set properties and add to new list + var albumToRefresh = existingAlbums.FirstOrDefault(s => s.ForeignAlbumId == album.ForeignAlbumId); + + if (albumToRefresh != null) + { + existingAlbums.Remove(albumToRefresh); + updateAlbumsList.Add(albumToRefresh); + } + else + { + newAlbumsList.Add(album); + } + } + + // Update new albums with artist info and correct monitored status + newAlbumsList = UpdateAlbums(artist, newAlbumsList); + + _artistService.UpdateArtist(artist); + + _addAlbumService.AddAlbums(newAlbumsList); + + _refreshAlbumService.RefreshAlbumInfo(updateAlbumsList); + + _albumService.DeleteMany(existingAlbums); _logger.Debug("Finished artist refresh for {0}", artist.Name); _eventAggregator.PublishEvent(new ArtistUpdatedEvent(artist)); } + private List UpdateAlbums(Artist artist, List albumsToUpdate) + { + foreach (var album in albumsToUpdate) + { + album.ArtistId = artist.Id; + album.ProfileId = artist.ProfileId; + album.Monitored = artist.Monitored; + } + + return albumsToUpdate; + } + public void Execute(RefreshArtistCommand message) { _eventAggregator.PublishEvent(new ArtistRefreshStartingEvent(message.Trigger == CommandTrigger.Manual)); diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index e894a44a4..35fc775fe 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -768,13 +768,13 @@ + -