Rework how albums are refreshed/added, add album search route (#190)

Rework how albums are refreshed/added, add album search route
This commit is contained in:
Qstick 2018-01-28 01:27:33 -05:00 committed by GitHub
parent 6ff5e6337b
commit 5551b2166a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 599 additions and 193 deletions

View File

@ -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<AlbumResource>
{
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<AlbumResource> MapToResource(IEnumerable<NzbDrone.Core.Music.Album> 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;
}
}
}
}

View File

@ -42,6 +42,8 @@ namespace Lidarr.Api.V1.Albums
public List<MediaCover> 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()
};
}

View File

@ -86,6 +86,7 @@
<Compile Include="Albums\AlbumResource.cs" />
<Compile Include="Albums\AlbumsMonitoredResource.cs" />
<Compile Include="Albums\AlbumStatisticsResource.cs" />
<Compile Include="Albums\AlbumLookupModule.cs" />
<Compile Include="Albums\MediumResource.cs" />
<Compile Include="Blacklist\BlacklistModule.cs" />
<Compile Include="Blacklist\BlacklistResource.cs" />

View File

@ -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<IMetadataProfileService>()
.Setup(s => s.Get(It.IsAny<int>()))
.Returns(_metadataProfile);
Mocker.GetMock<IMetadataProfileService>()
.Setup(s => s.Exists(It.IsAny<int>()))
.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<Album> {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<Album> { 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<BadRequestException>(() => Subject.GetArtistInfo("aaaaaa-aaa-aaaa-aaaa", 1));
Assert.Throws<ArtistNotFoundException>(() => Subject.GetArtistInfo("66c66aaa-6e2f-4930-8610-912e24c63ed1", 1));
}
[Test]
public void getting_details_of_invalid_guid_for_artist()
{
Assert.Throws<BadRequestException>(() => Subject.GetArtistInfo("66c66aaa-6e2f-4930-aaaaaa", 1));
}
[Test]
public void getting_details_of_invalid_album()
{
Assert.Throws<AlbumNotFoundException>(() => Subject.GetAlbumInfo("66c66aaa-6e2f-4930-8610-912e24c63ed1",null));
}
[Test]
public void getting_details_of_invalid_guid_for_album()
{
Assert.Throws<BadRequestException>(() => 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));

View File

@ -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();

View File

@ -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<AddAlbumService>
{
private Album _fakeAlbum;
[SetUp]
public void Setup()
{
_fakeAlbum = Builder<Album>
.CreateNew()
.Build();
}
private void GivenValidAlbum(string lidarrId)
{
Mocker.GetMock<IProvideAlbumInfo>()
.Setup(s => s.GetAlbumInfo(lidarrId, It.IsAny<string>()))
.Returns(new Tuple<Album, List<Track>>(_fakeAlbum, new List<Track>()));
}
[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<IProvideAlbumInfo>()
.Setup(s => s.GetAlbumInfo(newAlbum.ForeignAlbumId, It.IsAny<string>()))
.Throws(new AlbumNotFoundException(newAlbum.ForeignAlbumId));
Assert.Throws<ValidationException>(() => Subject.AddAlbum(newAlbum));
ExceptionVerification.ExpectedErrors(1);
}
}
}

View File

@ -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<AlbumMonitoredService>
{
private Artist _artist;
private List<Album> _albums;
[SetUp]
public void Setup()
{
const int albums = 4;
_artist = Builder<Artist>.CreateNew()
.Build();
_albums = Builder<Album>.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<IAlbumService>()
.Setup(s => s.GetAlbumsByArtist(It.IsAny<int>()))
.Returns(_albums);
Mocker.GetMock<ITrackService>()
.Setup(s => s.GetTracksByAlbum(It.IsAny<int>()))
.Returns(new List<Track>());
}
[Test]
public void should_be_able_to_monitor_artist_without_changing_albums()
{
Subject.SetAlbumMonitoredStatus(_artist, null);
Mocker.GetMock<IArtistService>()
.Verify(v => v.UpdateArtist(It.IsAny<Artist>()), Times.Once());
Mocker.GetMock<IAlbumService>()
.Verify(v => v.UpdateAlbums(It.IsAny<List<Album>>()), 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<IArtistService>()
.Verify(v => v.UpdateArtist(It.IsAny<Artist>()), 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<IAlbumService>()
.Verify(v => v.UpdateAlbums(It.Is<List<Album>>(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<Album, bool> predicate)
{
Mocker.GetMock<IAlbumService>()
.Verify(v => v.UpdateAlbums(It.Is<List<Album>>(l => l.Where(predicate).All(e => e.Monitored))));
}
private void VerifyNotMonitored(Func<Album, bool> predicate)
{
Mocker.GetMock<IAlbumService>()
.Verify(v => v.UpdateAlbums(It.Is<List<Album>>(l => l.Where(predicate).All(e => !e.Monitored))));
}
}
}

View File

@ -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<ArtistRepository, Artist>
{
[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<ProfilePrimaryAlbumTypeItem>(),
SecondaryAlbumTypes = new List<ProfileSecondaryAlbumTypeItem>()
};
Mocker.Resolve<ProfileRepository>().Insert(profile);
Mocker.Resolve<LanguageProfileRepository>().Insert(langProfile);
Mocker.Resolve<MetadataProfileRepository>().Insert(metaProfile);
var series = Builder<Artist>.CreateNew().BuildNew();
series.ProfileId = profile.Id;
series.LanguageProfileId = langProfile.Id;
var artist = Builder<Artist>.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();
}
}

View File

@ -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<RefreshAlbumService>
{
private Artist _artist;
private List<Album> _albums;
[SetUp]
public void Setup()
{
var album1 = Builder<Album>.CreateNew()
.With(s => s.ForeignAlbumId = "1")
.Build();
_albums = new List<Album>{album1};
_artist = Builder<Artist>.CreateNew()
.With(s => s.Albums = new List<Album>
{
album1
})
.Build();
Mocker.GetMock<IArtistService>()
.Setup(s => s.GetArtist(_artist.Id))
.Returns(_artist);
Mocker.GetMock<IProvideAlbumInfo>()
.Setup(s => s.GetAlbumInfo(It.IsAny<string>(), It.IsAny<string>()))
.Callback(() => { throw new AlbumNotFoundException(album1.ForeignAlbumId); });
}
private void GivenNewAlbumInfo(Album album)
{
Mocker.GetMock<IProvideAlbumInfo>()
.Setup(s => s.GetAlbumInfo(_albums.First().ForeignAlbumId, It.IsAny<string>()))
.Returns(new Tuple<Album, List<Track>>(album, new List<Track>()));
}
[Test]
public void should_log_error_if_musicbrainz_id_not_found()
{
Subject.RefreshAlbumInfo(_albums);
Mocker.GetMock<IAlbumService>()
.Verify(v => v.UpdateMany(It.IsAny<List<Album>>()), 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<IAlbumService>()
.Verify(v => v.UpdateMany(It.Is<List<Album>>(s => s.First().ForeignAlbumId == newAlbumInfo.ForeignAlbumId)));
ExceptionVerification.ExpectedWarns(1);
}
}
}

View File

@ -18,24 +18,33 @@ namespace NzbDrone.Core.Test.MusicTests
public class RefreshArtistServiceFixture : CoreTest<RefreshArtistService>
{
private Artist _artist;
private Album _album1;
private Album _album2;
private List<Album> _albums;
[SetUp]
public void Setup()
{
var season1 = Builder<Album>.CreateNew()
.With(s => s.ForeignAlbumId = "1")
.Build();
_album1 = Builder<Album>.CreateNew()
.With(s => s.ForeignAlbumId = "1")
.Build();
_album2 = Builder<Album>.CreateNew()
.With(s => s.ForeignAlbumId = "2")
.Build();
_albums = new List<Album> {_album1, _album2};
_artist = Builder<Artist>.CreateNew()
.With(s => s.Albums = new List<Album>
{
season1
})
.Build();
Mocker.GetMock<IArtistService>()
.Setup(s => s.GetArtist(_artist.Id))
.Returns(_artist);
Mocker.GetMock<IAlbumService>()
.Setup(s => s.GetAlbumsByArtist(It.IsAny<int>()))
.Returns(new List<Album>());
Mocker.GetMock<IProvideArtistInfo>()
.Setup(s => s.GetArtistInfo(It.IsAny<string>(), It.IsAny<int>()))
@ -46,25 +55,7 @@ namespace NzbDrone.Core.Test.MusicTests
{
Mocker.GetMock<IProvideArtistInfo>()
.Setup(s => s.GetArtistInfo(_artist.ForeignArtistId, _artist.MetadataProfileId))
.Returns(new Tuple<Artist, List<Album>>(artist, new List<Album>()));
}
// 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<Album>.CreateNew()
.With(s => s.ForeignAlbumId = "2")
.Build());
GivenNewArtistInfo(newArtistInfo);
Subject.Execute(new RefreshArtistCommand(_artist.Id));
Mocker.GetMock<IArtistService>()
.Verify(v => v.UpdateArtist(It.Is<Artist>(s => s.Albums.Count == 2 && s.Albums.Single(season => season.ForeignAlbumId == "2").Monitored == true)));
.Returns(new Tuple<Artist, List<Album>>(artist, _albums));
}
[Test]

View File

@ -284,10 +284,13 @@
<Compile Include="MetadataSource\SkyHook\SkyHookProxySearchFixture.cs" />
<Compile Include="MetadataSource\SearchArtistComparerFixture.cs" />
<Compile Include="MetadataSource\SkyHook\SkyHookProxyFixture.cs" />
<Compile Include="MusicTests\AddAlbumFixture.cs" />
<Compile Include="MusicTests\AddArtistFixture.cs" />
<Compile Include="MusicTests\AlbumMonitoredServiceTests\AlbumMonitoredServiceFixture.cs" />
<Compile Include="MusicTests\ArtistRepositoryTests\ArtistRepositoryFixture.cs" />
<Compile Include="MusicTests\ArtistServiceTests\AddArtistFixture.cs" />
<Compile Include="MusicTests\ArtistServiceTests\UpdateMultipleArtistFixture.cs" />
<Compile Include="MusicTests\RefreshAlbumServiceFixture.cs" />
<Compile Include="NotificationTests\NotificationBaseFixture.cs" />
<Compile Include="NotificationTests\SynologyIndexerFixture.cs" />
<Compile Include="OrganizerTests\FileNameBuilderTests\CleanTitleFixture.cs" />

View File

@ -6,6 +6,6 @@ namespace NzbDrone.Core.MetadataSource
{
public interface ISearchForNewAlbum
{
List<Album> SearchForNewAlbum(string title, string artist, DateTime releaseDate);
List<Album> SearchForNewAlbum(string title, string artist);
}
}

View File

@ -188,7 +188,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
}
}
public List<Album> SearchForNewAlbum(string title, string artist, DateTime releaseDate)
public List<Album> 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();

View File

@ -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<Album> AddAlbums(List<Album> 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<Album> AddAlbums(List<Album> newAlbums)
{
var added = DateTime.UtcNow;
var albumsToAdd = new List<Album>();
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<Album, List<Track>> AddSkyhookData(Album newAlbum)
{
Tuple<Album, List<Track>> 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<ValidationFailure>
{
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;
}
}
}

View File

@ -13,6 +13,7 @@ namespace NzbDrone.Core.Music
Images = new List<MediaCover.MediaCover>();
Media = new List<Medium>();
Releases = new List<AlbumRelease>();
CurrentRelease = new AlbumRelease();
}
public const string RELEASE_DATE_FORMAT = "yyyy-MM-dd";

View File

@ -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.

View File

@ -197,7 +197,7 @@ namespace NzbDrone.Core.Music
public List<Album> UpdateAlbums(List<Album> 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);

View File

@ -1,15 +0,0 @@
using System.Collections.Generic;
using NzbDrone.Common.Messaging;
namespace NzbDrone.Core.Music.Events
{
public class AlbumsAddedEvent : IEvent
{
public List<int> AlbumIds { get; private set; }
public AlbumsAddedEvent(List<int> albumIds)
{
AlbumIds = albumIds;
}
}
}

View File

@ -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<Album> remoteAlbums);
void RefreshAlbumInfo(Album album);
void RefreshAlbumInfo(List<Album> albums);
}
public class RefreshAlbumService : IRefreshAlbumService, IExecute<RefreshAlbumCommand>
@ -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<Album> 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>{album});
}
public void RefreshAlbumInfo(Artist artist, IEnumerable<Album> 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<Album>();
var newList = new List<Album>();
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<Album, List<Track>> 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<Artist> artists)
{
var artist = artists.SingleOrDefault(c => c.Id == album.ArtistId);
return album == null || album.Monitored;
}
private Album GetAlbumToUpdate(Artist artist, Album album, List<Album> existingAlbums)
{
return existingAlbums.FirstOrDefault(
e => e.ForeignAlbumId == album.ForeignAlbumId /* && e.ReleaseDate == album.ReleaseDate*/);
}
private IEnumerable<Album> OrderAlbums(Artist artist, List<Album> albums)
{
return albums.OrderBy(e => e.ForeignAlbumId).ThenBy(e => e.ReleaseDate);
}
public void Execute(RefreshAlbumCommand message)
{
if (message.AlbumId.HasValue)

View File

@ -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<Album>();
var updateAlbumsList = new List<Album>();
// 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<Album> UpdateAlbums(Artist artist, List<Album> 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));

View File

@ -768,13 +768,13 @@
<Compile Include="Extras\Metadata\MetadataRepository.cs" />
<Compile Include="Extras\Metadata\MetadataService.cs" />
<Compile Include="Extras\Metadata\MetadataType.cs" />
<Compile Include="Music\AddAlbumService.cs" />
<Compile Include="Music\AlbumEditedService.cs" />
<Compile Include="Music\AlbumRelease.cs" />
<Compile Include="Music\ArtistStatusType.cs" />
<Compile Include="Music\AlbumCutoffService.cs" />
<Compile Include="Music\Commands\BulkMoveArtistCommand.cs" />
<Compile Include="Music\Commands\RefreshAlbumCommand.cs" />
<Compile Include="Music\Events\AlbumsAddedEvent.cs" />
<Compile Include="Music\Events\ArtistsImportedEvent.cs" />
<Compile Include="Music\SecondaryAlbumType.cs" />
<Compile Include="Music\PrimaryAlbumType.cs" />