Rework how albums are refreshed/added, add album search route (#190)
Rework how albums are refreshed/added, add album search routepull/6/head
parent
6ff5e6337b
commit
5551b2166a
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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))));
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue