mirror of https://github.com/lidarr/Lidarr
New: Refactor metadata update
This commit is contained in:
parent
f5c1858d4c
commit
0b7a42ee3b
|
@ -61,7 +61,8 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
|||
_addArtistService = Mocker.Resolve<AddArtistService>();
|
||||
|
||||
Mocker.SetConstant<IRefreshTrackService>(Mocker.Resolve<RefreshTrackService>());
|
||||
Mocker.SetConstant<IAddAlbumService>(Mocker.Resolve<AddAlbumService>());
|
||||
Mocker.SetConstant<IRefreshAlbumReleaseService>(Mocker.Resolve<RefreshAlbumReleaseService>());
|
||||
Mocker.SetConstant<IRefreshAlbumService>(Mocker.Resolve<RefreshAlbumService>());
|
||||
_refreshArtistService = Mocker.Resolve<RefreshArtistService>();
|
||||
|
||||
Mocker.GetMock<IAddArtistValidator>().Setup(x => x.Validate(It.IsAny<Artist>())).Returns(new ValidationResult());
|
||||
|
|
|
@ -13,6 +13,8 @@ using NzbDrone.Core.Music.Commands;
|
|||
using NzbDrone.Test.Common;
|
||||
using FluentAssertions;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.History;
|
||||
|
||||
namespace NzbDrone.Core.Test.MusicTests
|
||||
{
|
||||
|
@ -40,6 +42,7 @@ namespace NzbDrone.Core.Test.MusicTests
|
|||
_releases = new List<AlbumRelease> { release };
|
||||
|
||||
var album1 = Builder<Album>.CreateNew()
|
||||
.With(x => x.ArtistMetadata = Builder<ArtistMetadata>.CreateNew().Build())
|
||||
.With(s => s.Id = 1234)
|
||||
.With(s => s.ForeignAlbumId = "1")
|
||||
.With(s => s.AlbumReleases = _releases)
|
||||
|
@ -63,6 +66,10 @@ namespace NzbDrone.Core.Test.MusicTests
|
|||
.Setup(s => s.FindById(It.IsAny<List<string>>()))
|
||||
.Returns(new List<ArtistMetadata>());
|
||||
|
||||
Mocker.GetMock<IArtistMetadataRepository>()
|
||||
.Setup(s => s.UpsertMany(It.IsAny<List<ArtistMetadata> >()))
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IProvideAlbumInfo>()
|
||||
.Setup(s => s.GetAlbumInfo(It.IsAny<string>()))
|
||||
.Callback(() => { throw new AlbumNotFoundException(album1.ForeignAlbumId); });
|
||||
|
@ -70,6 +77,18 @@ namespace NzbDrone.Core.Test.MusicTests
|
|||
Mocker.GetMock<ICheckIfAlbumShouldBeRefreshed>()
|
||||
.Setup(s => s.ShouldRefresh(It.IsAny<Album>()))
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(x => x.GetFilesByAlbum(It.IsAny<int>()))
|
||||
.Returns(new List<TrackFile>());
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(x => x.GetFilesByRelease(It.IsAny<int>()))
|
||||
.Returns(new List<TrackFile>());
|
||||
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Setup(x => x.GetByAlbum(It.IsAny<int>(), It.IsAny<HistoryEventType?>()))
|
||||
.Returns(new List<History.History>());
|
||||
}
|
||||
|
||||
private void GivenNewAlbumInfo(Album album)
|
||||
|
@ -80,29 +99,71 @@ namespace NzbDrone.Core.Test.MusicTests
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void should_log_error_if_musicbrainz_id_not_found()
|
||||
public void should_update_if_musicbrainz_id_changed_and_no_clash()
|
||||
{
|
||||
Subject.RefreshAlbumInfo(_albums, false, false);
|
||||
|
||||
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();
|
||||
var newAlbumInfo = _albums.First().JsonClone();
|
||||
newAlbumInfo.ArtistMetadata = _albums.First().ArtistMetadata.Value.JsonClone();
|
||||
newAlbumInfo.ForeignAlbumId = _albums.First().ForeignAlbumId + 1;
|
||||
newAlbumInfo.AlbumReleases = _releases;
|
||||
|
||||
GivenNewAlbumInfo(newAlbumInfo);
|
||||
|
||||
Subject.RefreshAlbumInfo(_albums, false, false);
|
||||
Subject.RefreshAlbumInfo(_albums, null, false, false);
|
||||
|
||||
Mocker.GetMock<IAlbumService>()
|
||||
.Verify(v => v.UpdateMany(It.Is<List<Album>>(s => s.First().ForeignAlbumId == newAlbumInfo.ForeignAlbumId)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_merge_if_musicbrainz_id_changed_and_new_already_exists()
|
||||
{
|
||||
var existing = _albums.First();
|
||||
|
||||
var clash = existing.JsonClone();
|
||||
clash.Id = 100;
|
||||
clash.ArtistMetadata = existing.ArtistMetadata.Value.JsonClone();
|
||||
clash.ForeignAlbumId = clash.ForeignAlbumId + 1;
|
||||
|
||||
clash.AlbumReleases = Builder<AlbumRelease>.CreateListOfSize(10)
|
||||
.All().With(x => x.AlbumId = clash.Id)
|
||||
.BuildList();
|
||||
|
||||
Mocker.GetMock<IAlbumService>()
|
||||
.Setup(x => x.FindById(clash.ForeignAlbumId))
|
||||
.Returns(clash);
|
||||
|
||||
Mocker.GetMock<IReleaseService>()
|
||||
.Setup(x => x.GetReleasesByAlbum(_albums.First().Id))
|
||||
.Returns(_releases);
|
||||
|
||||
Mocker.GetMock<IReleaseService>()
|
||||
.Setup(x => x.GetReleasesByAlbum(clash.Id))
|
||||
.Returns(new List<AlbumRelease>());
|
||||
|
||||
Mocker.GetMock<IReleaseService>()
|
||||
.Setup(x => x.GetReleasesForRefresh(It.IsAny<int>(), It.IsAny<IEnumerable<string>>()))
|
||||
.Returns(_releases);
|
||||
|
||||
var newAlbumInfo = existing.JsonClone();
|
||||
newAlbumInfo.ArtistMetadata = existing.ArtistMetadata.Value.JsonClone();
|
||||
newAlbumInfo.ForeignAlbumId = _albums.First().ForeignAlbumId + 1;
|
||||
newAlbumInfo.AlbumReleases = _releases;
|
||||
|
||||
GivenNewAlbumInfo(newAlbumInfo);
|
||||
|
||||
Subject.RefreshAlbumInfo(_albums, null, false, false);
|
||||
|
||||
// check releases moved to clashing album
|
||||
Mocker.GetMock<IReleaseService>()
|
||||
.Verify(v => v.UpdateMany(It.Is<List<AlbumRelease>>(x => x.All(y => y.AlbumId == clash.Id) && x.Count == _releases.Count)));
|
||||
|
||||
// check old album is deleted
|
||||
Mocker.GetMock<IAlbumService>()
|
||||
.Verify(v => v.DeleteMany(It.Is<List<Album>>(x => x.First().ForeignAlbumId == existing.ForeignAlbumId)));
|
||||
|
||||
// check that clash gets updated
|
||||
Mocker.GetMock<IAlbumService>()
|
||||
.Verify(v => v.UpdateMany(It.Is<List<Album>>(s => s.First().ForeignAlbumId == newAlbumInfo.ForeignAlbumId)));
|
||||
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
|
@ -115,11 +176,23 @@ namespace NzbDrone.Core.Test.MusicTests
|
|||
|
||||
GivenNewAlbumInfo(album);
|
||||
|
||||
Subject.RefreshAlbumInfo(album, false);
|
||||
Subject.RefreshAlbumInfo(album, null, false);
|
||||
|
||||
Mocker.GetMock<IAlbumService>()
|
||||
.Verify(x => x.DeleteMany(It.Is<List<Album>>(y => y.Count == 1 && y.First().ForeignAlbumId == album.ForeignAlbumId)),
|
||||
.Verify(x => x.DeleteAlbum(album.Id, true),
|
||||
Times.Once());
|
||||
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void two_equivalent_albums_should_be_equal()
|
||||
{
|
||||
var album = Builder<Album>.CreateNew().Build();
|
||||
var album2 = Builder<Album>.CreateNew().Build();
|
||||
|
||||
ReferenceEquals(album, album2).Should().BeFalse();
|
||||
album.Equals(album2).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -160,9 +233,13 @@ namespace NzbDrone.Core.Test.MusicTests
|
|||
[Test]
|
||||
public void should_not_add_duplicate_releases()
|
||||
{
|
||||
var newAlbum = Builder<Album>.CreateNew().Build();
|
||||
var newAlbum = Builder<Album>.CreateNew()
|
||||
.With(x => x.ArtistMetadata = Builder<ArtistMetadata>.CreateNew().Build())
|
||||
.Build();
|
||||
// this is required because RefreshAlbumInfo will edit the album passed in
|
||||
var albumCopy = Builder<Album>.CreateNew().Build();
|
||||
var albumCopy = Builder<Album>.CreateNew()
|
||||
.With(x => x.ArtistMetadata = Builder<ArtistMetadata>.CreateNew().Build())
|
||||
.Build();
|
||||
|
||||
var releases = Builder<AlbumRelease>.CreateListOfSize(10)
|
||||
.All()
|
||||
|
@ -191,21 +268,13 @@ namespace NzbDrone.Core.Test.MusicTests
|
|||
.Setup(x => x.GetAlbumInfo(It.IsAny<string>()))
|
||||
.Returns(Tuple.Create("dummy string", albumCopy, new List<ArtistMetadata>()));
|
||||
|
||||
Subject.RefreshAlbumInfo(newAlbum, false);
|
||||
Subject.RefreshAlbumInfo(newAlbum, null, false);
|
||||
|
||||
newAlbum.AlbumReleases.Value.Should().HaveCount(7);
|
||||
|
||||
Mocker.GetMock<IReleaseService>()
|
||||
.Verify(x => x.DeleteMany(It.Is<List<AlbumRelease>>(l => l.Count == 0)), Times.Once());
|
||||
|
||||
Mocker.GetMock<IReleaseService>()
|
||||
.Verify(x => x.UpdateMany(It.Is<List<AlbumRelease>>(l => l.Count == 1 && l.Select(r => r.ForeignReleaseId).Distinct().Count() == 1)), Times.Once());
|
||||
|
||||
Mocker.GetMock<IReleaseService>()
|
||||
.Verify(x => x.InsertMany(It.Is<List<AlbumRelease>>(l => l.Count == 6 &&
|
||||
l.Select(r => r.ForeignReleaseId).Distinct().Count() == l.Count &&
|
||||
!l.Select(r => r.ForeignReleaseId).Contains("DuplicateId2"))),
|
||||
Times.Once());
|
||||
Mocker.GetMock<IRefreshAlbumReleaseService>()
|
||||
.Verify(x => x.RefreshEntityInfo(It.Is<List<AlbumRelease>>(l => l.Count == 7 && l.Count(y => y.Monitored) == 1),
|
||||
It.IsAny<List<AlbumRelease>>(),
|
||||
It.IsAny<bool>(),
|
||||
It.IsAny<bool>()));
|
||||
}
|
||||
|
||||
[TestCase(true, true, 1)]
|
||||
|
@ -214,9 +283,13 @@ namespace NzbDrone.Core.Test.MusicTests
|
|||
[TestCase(false, false, 0)]
|
||||
public void should_only_leave_one_release_monitored(bool skyhookMonitored, bool existingMonitored, int expectedUpdates)
|
||||
{
|
||||
var newAlbum = Builder<Album>.CreateNew().Build();
|
||||
var newAlbum = Builder<Album>.CreateNew()
|
||||
.With(x => x.ArtistMetadata = Builder<ArtistMetadata>.CreateNew().Build())
|
||||
.Build();
|
||||
// this is required because RefreshAlbumInfo will edit the album passed in
|
||||
var albumCopy = Builder<Album>.CreateNew().Build();
|
||||
var albumCopy = Builder<Album>.CreateNew()
|
||||
.With(x => x.ArtistMetadata = Builder<ArtistMetadata>.CreateNew().Build())
|
||||
.Build();
|
||||
|
||||
var releases = Builder<AlbumRelease>.CreateListOfSize(10)
|
||||
.All()
|
||||
|
@ -248,31 +321,26 @@ namespace NzbDrone.Core.Test.MusicTests
|
|||
.Setup(x => x.GetAlbumInfo(It.IsAny<string>()))
|
||||
.Returns(Tuple.Create("dummy string", albumCopy, new List<ArtistMetadata>()));
|
||||
|
||||
Subject.RefreshAlbumInfo(newAlbum, false);
|
||||
Subject.RefreshAlbumInfo(newAlbum, null, false);
|
||||
|
||||
newAlbum.AlbumReleases.Value.Should().HaveCount(10);
|
||||
newAlbum.AlbumReleases.Value.Where(x => x.Monitored).Should().HaveCount(1);
|
||||
|
||||
Mocker.GetMock<IReleaseService>()
|
||||
.Verify(x => x.DeleteMany(It.Is<List<AlbumRelease>>(l => l.Count == 0)), Times.Once());
|
||||
|
||||
Mocker.GetMock<IReleaseService>()
|
||||
.Verify(x => x.UpdateMany(It.Is<List<AlbumRelease>>(l => l.Count == expectedUpdates && l.Select(r => r.ForeignReleaseId).Distinct().Count() == expectedUpdates)), Times.Once());
|
||||
Mocker.GetMock<IRefreshAlbumReleaseService>()
|
||||
.Verify(x => x.RefreshEntityInfo(It.Is<List<AlbumRelease>>(l => l.Count == 10 && l.Count(y => y.Monitored) == 1),
|
||||
It.IsAny<List<AlbumRelease>>(),
|
||||
It.IsAny<bool>(),
|
||||
It.IsAny<bool>()));
|
||||
|
||||
Mocker.GetMock<IReleaseService>()
|
||||
.Verify(x => x.InsertMany(It.Is<List<AlbumRelease>>(l => l.Count == 8 &&
|
||||
l.Select(r => r.ForeignReleaseId).Distinct().Count() == l.Count &&
|
||||
!l.Select(r => r.ForeignReleaseId).Contains("ExistingId1") &&
|
||||
!l.Select(r => r.ForeignReleaseId).Contains("ExistingId2"))),
|
||||
Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void refreshing_album_should_not_change_monitored_release_if_monitored_release_not_deleted()
|
||||
{
|
||||
var newAlbum = Builder<Album>.CreateNew().Build();
|
||||
var newAlbum = Builder<Album>.CreateNew()
|
||||
.With(x => x.ArtistMetadata = Builder<ArtistMetadata>.CreateNew().Build())
|
||||
.Build();
|
||||
// this is required because RefreshAlbumInfo will edit the album passed in
|
||||
var albumCopy = Builder<Album>.CreateNew().Build();
|
||||
var albumCopy = Builder<Album>.CreateNew()
|
||||
.With(x => x.ArtistMetadata = Builder<ArtistMetadata>.CreateNew().Build())
|
||||
.Build();
|
||||
|
||||
// only ExistingId1 is monitored from dummy skyhook
|
||||
var releases = Builder<AlbumRelease>.CreateListOfSize(10)
|
||||
|
@ -308,32 +376,28 @@ namespace NzbDrone.Core.Test.MusicTests
|
|||
.Setup(x => x.GetAlbumInfo(It.IsAny<string>()))
|
||||
.Returns(Tuple.Create("dummy string", albumCopy, new List<ArtistMetadata>()));
|
||||
|
||||
Subject.RefreshAlbumInfo(newAlbum, false);
|
||||
|
||||
newAlbum.AlbumReleases.Value.Should().HaveCount(10);
|
||||
newAlbum.AlbumReleases.Value.Where(x => x.Monitored).Should().HaveCount(1);
|
||||
newAlbum.AlbumReleases.Value.Single(x => x.Monitored).ForeignReleaseId.Should().Be("ExistingId2");
|
||||
Subject.RefreshAlbumInfo(newAlbum, null, false);
|
||||
|
||||
Mocker.GetMock<IReleaseService>()
|
||||
.Verify(x => x.DeleteMany(It.Is<List<AlbumRelease>>(l => l.Count == 0)), Times.Once());
|
||||
|
||||
Mocker.GetMock<IReleaseService>()
|
||||
.Verify(x => x.UpdateMany(It.Is<List<AlbumRelease>>(l => l.Count == 0)), Times.Once());
|
||||
|
||||
Mocker.GetMock<IReleaseService>()
|
||||
.Verify(x => x.InsertMany(It.Is<List<AlbumRelease>>(l => l.Count == 8 &&
|
||||
l.Select(r => r.ForeignReleaseId).Distinct().Count() == l.Count &&
|
||||
!l.Select(r => r.ForeignReleaseId).Contains("ExistingId1") &&
|
||||
!l.Select(r => r.ForeignReleaseId).Contains("ExistingId2"))),
|
||||
Times.Once());
|
||||
Mocker.GetMock<IRefreshAlbumReleaseService>()
|
||||
.Verify(x => x.RefreshEntityInfo(It.Is<List<AlbumRelease>>(
|
||||
l => l.Count == 10 &&
|
||||
l.Count(y => y.Monitored) == 1 &&
|
||||
l.Single(y => y.Monitored).ForeignReleaseId == "ExistingId2"),
|
||||
It.IsAny<List<AlbumRelease>>(),
|
||||
It.IsAny<bool>(),
|
||||
It.IsAny<bool>()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void refreshing_album_should_change_monitored_release_if_monitored_release_deleted()
|
||||
{
|
||||
var newAlbum = Builder<Album>.CreateNew().Build();
|
||||
var newAlbum = Builder<Album>.CreateNew()
|
||||
.With(x => x.ArtistMetadata = Builder<ArtistMetadata>.CreateNew().Build())
|
||||
.Build();
|
||||
// this is required because RefreshAlbumInfo will edit the album passed in
|
||||
var albumCopy = Builder<Album>.CreateNew().Build();
|
||||
var albumCopy = Builder<Album>.CreateNew()
|
||||
.With(x => x.ArtistMetadata = Builder<ArtistMetadata>.CreateNew().Build())
|
||||
.Build();
|
||||
|
||||
// Only existingId1 monitored in skyhook. ExistingId2 is missing
|
||||
var releases = Builder<AlbumRelease>.CreateListOfSize(10)
|
||||
|
@ -369,24 +433,16 @@ namespace NzbDrone.Core.Test.MusicTests
|
|||
.Setup(x => x.GetAlbumInfo(It.IsAny<string>()))
|
||||
.Returns(Tuple.Create("dummy string", albumCopy, new List<ArtistMetadata>()));
|
||||
|
||||
Subject.RefreshAlbumInfo(newAlbum, false);
|
||||
|
||||
newAlbum.AlbumReleases.Value.Should().HaveCount(10);
|
||||
newAlbum.AlbumReleases.Value.Where(x => x.Monitored).Should().HaveCount(1);
|
||||
newAlbum.AlbumReleases.Value.Single(x => x.Monitored).ForeignReleaseId.Should().NotBe("ExistingId2");
|
||||
|
||||
Mocker.GetMock<IReleaseService>()
|
||||
.Verify(x => x.DeleteMany(It.Is<List<AlbumRelease>>(l => l.Single().ForeignReleaseId == "ExistingId2")), Times.Once());
|
||||
|
||||
Mocker.GetMock<IReleaseService>()
|
||||
.Verify(x => x.UpdateMany(It.Is<List<AlbumRelease>>(l => l.Count == 0)), Times.Once());
|
||||
Subject.RefreshAlbumInfo(newAlbum, null, false);
|
||||
|
||||
Mocker.GetMock<IReleaseService>()
|
||||
.Verify(x => x.InsertMany(It.Is<List<AlbumRelease>>(l => l.Count == 9 &&
|
||||
l.Select(r => r.ForeignReleaseId).Distinct().Count() == l.Count &&
|
||||
!l.Select(r => r.ForeignReleaseId).Contains("ExistingId1") &&
|
||||
!l.Select(r => r.ForeignReleaseId).Contains("ExistingId2"))),
|
||||
Times.Once());
|
||||
Mocker.GetMock<IRefreshAlbumReleaseService>()
|
||||
.Verify(x => x.RefreshEntityInfo(It.Is<List<AlbumRelease>>(
|
||||
l => l.Count == 11 &&
|
||||
l.Count(y => y.Monitored) == 1 &&
|
||||
l.Single(y => y.Monitored).ForeignReleaseId != "ExistingId2"),
|
||||
It.IsAny<List<AlbumRelease>>(),
|
||||
It.IsAny<bool>(),
|
||||
It.IsAny<bool>()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,9 @@ using NzbDrone.Core.Test.Framework;
|
|||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Music.Commands;
|
||||
using NzbDrone.Test.Common;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.History;
|
||||
using NzbDrone.Core.Music.Events;
|
||||
|
||||
namespace NzbDrone.Core.Test.MusicTests
|
||||
{
|
||||
|
@ -41,17 +44,24 @@ namespace NzbDrone.Core.Test.MusicTests
|
|||
.With(a => a.Metadata = metadata)
|
||||
.Build();
|
||||
|
||||
Mocker.GetMock<IArtistService>()
|
||||
Mocker.GetMock<IArtistService>(MockBehavior.Strict)
|
||||
.Setup(s => s.GetArtist(_artist.Id))
|
||||
.Returns(_artist);
|
||||
|
||||
Mocker.GetMock<IAlbumService>()
|
||||
.Setup(s => s.GetAlbumsForRefresh(It.IsAny<int>(), It.IsAny<IEnumerable<string>>()))
|
||||
.Returns(new List<Album>());
|
||||
Mocker.GetMock<IAlbumService>(MockBehavior.Strict)
|
||||
.Setup(s => s.InsertMany(It.IsAny<List<Album>>()));
|
||||
|
||||
Mocker.GetMock<IProvideArtistInfo>()
|
||||
.Setup(s => s.GetArtistInfo(It.IsAny<string>(), It.IsAny<int>()))
|
||||
.Callback(() => { throw new ArtistNotFoundException(_artist.ForeignArtistId); });
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(x => x.GetFilesByArtist(It.IsAny<int>()))
|
||||
.Returns(new List<TrackFile>());
|
||||
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Setup(x => x.GetByArtist(It.IsAny<int>(), It.IsAny<HistoryEventType?>()))
|
||||
.Returns(new List<History.History>());
|
||||
}
|
||||
|
||||
private void GivenNewArtistInfo(Artist artist)
|
||||
|
@ -60,81 +70,207 @@ namespace NzbDrone.Core.Test.MusicTests
|
|||
.Setup(s => s.GetArtistInfo(_artist.ForeignArtistId, _artist.MetadataProfileId))
|
||||
.Returns(artist);
|
||||
}
|
||||
|
||||
private void GivenArtistFiles()
|
||||
{
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(x => x.GetFilesByArtist(It.IsAny<int>()))
|
||||
.Returns(Builder<TrackFile>.CreateListOfSize(1).BuildList());
|
||||
}
|
||||
|
||||
private void GivenAlbumsForRefresh()
|
||||
{
|
||||
Mocker.GetMock<IAlbumService>(MockBehavior.Strict)
|
||||
.Setup(s => s.GetAlbumsForRefresh(It.IsAny<int>(), It.IsAny<IEnumerable<string>>()))
|
||||
.Returns(new List<Album>());
|
||||
}
|
||||
|
||||
private void AllowArtistUpdate()
|
||||
{
|
||||
Mocker.GetMock<IArtistService>(MockBehavior.Strict)
|
||||
.Setup(x => x.UpdateArtist(It.IsAny<Artist>()))
|
||||
.Returns((Artist a) => a);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_log_error_if_musicbrainz_id_not_found()
|
||||
public void should_not_publish_artist_updated_event_if_metadata_not_updated()
|
||||
{
|
||||
var newArtistInfo = _artist.JsonClone();
|
||||
newArtistInfo.Metadata = _artist.Metadata.Value.JsonClone();
|
||||
newArtistInfo.Albums = _albums;
|
||||
|
||||
GivenNewArtistInfo(newArtistInfo);
|
||||
GivenAlbumsForRefresh();
|
||||
AllowArtistUpdate();
|
||||
|
||||
Subject.Execute(new RefreshArtistCommand(_artist.Id));
|
||||
|
||||
VerifyEventNotPublished<ArtistUpdatedEvent>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_publish_artist_updated_event_if_metadata_updated()
|
||||
{
|
||||
var newArtistInfo = _artist.JsonClone();
|
||||
newArtistInfo.Metadata = _artist.Metadata.Value.JsonClone();
|
||||
newArtistInfo.Metadata.Value.Images = new List<MediaCover.MediaCover> {
|
||||
new MediaCover.MediaCover(MediaCover.MediaCoverTypes.Logo, "dummy")
|
||||
};
|
||||
newArtistInfo.Albums = _albums;
|
||||
|
||||
GivenNewArtistInfo(newArtistInfo);
|
||||
GivenAlbumsForRefresh();
|
||||
AllowArtistUpdate();
|
||||
|
||||
Subject.Execute(new RefreshArtistCommand(_artist.Id));
|
||||
|
||||
VerifyEventPublished<ArtistUpdatedEvent>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_log_error_and_delete_if_musicbrainz_id_not_found_and_artist_has_no_files()
|
||||
{
|
||||
Mocker.GetMock<IArtistService>()
|
||||
.Setup(x => x.DeleteArtist(It.IsAny<int>(), It.IsAny<bool>(), It.IsAny<bool>()));
|
||||
|
||||
Subject.Execute(new RefreshArtistCommand(_artist.Id));
|
||||
|
||||
Mocker.GetMock<IArtistService>()
|
||||
.Verify(v => v.UpdateArtist(It.IsAny<Artist>()), Times.Never());
|
||||
|
||||
Mocker.GetMock<IArtistService>()
|
||||
.Verify(v => v.DeleteArtist(It.IsAny<int>(), It.IsAny<bool>(), It.IsAny<bool>()), Times.Once());
|
||||
|
||||
ExceptionVerification.ExpectedErrors(1);
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_log_error_but_not_delete_if_musicbrainz_id_not_found_and_artist_has_files()
|
||||
{
|
||||
GivenArtistFiles();
|
||||
GivenAlbumsForRefresh();
|
||||
|
||||
Subject.Execute(new RefreshArtistCommand(_artist.Id));
|
||||
|
||||
Mocker.GetMock<IArtistService>()
|
||||
.Verify(v => v.UpdateArtist(It.IsAny<Artist>()), Times.Never());
|
||||
|
||||
Mocker.GetMock<IArtistService>()
|
||||
.Verify(v => v.DeleteArtist(It.IsAny<int>(), It.IsAny<bool>(), It.IsAny<bool>()), Times.Never());
|
||||
|
||||
ExceptionVerification.ExpectedErrors(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_update_if_musicbrainz_id_changed()
|
||||
public void should_update_if_musicbrainz_id_changed_and_no_clash()
|
||||
{
|
||||
var newArtistInfo = _artist.JsonClone();
|
||||
newArtistInfo.Metadata = _artist.Metadata.Value.JsonClone();
|
||||
newArtistInfo.Albums = _albums;
|
||||
newArtistInfo.ForeignArtistId = _artist.ForeignArtistId + 1;
|
||||
newArtistInfo.Metadata.Value.Id = 100;
|
||||
|
||||
GivenNewArtistInfo(newArtistInfo);
|
||||
|
||||
var seq = new MockSequence();
|
||||
|
||||
Mocker.GetMock<IArtistService>(MockBehavior.Strict)
|
||||
.Setup(x => x.FindById(newArtistInfo.ForeignArtistId))
|
||||
.Returns(default(Artist));
|
||||
|
||||
// Make sure that the artist is updated before we refresh the albums
|
||||
Mocker.GetMock<IArtistService>(MockBehavior.Strict)
|
||||
.InSequence(seq)
|
||||
.Setup(x => x.UpdateArtist(It.IsAny<Artist>()))
|
||||
.Returns((Artist a) => a);
|
||||
|
||||
Mocker.GetMock<IAlbumService>(MockBehavior.Strict)
|
||||
.InSequence(seq)
|
||||
.Setup(x => x.GetAlbumsForRefresh(It.IsAny<int>(), It.IsAny<IEnumerable<string>>()))
|
||||
.Returns(new List<Album>());
|
||||
|
||||
// Update called twice for a move/merge
|
||||
Mocker.GetMock<IArtistService>(MockBehavior.Strict)
|
||||
.InSequence(seq)
|
||||
.Setup(x => x.UpdateArtist(It.IsAny<Artist>()))
|
||||
.Returns((Artist a) => a);
|
||||
|
||||
Subject.Execute(new RefreshArtistCommand(_artist.Id));
|
||||
|
||||
Mocker.GetMock<IArtistService>()
|
||||
.Verify(v => v.UpdateArtist(It.Is<Artist>(s => s.ForeignArtistId == newArtistInfo.ForeignArtistId)));
|
||||
.Verify(v => v.UpdateArtist(It.Is<Artist>(s => s.ArtistMetadataId == 100 && s.ForeignArtistId == newArtistInfo.ForeignArtistId)),
|
||||
Times.Exactly(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_merge_if_musicbrainz_id_changed_and_new_id_already_exists()
|
||||
{
|
||||
var existing = _artist;
|
||||
|
||||
var clash = _artist.JsonClone();
|
||||
clash.Id = 100;
|
||||
clash.Metadata = existing.Metadata.Value.JsonClone();
|
||||
clash.Metadata.Value.Id = 101;
|
||||
clash.Metadata.Value.ForeignArtistId = clash.Metadata.Value.ForeignArtistId + 1;
|
||||
|
||||
Mocker.GetMock<IArtistService>(MockBehavior.Strict)
|
||||
.Setup(x => x.FindById(clash.Metadata.Value.ForeignArtistId))
|
||||
.Returns(clash);
|
||||
|
||||
var newArtistInfo = clash.JsonClone();
|
||||
newArtistInfo.Metadata = clash.Metadata.Value.JsonClone();
|
||||
newArtistInfo.Albums = _albums.JsonClone();
|
||||
newArtistInfo.Albums.Value.ForEach(x => x.Id = 0);
|
||||
|
||||
GivenNewArtistInfo(newArtistInfo);
|
||||
|
||||
var seq = new MockSequence();
|
||||
|
||||
// Make sure that the artist is updated before we refresh the albums
|
||||
Mocker.GetMock<IAlbumService>(MockBehavior.Strict)
|
||||
.InSequence(seq)
|
||||
.Setup(x => x.GetAlbumsByArtist(existing.Id))
|
||||
.Returns(_albums);
|
||||
|
||||
Mocker.GetMock<IAlbumService>(MockBehavior.Strict)
|
||||
.InSequence(seq)
|
||||
.Setup(x => x.UpdateMany(It.IsAny<List<Album>>()));
|
||||
|
||||
Mocker.GetMock<IArtistService>(MockBehavior.Strict)
|
||||
.InSequence(seq)
|
||||
.Setup(x => x.DeleteArtist(existing.Id, It.IsAny<bool>(), false));
|
||||
|
||||
Mocker.GetMock<IArtistService>(MockBehavior.Strict)
|
||||
.InSequence(seq)
|
||||
.Setup(x => x.UpdateArtist(It.Is<Artist>(a => a.Id == clash.Id)))
|
||||
.Returns((Artist a) => a);
|
||||
|
||||
Mocker.GetMock<IAlbumService>(MockBehavior.Strict)
|
||||
.InSequence(seq)
|
||||
.Setup(x => x.GetAlbumsForRefresh(clash.ArtistMetadataId, It.IsAny<IEnumerable<string>>()))
|
||||
.Returns(_albums);
|
||||
|
||||
// Update called twice for a move/merge
|
||||
Mocker.GetMock<IArtistService>(MockBehavior.Strict)
|
||||
.InSequence(seq)
|
||||
.Setup(x => x.UpdateArtist(It.IsAny<Artist>()))
|
||||
.Returns((Artist a) => a);
|
||||
|
||||
Subject.Execute(new RefreshArtistCommand(_artist.Id));
|
||||
|
||||
// the retained artist gets updated
|
||||
Mocker.GetMock<IArtistService>()
|
||||
.Verify(v => v.UpdateArtist(It.Is<Artist>(s => s.Id == clash.Id)), Times.Exactly(2));
|
||||
|
||||
// the old one gets removed
|
||||
Mocker.GetMock<IArtistService>()
|
||||
.Verify(v => v.DeleteArtist(existing.Id, false, false));
|
||||
|
||||
Mocker.GetMock<IAlbumService>()
|
||||
.Verify(v => v.UpdateMany(It.Is<List<Album>>(x => x.Count == _albums.Count)));
|
||||
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Ignore("This test needs to be re-written as we no longer store albums in artist table or object")]
|
||||
public void should_not_throw_if_duplicate_album_is_in_existing_info()
|
||||
{
|
||||
var newArtistInfo = _artist.JsonClone();
|
||||
newArtistInfo.Albums.Value.Add(Builder<Album>.CreateNew()
|
||||
.With(s => s.ForeignAlbumId = "2")
|
||||
.Build());
|
||||
|
||||
_artist.Albums.Value.Add(Builder<Album>.CreateNew()
|
||||
.With(s => s.ForeignAlbumId = "2")
|
||||
.Build());
|
||||
|
||||
_artist.Albums.Value.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.Value.Count == 2)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Ignore("This test needs to be re-written as we no longer store albums in artist table or object")]
|
||||
public void should_filter_duplicate_albums()
|
||||
{
|
||||
var newArtistInfo = _artist.JsonClone();
|
||||
newArtistInfo.Albums.Value.Add(Builder<Album>.CreateNew()
|
||||
.With(s => s.ForeignAlbumId = "2")
|
||||
.Build());
|
||||
|
||||
newArtistInfo.Albums.Value.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.Value.Count == 2)));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -275,7 +275,6 @@
|
|||
<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\AlbumServiceFixture.cs" />
|
||||
<Compile Include="MusicTests\AddArtistFixture.cs" />
|
||||
<Compile Include="MusicTests\AlbumMonitoredServiceTests\AlbumMonitoredServiceFixture.cs" />
|
||||
|
|
|
@ -28,6 +28,7 @@ namespace NzbDrone.Core.History
|
|||
List<History> Find(string downloadId, HistoryEventType eventType);
|
||||
List<History> FindByDownloadId(string downloadId);
|
||||
List<History> Since(DateTime date, HistoryEventType? eventType);
|
||||
void UpdateMany(IList<History> items);
|
||||
}
|
||||
|
||||
public class HistoryService : IHistoryService,
|
||||
|
@ -381,5 +382,10 @@ namespace NzbDrone.Core.History
|
|||
{
|
||||
return _historyRepository.Since(date, eventType);
|
||||
}
|
||||
|
||||
public void UpdateMany(IList<History> items)
|
||||
{
|
||||
_historyRepository.UpdateMany(items);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
|
||||
{
|
||||
public class ArtistResource
|
||||
{
|
||||
public ArtistResource() {
|
||||
public ArtistResource()
|
||||
{
|
||||
Albums = new List<AlbumResource>();
|
||||
Genres = new List<string>();
|
||||
}
|
||||
|
||||
public List<string> Genres { get; set; }
|
||||
|
|
|
@ -82,10 +82,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
|||
artist.SortName = Parser.Parser.NormalizeTitle(artist.Metadata.Value.Name);
|
||||
|
||||
artist.Albums = FilterAlbums(httpResponse.Resource.Albums, metadataProfileId)
|
||||
.Select(x => new Album {
|
||||
ForeignAlbumId = x.Id
|
||||
})
|
||||
.ToList();
|
||||
.Select(x => MapAlbum(x, null)).ToList();
|
||||
|
||||
return artist;
|
||||
}
|
||||
|
@ -136,6 +133,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
|||
var artists = httpResponse.Resource.Artists.Select(MapArtistMetadata).ToList();
|
||||
var artistDict = artists.ToDictionary(x => x.ForeignArtistId, x => x);
|
||||
var album = MapAlbum(httpResponse.Resource, artistDict);
|
||||
album.ArtistMetadata = artistDict[httpResponse.Resource.ArtistId];
|
||||
|
||||
return new Tuple<string, Album, List<ArtistMetadata>>(httpResponse.Resource.ArtistId, album, artists);
|
||||
}
|
||||
|
@ -181,8 +179,6 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
|||
.SetSegment("route", "search")
|
||||
.AddQueryParam("type", "artist")
|
||||
.AddQueryParam("query", title.ToLower().Trim())
|
||||
//.AddQueryParam("images","false") // Should pass these on import search to avoid looking to fanart and wiki
|
||||
//.AddQueryParam("overview","false")
|
||||
.Build();
|
||||
|
||||
|
||||
|
|
|
@ -1,160 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
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 IReleaseService _releaseService;
|
||||
private readonly IProvideAlbumInfo _albumInfo;
|
||||
private readonly IArtistMetadataRepository _artistMetadataRepository;
|
||||
private readonly IRefreshTrackService _refreshTrackService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public AddAlbumService(IAlbumService albumService,
|
||||
IReleaseService releaseService,
|
||||
IProvideAlbumInfo albumInfo,
|
||||
IArtistMetadataRepository artistMetadataRepository,
|
||||
IRefreshTrackService refreshTrackService,
|
||||
Logger logger)
|
||||
{
|
||||
_albumService = albumService;
|
||||
_releaseService = releaseService;
|
||||
_albumInfo = albumInfo;
|
||||
_artistMetadataRepository = artistMetadataRepository;
|
||||
_refreshTrackService = refreshTrackService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public List<AlbumRelease> AddAlbumReleases(Album album)
|
||||
{
|
||||
var remoteReleases = album.AlbumReleases.Value.DistinctBy(m => m.ForeignReleaseId).ToList();
|
||||
var existingReleases = _releaseService.GetReleasesForRefresh(album.Id, remoteReleases.Select(x => x.ForeignReleaseId));
|
||||
var newReleaseList = new List<AlbumRelease>();
|
||||
var updateReleaseList = new List<AlbumRelease>();
|
||||
|
||||
foreach (var release in remoteReleases)
|
||||
{
|
||||
release.AlbumId = album.Id;
|
||||
release.Album = album;
|
||||
var releaseToRefresh = existingReleases.SingleOrDefault(r => r.ForeignReleaseId == release.ForeignReleaseId);
|
||||
|
||||
if (releaseToRefresh != null)
|
||||
{
|
||||
existingReleases.Remove(releaseToRefresh);
|
||||
|
||||
// copy across the db keys and check for equality
|
||||
release.Id = releaseToRefresh.Id;
|
||||
release.AlbumId = releaseToRefresh.AlbumId;
|
||||
|
||||
updateReleaseList.Add(release);
|
||||
}
|
||||
else
|
||||
{
|
||||
newReleaseList.Add(release);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure only one release is monitored
|
||||
remoteReleases.ForEach(x => x.Monitored = false);
|
||||
remoteReleases.OrderByDescending(x => x.TrackCount).First().Monitored = true;
|
||||
Ensure.That(remoteReleases.Count(x => x.Monitored) == 1).IsTrue();
|
||||
|
||||
// Since this is a new album, we can't be deleting any existing releases
|
||||
_releaseService.UpdateMany(updateReleaseList);
|
||||
_releaseService.InsertMany(newReleaseList);
|
||||
|
||||
return remoteReleases;
|
||||
}
|
||||
|
||||
private Album AddAlbum(Tuple<string, Album, List<ArtistMetadata>> skyHookData)
|
||||
{
|
||||
var newAlbum = skyHookData.Item2;
|
||||
|
||||
if (newAlbum.AlbumReleases.Value.Count == 0)
|
||||
{
|
||||
_logger.Debug($"Skipping album with no valid releases {newAlbum}");
|
||||
return null;
|
||||
}
|
||||
|
||||
_logger.ProgressInfo("Adding Album {0}", newAlbum.Title);
|
||||
|
||||
_artistMetadataRepository.UpsertMany(skyHookData.Item3);
|
||||
newAlbum.ArtistMetadata = _artistMetadataRepository.FindById(skyHookData.Item1);
|
||||
newAlbum.ArtistMetadataId = newAlbum.ArtistMetadata.Value.Id;
|
||||
|
||||
_albumService.AddAlbum(newAlbum);
|
||||
AddAlbumReleases(newAlbum);
|
||||
|
||||
_refreshTrackService.RefreshTrackInfo(newAlbum, false);
|
||||
|
||||
return newAlbum;
|
||||
}
|
||||
|
||||
public Album AddAlbum(Album newAlbum)
|
||||
{
|
||||
Ensure.That(newAlbum, () => newAlbum).IsNotNull();
|
||||
|
||||
var tuple = AddSkyhookData(newAlbum);
|
||||
|
||||
return AddAlbum(tuple);
|
||||
}
|
||||
|
||||
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);
|
||||
tuple.Item2.Added = added;
|
||||
tuple.Item2.LastInfoSync = added;
|
||||
|
||||
albumsToAdd.Add(AddAlbum(tuple));
|
||||
}
|
||||
|
||||
return albumsToAdd;
|
||||
}
|
||||
|
||||
private Tuple<string, Album, List<ArtistMetadata>> AddSkyhookData(Album newAlbum)
|
||||
{
|
||||
Tuple<string, Album, List<ArtistMetadata>> tuple;
|
||||
|
||||
try
|
||||
{
|
||||
tuple = _albumInfo.GetAlbumInfo(newAlbum.ForeignAlbumId);
|
||||
}
|
||||
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.Item2.Monitored = newAlbum.Monitored;
|
||||
tuple.Item2.ProfileId = newAlbum.ProfileId;
|
||||
|
||||
return tuple;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,18 +23,21 @@ namespace NzbDrone.Core.Music
|
|||
public class AddArtistService : IAddArtistService
|
||||
{
|
||||
private readonly IArtistService _artistService;
|
||||
private readonly IArtistMetadataRepository _artistMetadataRepository;
|
||||
private readonly IProvideArtistInfo _artistInfo;
|
||||
private readonly IBuildFileNames _fileNameBuilder;
|
||||
private readonly IAddArtistValidator _addArtistValidator;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public AddArtistService(IArtistService artistService,
|
||||
IArtistMetadataRepository artistMetadataRepository,
|
||||
IProvideArtistInfo artistInfo,
|
||||
IBuildFileNames fileNameBuilder,
|
||||
IAddArtistValidator addArtistValidator,
|
||||
Logger logger)
|
||||
{
|
||||
_artistService = artistService;
|
||||
_artistMetadataRepository = artistMetadataRepository;
|
||||
_artistInfo = artistInfo;
|
||||
_fileNameBuilder = fileNameBuilder;
|
||||
_addArtistValidator = addArtistValidator;
|
||||
|
@ -49,6 +52,12 @@ namespace NzbDrone.Core.Music
|
|||
newArtist = SetPropertiesAndValidate(newArtist);
|
||||
|
||||
_logger.Info("Adding Artist {0} Path: [{1}]", newArtist, newArtist.Path);
|
||||
|
||||
// add metadata
|
||||
_artistMetadataRepository.Upsert(newArtist.Metadata.Value);
|
||||
newArtist.ArtistMetadataId = newArtist.Metadata.Value.Id;
|
||||
|
||||
// add the artist itself
|
||||
_artistService.AddArtist(newArtist);
|
||||
|
||||
return newArtist;
|
||||
|
@ -77,6 +86,12 @@ namespace NzbDrone.Core.Music
|
|||
|
||||
}
|
||||
|
||||
// add metadata
|
||||
_artistMetadataRepository.UpsertMany(artistsToAdd);
|
||||
|
||||
_logger.Debug("metadata id 1 {0}", string.Join(", ", artistsToAdd.Select(x => x.Metadata.Value.Id)));
|
||||
_logger.Debug("metadata id 2 {0}", string.Join(", ", artistsToAdd.Select(x => x.ArtistMetadataId)));
|
||||
|
||||
return _artistService.AddArtists(artistsToAdd);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,10 +3,12 @@ using NzbDrone.Core.Datastore;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Marr.Data;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Serializer;
|
||||
|
||||
namespace NzbDrone.Core.Music
|
||||
{
|
||||
public class Album : ModelBase
|
||||
public class Album : ModelBase, IEquatable<Album>
|
||||
{
|
||||
public Album()
|
||||
{
|
||||
|
@ -66,5 +68,72 @@ namespace NzbDrone.Core.Music
|
|||
Monitored = otherAlbum.Monitored;
|
||||
AnyReleaseOk = otherAlbum.AnyReleaseOk;
|
||||
}
|
||||
|
||||
public bool Equals(Album other)
|
||||
{
|
||||
if (other == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Id == other.Id &&
|
||||
ForeignAlbumId == other.ForeignAlbumId &&
|
||||
(OldForeignAlbumIds?.SequenceEqual(other.OldForeignAlbumIds) ?? true) &&
|
||||
Title == other.Title &&
|
||||
Overview == other.Overview &&
|
||||
Disambiguation == other.Disambiguation &&
|
||||
ReleaseDate == other.ReleaseDate &&
|
||||
Images?.ToJson() == other.Images?.ToJson() &&
|
||||
Links?.ToJson() == other.Links?.ToJson() &&
|
||||
(Genres?.SequenceEqual(other.Genres) ?? true) &&
|
||||
AlbumType == other.AlbumType &&
|
||||
(SecondaryTypes?.SequenceEqual(other.SecondaryTypes) ?? true) &&
|
||||
Ratings?.ToJson() == other.Ratings?.ToJson())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var other = obj as Album;
|
||||
if (other == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Equals(other);
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hash = 17;
|
||||
hash = hash * 23 + Id;
|
||||
hash = hash * 23 + ForeignAlbumId.GetHashCode();
|
||||
hash = hash * 23 + OldForeignAlbumIds?.GetHashCode() ?? 0;
|
||||
hash = hash * 23 + Title?.GetHashCode() ?? 0;
|
||||
hash = hash * 23 + Overview?.GetHashCode() ?? 0;
|
||||
hash = hash * 23 + Disambiguation?.GetHashCode() ?? 0;
|
||||
hash = hash * 23 + ReleaseDate?.GetHashCode() ?? 0;
|
||||
hash = hash * 23 + Images?.GetHashCode() ?? 0;
|
||||
hash = hash * 23 + Links?.GetHashCode() ?? 0;
|
||||
hash = hash * 23 + Genres?.GetHashCode() ?? 0;
|
||||
hash = hash * 23 + AlbumType?.GetHashCode() ?? 0;
|
||||
hash = hash * 23 + SecondaryTypes?.GetHashCode() ?? 0;
|
||||
hash = hash * 23 + Ratings?.GetHashCode() ?? 0;
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using Marr.Data;
|
||||
using NLog;
|
||||
|
||||
namespace NzbDrone.Core.Music
|
||||
|
@ -16,8 +15,8 @@ namespace NzbDrone.Core.Music
|
|||
void UpdateMany(List<Artist> artists);
|
||||
ArtistMetadata FindById(string foreignArtistId);
|
||||
List<ArtistMetadata> FindById(List<string> foreignIds);
|
||||
void UpsertMany(List<ArtistMetadata> artists);
|
||||
void UpsertMany(List<Artist> artists);
|
||||
bool UpsertMany(List<ArtistMetadata> artists);
|
||||
bool UpsertMany(List<Artist> artists);
|
||||
}
|
||||
|
||||
public class ArtistMetadataRepository : BasicRepository<ArtistMetadata>, IArtistMetadataRepository
|
||||
|
@ -40,10 +39,8 @@ namespace NzbDrone.Core.Music
|
|||
public List<Artist> InsertMany(List<Artist> artists)
|
||||
{
|
||||
InsertMany(artists.Select(x => x.Metadata.Value).ToList());
|
||||
foreach (var artist in artists)
|
||||
{
|
||||
artist.ArtistMetadataId = artist.Metadata.Value.Id;
|
||||
}
|
||||
artists.ForEach(x => x.ArtistMetadataId = x.Metadata.Value.Id);
|
||||
|
||||
return artists;
|
||||
}
|
||||
|
||||
|
@ -70,12 +67,12 @@ namespace NzbDrone.Core.Music
|
|||
return artist;
|
||||
}
|
||||
|
||||
public void UpsertMany(List<Artist> artists)
|
||||
public bool UpsertMany(List<Artist> artists)
|
||||
{
|
||||
foreach (var artist in artists)
|
||||
{
|
||||
Upsert(artist);
|
||||
}
|
||||
var result = UpsertMany(artists.Select(x => x.Metadata.Value).ToList());
|
||||
artists.ForEach(x => x.ArtistMetadataId = x.Metadata.Value.Id);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void UpdateMany(List<Artist> artists)
|
||||
|
@ -93,22 +90,40 @@ namespace NzbDrone.Core.Music
|
|||
return Query.Where($"[ForeignArtistId] IN ('{string.Join("','", foreignIds)}')").ToList();
|
||||
}
|
||||
|
||||
public void UpsertMany(List<ArtistMetadata> artists)
|
||||
public bool UpsertMany(List<ArtistMetadata> data)
|
||||
{
|
||||
foreach (var artist in artists)
|
||||
var existingMetadata = FindById(data.Select(x => x.ForeignArtistId).ToList());
|
||||
var updateMetadataList = new List<ArtistMetadata>();
|
||||
var addMetadataList = new List<ArtistMetadata>();
|
||||
int upToDateMetadataCount = 0;
|
||||
|
||||
foreach (var meta in data)
|
||||
{
|
||||
var existing = FindById(artist.ForeignArtistId);
|
||||
var existing = existingMetadata.SingleOrDefault(x => x.ForeignArtistId == meta.ForeignArtistId);
|
||||
if (existing != null)
|
||||
{
|
||||
artist.Id = existing.Id;
|
||||
Update(artist);
|
||||
meta.Id = existing.Id;
|
||||
if (!meta.Equals(existing))
|
||||
{
|
||||
updateMetadataList.Add(meta);
|
||||
}
|
||||
else
|
||||
{
|
||||
upToDateMetadataCount++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Insert(artist);
|
||||
addMetadataList.Add(meta);
|
||||
}
|
||||
_logger.Debug("Upserted metadata with ID {0}", artist.Id);
|
||||
}
|
||||
|
||||
UpdateMany(updateMetadataList);
|
||||
InsertMany(addMetadataList);
|
||||
|
||||
_logger.Debug($"{upToDateMetadataCount} artist metadata up to date; Updating {updateMetadataList.Count}, Adding {addMetadataList.Count} artist metadata entries.");
|
||||
|
||||
return updateMetadataList.Count > 0 || addMetadataList.Count > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,6 @@ namespace NzbDrone.Core.Music
|
|||
public class ArtistService : IArtistService
|
||||
{
|
||||
private readonly IArtistRepository _artistRepository;
|
||||
private readonly IArtistMetadataRepository _artistMetadataRepository;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly ITrackService _trackService;
|
||||
private readonly IImportListExclusionService _importListExclusionService;
|
||||
|
@ -43,7 +42,6 @@ namespace NzbDrone.Core.Music
|
|||
private readonly ICached<List<Artist>> _cache;
|
||||
|
||||
public ArtistService(IArtistRepository artistRepository,
|
||||
IArtistMetadataRepository artistMetadataRepository,
|
||||
IEventAggregator eventAggregator,
|
||||
ITrackService trackService,
|
||||
IImportListExclusionService importListExclusionService,
|
||||
|
@ -52,7 +50,6 @@ namespace NzbDrone.Core.Music
|
|||
Logger logger)
|
||||
{
|
||||
_artistRepository = artistRepository;
|
||||
_artistMetadataRepository = artistMetadataRepository;
|
||||
_eventAggregator = eventAggregator;
|
||||
_trackService = trackService;
|
||||
_importListExclusionService = importListExclusionService;
|
||||
|
@ -64,7 +61,6 @@ namespace NzbDrone.Core.Music
|
|||
public Artist AddArtist(Artist newArtist)
|
||||
{
|
||||
_cache.Clear();
|
||||
_artistMetadataRepository.Upsert(newArtist);
|
||||
_artistRepository.Insert(newArtist);
|
||||
_eventAggregator.PublishEvent(new ArtistAddedEvent(GetArtist(newArtist.Id)));
|
||||
|
||||
|
@ -74,7 +70,6 @@ namespace NzbDrone.Core.Music
|
|||
public List<Artist> AddArtists(List<Artist> newArtists)
|
||||
{
|
||||
_cache.Clear();
|
||||
_artistMetadataRepository.UpsertMany(newArtists);
|
||||
_artistRepository.InsertMany(newArtists);
|
||||
_eventAggregator.PublishEvent(new ArtistsImportedEvent(newArtists.Select(s => s.Id).ToList()));
|
||||
|
||||
|
@ -211,9 +206,8 @@ namespace NzbDrone.Core.Music
|
|||
public Artist UpdateArtist(Artist artist)
|
||||
{
|
||||
_cache.Clear();
|
||||
var storedArtist = GetArtist(artist.Id); // Is it Id or iTunesId?
|
||||
var updatedArtist = _artistMetadataRepository.Update(artist);
|
||||
updatedArtist = _artistRepository.Update(updatedArtist);
|
||||
var storedArtist = GetArtist(artist.Id);
|
||||
var updatedArtist = _artistRepository.Update(artist);
|
||||
_eventAggregator.PublishEvent(new ArtistEditedEvent(updatedArtist, storedArtist));
|
||||
|
||||
return updatedArtist;
|
||||
|
@ -242,7 +236,6 @@ namespace NzbDrone.Core.Music
|
|||
}
|
||||
}
|
||||
|
||||
_artistMetadataRepository.UpdateMany(artist);
|
||||
_artistRepository.UpdateMany(artist);
|
||||
_logger.Debug("{0} artists updated", artist.Count);
|
||||
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
|
||||
namespace NzbDrone.Core.Music
|
||||
{
|
||||
public interface IRefreshAlbumReleaseService
|
||||
{
|
||||
bool RefreshEntityInfo(AlbumRelease entity, List<AlbumRelease> remoteEntityList, bool forceChildRefresh, bool forceUpdateFileTags);
|
||||
bool RefreshEntityInfo(List<AlbumRelease> releases, List<AlbumRelease> remoteEntityList, bool forceChildRefresh, bool forceUpdateFileTags);
|
||||
}
|
||||
|
||||
public class RefreshAlbumReleaseService : RefreshEntityServiceBase<AlbumRelease, Track>, IRefreshAlbumReleaseService
|
||||
{
|
||||
private readonly IReleaseService _releaseService;
|
||||
private readonly IRefreshTrackService _refreshTrackService;
|
||||
private readonly ITrackService _trackService;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public RefreshAlbumReleaseService(IReleaseService releaseService,
|
||||
IArtistMetadataRepository artistMetadataRepository,
|
||||
IRefreshTrackService refreshTrackService,
|
||||
ITrackService trackService,
|
||||
IMediaFileService mediaFileService,
|
||||
Logger logger)
|
||||
: base(logger, artistMetadataRepository)
|
||||
{
|
||||
_releaseService = releaseService;
|
||||
_trackService = trackService;
|
||||
_refreshTrackService = refreshTrackService;
|
||||
_mediaFileService = mediaFileService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override RemoteData GetRemoteData(AlbumRelease local, List<AlbumRelease> remote)
|
||||
{
|
||||
var result = new RemoteData();
|
||||
result.Entity = remote.SingleOrDefault(x => x.ForeignReleaseId == local.ForeignReleaseId || x.OldForeignReleaseIds.Contains(local.ForeignReleaseId));
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override bool IsMerge(AlbumRelease local, AlbumRelease remote)
|
||||
{
|
||||
return local.ForeignReleaseId != remote.ForeignReleaseId;
|
||||
}
|
||||
|
||||
protected override UpdateResult UpdateEntity(AlbumRelease local, AlbumRelease remote)
|
||||
{
|
||||
if (local.Equals(remote))
|
||||
{
|
||||
return UpdateResult.None;
|
||||
}
|
||||
|
||||
local.OldForeignReleaseIds = remote.OldForeignReleaseIds;
|
||||
local.Title = remote.Title;
|
||||
local.Status = remote.Status;
|
||||
local.Duration = remote.Duration;
|
||||
local.Label = remote.Label;
|
||||
local.Disambiguation = remote.Disambiguation;
|
||||
local.Country = remote.Country;
|
||||
local.ReleaseDate = remote.ReleaseDate;
|
||||
local.Media = remote.Media;
|
||||
local.TrackCount = remote.TrackCount;
|
||||
|
||||
return UpdateResult.UpdateTags;
|
||||
}
|
||||
|
||||
protected override AlbumRelease GetEntityByForeignId(AlbumRelease local)
|
||||
{
|
||||
return _releaseService.GetReleaseByForeignReleaseId(local.ForeignReleaseId);
|
||||
}
|
||||
|
||||
protected override void SaveEntity(AlbumRelease local)
|
||||
{
|
||||
_releaseService.UpdateMany(new List<AlbumRelease> { local });
|
||||
}
|
||||
|
||||
protected override void DeleteEntity(AlbumRelease local, bool deleteFiles)
|
||||
{
|
||||
_releaseService.DeleteMany(new List<AlbumRelease> { local });
|
||||
}
|
||||
|
||||
protected override List<Track> GetRemoteChildren(AlbumRelease remote)
|
||||
{
|
||||
return remote.Tracks.Value.DistinctBy(m => m.ForeignTrackId).ToList();
|
||||
}
|
||||
|
||||
protected override List<Track> GetLocalChildren(AlbumRelease entity, List<Track> remoteChildren)
|
||||
{
|
||||
return _trackService.GetTracksForRefresh(entity.Id,
|
||||
remoteChildren.Select(x => x.ForeignTrackId)
|
||||
.Concat(remoteChildren.SelectMany(x => x.OldForeignTrackIds)));
|
||||
}
|
||||
|
||||
protected override Tuple<Track, List<Track> > GetMatchingExistingChildren(List<Track> existingChildren, Track remote)
|
||||
{
|
||||
var existingChild = existingChildren.SingleOrDefault(x => x.ForeignTrackId == remote.ForeignTrackId);
|
||||
var mergeChildren = existingChildren.Where(x => remote.OldForeignTrackIds.Contains(x.ForeignTrackId)).ToList();
|
||||
return Tuple.Create(existingChild, mergeChildren);
|
||||
}
|
||||
|
||||
protected override void PrepareNewChild(Track child, AlbumRelease entity)
|
||||
{
|
||||
child.AlbumReleaseId = entity.Id;
|
||||
child.AlbumRelease = entity;
|
||||
child.ArtistMetadataId = child.ArtistMetadata.Value.Id;
|
||||
|
||||
// make sure title is not null
|
||||
child.Title = child.Title ?? "Unknown";
|
||||
}
|
||||
|
||||
protected override void PrepareExistingChild(Track local, Track remote, AlbumRelease entity)
|
||||
{
|
||||
local.AlbumRelease = entity;
|
||||
local.AlbumReleaseId = entity.Id;
|
||||
local.ArtistMetadataId = remote.ArtistMetadata.Value.Id;
|
||||
remote.Id = local.Id;
|
||||
remote.TrackFileId = local.TrackFileId;
|
||||
remote.AlbumReleaseId = local.AlbumReleaseId;
|
||||
remote.ArtistMetadataId = local.ArtistMetadataId;
|
||||
}
|
||||
|
||||
protected override void AddChildren(List<Track> children)
|
||||
{
|
||||
_trackService.InsertMany(children);
|
||||
}
|
||||
|
||||
protected override bool RefreshChildren(SortedChildren localChildren, List<Track> remoteChildren, bool forceChildRefresh, bool forceUpdateFileTags)
|
||||
{
|
||||
return _refreshTrackService.RefreshTrackInfo(localChildren.Added, localChildren.Updated, localChildren.Merged, localChildren.Deleted, localChildren.UpToDate, remoteChildren, forceUpdateFileTags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,238 +11,314 @@ using NzbDrone.Core.Exceptions;
|
|||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Music.Commands;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using NzbDrone.Core.History;
|
||||
|
||||
namespace NzbDrone.Core.Music
|
||||
{
|
||||
public interface IRefreshAlbumService
|
||||
{
|
||||
bool RefreshAlbumInfo(Album album, bool forceUpdateFileTags);
|
||||
bool RefreshAlbumInfo(List<Album> albums, bool forceAlbumRefresh, bool forceUpdateFileTags);
|
||||
bool RefreshAlbumInfo(Album album, List<Album> remoteAlbums, bool forceUpdateFileTags);
|
||||
bool RefreshAlbumInfo(List<Album> albums, List<Album> remoteAlbums, bool forceAlbumRefresh, bool forceUpdateFileTags);
|
||||
}
|
||||
|
||||
public class RefreshAlbumService : IRefreshAlbumService, IExecute<RefreshAlbumCommand>
|
||||
public class RefreshAlbumService : RefreshEntityServiceBase<Album, AlbumRelease>, IRefreshAlbumService, IExecute<RefreshAlbumCommand>
|
||||
{
|
||||
private readonly IAlbumService _albumService;
|
||||
private readonly IArtistService _artistService;
|
||||
private readonly IArtistMetadataRepository _artistMetadataRepository;
|
||||
private readonly IAddArtistService _addArtistService;
|
||||
private readonly IReleaseService _releaseService;
|
||||
private readonly IProvideAlbumInfo _albumInfo;
|
||||
private readonly IRefreshTrackService _refreshTrackService;
|
||||
private readonly IAudioTagService _audioTagService;
|
||||
private readonly IRefreshAlbumReleaseService _refreshAlbumReleaseService;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IHistoryService _historyService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly ICheckIfAlbumShouldBeRefreshed _checkIfAlbumShouldBeRefreshed;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public RefreshAlbumService(IAlbumService albumService,
|
||||
IArtistService artistService,
|
||||
IAddArtistService addArtistService,
|
||||
IArtistMetadataRepository artistMetadataRepository,
|
||||
IReleaseService releaseService,
|
||||
IProvideAlbumInfo albumInfo,
|
||||
IRefreshTrackService refreshTrackService,
|
||||
IAudioTagService audioTagService,
|
||||
IRefreshAlbumReleaseService refreshAlbumReleaseService,
|
||||
IMediaFileService mediaFileService,
|
||||
IHistoryService historyService,
|
||||
IEventAggregator eventAggregator,
|
||||
ICheckIfAlbumShouldBeRefreshed checkIfAlbumShouldBeRefreshed,
|
||||
Logger logger)
|
||||
: base(logger, artistMetadataRepository)
|
||||
{
|
||||
_albumService = albumService;
|
||||
_artistService = artistService;
|
||||
_artistMetadataRepository = artistMetadataRepository;
|
||||
_addArtistService = addArtistService;
|
||||
_releaseService = releaseService;
|
||||
_albumInfo = albumInfo;
|
||||
_refreshTrackService = refreshTrackService;
|
||||
_audioTagService = audioTagService;
|
||||
_refreshAlbumReleaseService = refreshAlbumReleaseService;
|
||||
_mediaFileService = mediaFileService;
|
||||
_historyService = historyService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_checkIfAlbumShouldBeRefreshed = checkIfAlbumShouldBeRefreshed;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public bool RefreshAlbumInfo(List<Album> albums, bool forceAlbumRefresh, bool forceUpdateFileTags)
|
||||
protected override RemoteData GetRemoteData(Album local, List<Album> remote)
|
||||
{
|
||||
var result = new RemoteData();
|
||||
|
||||
// remove not in remote list and ShouldDelete is true
|
||||
if (remote != null &&
|
||||
!remote.Any(x => x.ForeignAlbumId == local.ForeignAlbumId || x.OldForeignAlbumIds.Contains(local.ForeignAlbumId)) &&
|
||||
ShouldDelete(local))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
Tuple<string, Album, List<ArtistMetadata>> tuple = null;
|
||||
try
|
||||
{
|
||||
tuple = _albumInfo.GetAlbumInfo(local.ForeignAlbumId);
|
||||
}
|
||||
catch (AlbumNotFoundException)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (tuple.Item2.AlbumReleases.Value.Count == 0)
|
||||
{
|
||||
_logger.Debug($"{local} has no valid releases, removing.");
|
||||
return result;
|
||||
}
|
||||
|
||||
result.Entity = tuple.Item2;
|
||||
result.Metadata = tuple.Item3;
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override void EnsureNewParent(Album local, Album remote)
|
||||
{
|
||||
// Make sure the appropriate artist exists (it could be that an album changes parent)
|
||||
// The artistMetadata entry will be in the db but make sure a corresponding artist is too
|
||||
// so that the album doesn't just disappear.
|
||||
|
||||
// TODO filter by metadata id before hitting database
|
||||
_logger.Trace($"Ensuring parent artist exists [{remote.ArtistMetadata.Value.ForeignArtistId}]");
|
||||
|
||||
var newArtist = _artistService.FindById(remote.ArtistMetadata.Value.ForeignArtistId);
|
||||
|
||||
if (newArtist == null)
|
||||
{
|
||||
var oldArtist = local.Artist.Value;
|
||||
var addArtist = new Artist {
|
||||
Metadata = remote.ArtistMetadata.Value,
|
||||
MetadataProfileId = oldArtist.MetadataProfileId,
|
||||
QualityProfileId = oldArtist.QualityProfileId,
|
||||
LanguageProfileId = oldArtist.LanguageProfileId,
|
||||
RootFolderPath = oldArtist.RootFolderPath,
|
||||
Monitored = oldArtist.Monitored,
|
||||
AlbumFolder = oldArtist.AlbumFolder
|
||||
};
|
||||
_logger.Debug($"Adding missing parent artist {addArtist}");
|
||||
_addArtistService.AddArtist(addArtist);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool ShouldDelete(Album local)
|
||||
{
|
||||
return !_mediaFileService.GetFilesByAlbum(local.Id).Any();
|
||||
}
|
||||
|
||||
protected override void LogProgress(Album local)
|
||||
{
|
||||
_logger.ProgressInfo("Updating Info for {0}", local.Title);
|
||||
}
|
||||
|
||||
protected override bool IsMerge(Album local, Album remote)
|
||||
{
|
||||
return local.ForeignAlbumId != remote.ForeignAlbumId;
|
||||
}
|
||||
|
||||
protected override UpdateResult UpdateEntity(Album local, Album remote)
|
||||
{
|
||||
UpdateResult result;
|
||||
|
||||
if (local.Title != (remote.Title ?? "Unknown") ||
|
||||
local.ForeignAlbumId != remote.ForeignAlbumId ||
|
||||
local.ArtistMetadata.Value.ForeignArtistId != remote.ArtistMetadata.Value.ForeignArtistId)
|
||||
{
|
||||
result = UpdateResult.UpdateTags;
|
||||
}
|
||||
else if (!local.Equals(remote))
|
||||
{
|
||||
result = UpdateResult.Standard;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = UpdateResult.None;
|
||||
}
|
||||
|
||||
local.ArtistMetadataId = remote.ArtistMetadata.Value.Id;
|
||||
local.ForeignAlbumId = remote.ForeignAlbumId;
|
||||
local.OldForeignAlbumIds = remote.OldForeignAlbumIds;
|
||||
local.LastInfoSync = DateTime.UtcNow;
|
||||
local.CleanTitle = remote.CleanTitle;
|
||||
local.Title = remote.Title ?? "Unknown";
|
||||
local.Overview = remote.Overview.IsNullOrWhiteSpace() ? local.Overview : remote.Overview;
|
||||
local.Disambiguation = remote.Disambiguation;
|
||||
local.AlbumType = remote.AlbumType;
|
||||
local.SecondaryTypes = remote.SecondaryTypes;
|
||||
local.Genres = remote.Genres;
|
||||
local.Images = remote.Images.Any() ? remote.Images : local.Images;
|
||||
local.Links = remote.Links;
|
||||
local.ReleaseDate = remote.ReleaseDate;
|
||||
local.Ratings = remote.Ratings;
|
||||
local.AlbumReleases = new List<AlbumRelease>();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override UpdateResult MergeEntity(Album local, Album target, Album remote)
|
||||
{
|
||||
_logger.Warn($"Album {local} was merged with {remote} because the original was a duplicate.");
|
||||
|
||||
// move releases over to the new album and delete
|
||||
var localReleases = _releaseService.GetReleasesByAlbum(local.Id);
|
||||
var allReleases = localReleases.Concat(_releaseService.GetReleasesByAlbum(target.Id)).ToList();
|
||||
_logger.Trace($"Moving {localReleases.Count} releases from {local} to {remote}");
|
||||
|
||||
// Update album ID and unmonitor all releases from the old album
|
||||
allReleases.ForEach(x => x.AlbumId = target.Id);
|
||||
MonitorSingleRelease(allReleases);
|
||||
_releaseService.UpdateMany(allReleases);
|
||||
|
||||
// Update album ids for trackfiles
|
||||
var files = _mediaFileService.GetFilesByAlbum(local.Id);
|
||||
files.ForEach(x => x.AlbumId = target.Id);
|
||||
_mediaFileService.Update(files);
|
||||
|
||||
// Update album ids for history
|
||||
var items = _historyService.GetByAlbum(local.Id, null);
|
||||
items.ForEach(x => x.AlbumId = target.Id);
|
||||
_historyService.UpdateMany(items);
|
||||
|
||||
// Finally delete the old album
|
||||
_albumService.DeleteMany(new List<Album> { local });
|
||||
|
||||
return UpdateResult.UpdateTags;
|
||||
}
|
||||
|
||||
protected override Album GetEntityByForeignId(Album local)
|
||||
{
|
||||
return _albumService.FindById(local.ForeignAlbumId);
|
||||
}
|
||||
|
||||
protected override void SaveEntity(Album local)
|
||||
{
|
||||
// Use UpdateMany to avoid firing the album edited event
|
||||
_albumService.UpdateMany(new List<Album> { local });
|
||||
}
|
||||
|
||||
protected override void DeleteEntity(Album local, bool deleteFiles)
|
||||
{
|
||||
_albumService.DeleteAlbum(local.Id, true);
|
||||
}
|
||||
|
||||
protected override List<AlbumRelease> GetRemoteChildren(Album remote)
|
||||
{
|
||||
return remote.AlbumReleases.Value.DistinctBy(m => m.ForeignReleaseId).ToList();
|
||||
}
|
||||
|
||||
protected override List<AlbumRelease> GetLocalChildren(Album entity, List<AlbumRelease> remoteChildren)
|
||||
{
|
||||
var children = _releaseService.GetReleasesForRefresh(entity.Id,
|
||||
remoteChildren.Select(x => x.ForeignReleaseId)
|
||||
.Concat(remoteChildren.SelectMany(x => x.OldForeignReleaseIds)));
|
||||
|
||||
// Make sure trackfiles point to the new album where we are grabbing a release from another album
|
||||
var files = new List<TrackFile>();
|
||||
foreach (var release in children.Where(x => x.AlbumId != entity.Id))
|
||||
{
|
||||
files.AddRange(_mediaFileService.GetFilesByRelease(release.Id));
|
||||
}
|
||||
files.ForEach(x => x.AlbumId = entity.Id);
|
||||
_mediaFileService.Update(files);
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
protected override Tuple<AlbumRelease, List<AlbumRelease> > GetMatchingExistingChildren(List<AlbumRelease> existingChildren, AlbumRelease remote)
|
||||
{
|
||||
var existingChild = existingChildren.SingleOrDefault(x => x.ForeignReleaseId == remote.ForeignReleaseId);
|
||||
var mergeChildren = existingChildren.Where(x => remote.OldForeignReleaseIds.Contains(x.ForeignReleaseId)).ToList();
|
||||
return Tuple.Create(existingChild, mergeChildren);
|
||||
}
|
||||
|
||||
protected override void PrepareNewChild(AlbumRelease child, Album entity)
|
||||
{
|
||||
child.AlbumId = entity.Id;
|
||||
child.Album = entity;
|
||||
}
|
||||
|
||||
protected override void PrepareExistingChild(AlbumRelease local, AlbumRelease remote, Album entity)
|
||||
{
|
||||
local.AlbumId = entity.Id;
|
||||
local.Album = entity;
|
||||
remote.Id = local.Id;
|
||||
remote.Album = entity;
|
||||
remote.AlbumId = entity.Id;
|
||||
remote.Monitored = local.Monitored;
|
||||
}
|
||||
|
||||
protected override void AddChildren(List<AlbumRelease> children)
|
||||
{
|
||||
_releaseService.InsertMany(children);
|
||||
}
|
||||
|
||||
private void MonitorSingleRelease(List<AlbumRelease> releases)
|
||||
{
|
||||
var monitored = releases.Where(x => x.Monitored).ToList();
|
||||
if (!monitored.Any())
|
||||
{
|
||||
monitored = releases;
|
||||
}
|
||||
|
||||
var toMonitor = monitored.OrderByDescending(x => _mediaFileService.GetFilesByRelease(x.Id).Count)
|
||||
.ThenByDescending(x => x.TrackCount)
|
||||
.First();
|
||||
|
||||
releases.ForEach(x => x.Monitored = false);
|
||||
toMonitor.Monitored = true;
|
||||
}
|
||||
|
||||
protected override bool RefreshChildren(SortedChildren localChildren, List<AlbumRelease> remoteChildren, bool forceChildRefresh, bool forceUpdateFileTags)
|
||||
{
|
||||
var refreshList = localChildren.All;
|
||||
|
||||
// make sure only one of the releases ends up monitored
|
||||
localChildren.Old.ForEach(x => x.Monitored = false);
|
||||
MonitorSingleRelease(localChildren.Future);
|
||||
|
||||
refreshList.ForEach(x => _logger.Trace($"release: {x} monitored: {x.Monitored}"));
|
||||
|
||||
return _refreshAlbumReleaseService.RefreshEntityInfo(refreshList, remoteChildren, forceChildRefresh, forceUpdateFileTags);
|
||||
}
|
||||
|
||||
public bool RefreshAlbumInfo(List<Album> albums, List<Album> remoteAlbums, bool forceAlbumRefresh, bool forceUpdateFileTags)
|
||||
{
|
||||
bool updated = false;
|
||||
foreach (var album in albums)
|
||||
{
|
||||
if (forceAlbumRefresh || _checkIfAlbumShouldBeRefreshed.ShouldRefresh(album))
|
||||
{
|
||||
updated |= RefreshAlbumInfo(album, forceUpdateFileTags);
|
||||
updated |= RefreshAlbumInfo(album, remoteAlbums, forceUpdateFileTags);
|
||||
}
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
public bool RefreshAlbumInfo(Album album, bool forceUpdateFileTags)
|
||||
public bool RefreshAlbumInfo(Album album, List<Album> remoteAlbums, bool forceUpdateFileTags)
|
||||
{
|
||||
_logger.ProgressInfo("Updating Info for {0}", album.Title);
|
||||
bool updated = false;
|
||||
|
||||
Tuple<string, Album, List<ArtistMetadata>> tuple;
|
||||
|
||||
try
|
||||
{
|
||||
tuple = _albumInfo.GetAlbumInfo(album.ForeignAlbumId);
|
||||
}
|
||||
catch (AlbumNotFoundException)
|
||||
{
|
||||
_logger.Error($"{album} was not found, it may have been removed from Metadata sources.");
|
||||
return updated;
|
||||
}
|
||||
|
||||
if (tuple.Item2.AlbumReleases.Value.Count == 0)
|
||||
{
|
||||
_logger.Debug($"{album} has no valid releases, removing.");
|
||||
_albumService.DeleteMany(new List<Album> { album });
|
||||
return true;
|
||||
}
|
||||
|
||||
var remoteMetadata = tuple.Item3.DistinctBy(x => x.ForeignArtistId).ToList();
|
||||
var existingMetadata = _artistMetadataRepository.FindById(remoteMetadata.Select(x => x.ForeignArtistId).ToList());
|
||||
var updateMetadataList = new List<ArtistMetadata>();
|
||||
var addMetadataList = new List<ArtistMetadata>();
|
||||
var upToDateMetadataCount = 0;
|
||||
|
||||
foreach (var meta in remoteMetadata)
|
||||
{
|
||||
var existing = existingMetadata.SingleOrDefault(x => x.ForeignArtistId == meta.ForeignArtistId);
|
||||
if (existing != null)
|
||||
{
|
||||
meta.Id = existing.Id;
|
||||
if (!meta.Equals(existing))
|
||||
{
|
||||
updateMetadataList.Add(meta);
|
||||
}
|
||||
else
|
||||
{
|
||||
upToDateMetadataCount++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
addMetadataList.Add(meta);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Debug($"{album}: {upToDateMetadataCount} artist metadata up to date; Updating {updateMetadataList.Count}, Adding {addMetadataList.Count} artist metadata entries.");
|
||||
|
||||
_artistMetadataRepository.UpdateMany(updateMetadataList);
|
||||
_artistMetadataRepository.InsertMany(addMetadataList);
|
||||
|
||||
forceUpdateFileTags |= updateMetadataList.Any();
|
||||
updated |= updateMetadataList.Any() || addMetadataList.Any();
|
||||
|
||||
var albumInfo = tuple.Item2;
|
||||
|
||||
if (album.ForeignAlbumId != albumInfo.ForeignAlbumId)
|
||||
{
|
||||
_logger.Warn(
|
||||
"Album '{0}' (Album {1}) was replaced with '{2}' (LidarrAPI {3}), because the original was a duplicate.",
|
||||
album.Title, album.ForeignAlbumId, albumInfo.Title, albumInfo.ForeignAlbumId);
|
||||
album.ForeignAlbumId = albumInfo.ForeignAlbumId;
|
||||
}
|
||||
|
||||
// the only thing written to tags from the album object is the title
|
||||
forceUpdateFileTags |= album.Title != (albumInfo.Title ?? "Unknown");
|
||||
updated |= forceUpdateFileTags;
|
||||
|
||||
album.OldForeignAlbumIds = albumInfo.OldForeignAlbumIds;
|
||||
album.LastInfoSync = DateTime.UtcNow;
|
||||
album.CleanTitle = albumInfo.CleanTitle;
|
||||
album.Title = albumInfo.Title ?? "Unknown";
|
||||
album.Overview = albumInfo.Overview.IsNullOrWhiteSpace() ? album.Overview : albumInfo.Overview;
|
||||
album.Disambiguation = albumInfo.Disambiguation;
|
||||
album.AlbumType = albumInfo.AlbumType;
|
||||
album.SecondaryTypes = albumInfo.SecondaryTypes;
|
||||
album.Genres = albumInfo.Genres;
|
||||
album.Images = albumInfo.Images.Any() ? albumInfo.Images : album.Images;
|
||||
album.Links = albumInfo.Links;
|
||||
album.ReleaseDate = albumInfo.ReleaseDate;
|
||||
album.Ratings = albumInfo.Ratings;
|
||||
album.AlbumReleases = new List<AlbumRelease>();
|
||||
|
||||
var remoteReleases = albumInfo.AlbumReleases.Value.DistinctBy(m => m.ForeignReleaseId).ToList();
|
||||
var existingReleases = _releaseService.GetReleasesForRefresh(album.Id, remoteReleases.Select(x => x.ForeignReleaseId));
|
||||
// Keep track of which existing release we want to end up monitored
|
||||
var existingToMonitor = existingReleases.Where(x => x.Monitored).OrderByDescending(x => x.TrackCount).FirstOrDefault();
|
||||
|
||||
var newReleaseList = new List<AlbumRelease>();
|
||||
var updateReleaseList = new List<AlbumRelease>();
|
||||
var upToDateReleaseList = new List<AlbumRelease>();
|
||||
|
||||
foreach (var release in remoteReleases)
|
||||
{
|
||||
release.AlbumId = album.Id;
|
||||
release.Album = album;
|
||||
|
||||
// force to unmonitored, then fix monitored one later
|
||||
// once we have made sure that it's unique. This make sure
|
||||
// that we unmonitor anything in database that shouldn't be monitored.
|
||||
release.Monitored = false;
|
||||
|
||||
var releaseToRefresh = existingReleases.SingleOrDefault(r => r.ForeignReleaseId == release.ForeignReleaseId);
|
||||
|
||||
if (releaseToRefresh != null)
|
||||
{
|
||||
existingReleases.Remove(releaseToRefresh);
|
||||
|
||||
// copy across the db keys and check for equality
|
||||
release.Id = releaseToRefresh.Id;
|
||||
release.AlbumId = releaseToRefresh.AlbumId;
|
||||
|
||||
if (!releaseToRefresh.Equals(release))
|
||||
{
|
||||
updateReleaseList.Add(release);
|
||||
}
|
||||
else
|
||||
{
|
||||
upToDateReleaseList.Add(release);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newReleaseList.Add(release);
|
||||
}
|
||||
|
||||
album.AlbumReleases.Value.Add(release);
|
||||
}
|
||||
|
||||
var refreshedToMonitor = remoteReleases.SingleOrDefault(x => x.ForeignReleaseId == existingToMonitor?.ForeignReleaseId) ??
|
||||
remoteReleases.OrderByDescending(x => x.TrackCount).First();
|
||||
refreshedToMonitor.Monitored = true;
|
||||
|
||||
if (upToDateReleaseList.Contains(refreshedToMonitor))
|
||||
{
|
||||
// we weren't going to update, but have changed monitored so now need to
|
||||
upToDateReleaseList.Remove(refreshedToMonitor);
|
||||
updateReleaseList.Add(refreshedToMonitor);
|
||||
}
|
||||
else if (updateReleaseList.Contains(refreshedToMonitor) && refreshedToMonitor.Equals(existingToMonitor))
|
||||
{
|
||||
// we were going to update because Monitored was incorrect but now it matches
|
||||
// and so no need to update
|
||||
updateReleaseList.Remove(refreshedToMonitor);
|
||||
upToDateReleaseList.Add(refreshedToMonitor);
|
||||
}
|
||||
|
||||
Ensure.That(album.AlbumReleases.Value.Count(x => x.Monitored) == 1).IsTrue();
|
||||
|
||||
_logger.Debug($"{album} {upToDateReleaseList.Count} releases up to date; Deleting {existingReleases.Count}, Updating {updateReleaseList.Count}, Adding {newReleaseList.Count} releases.");
|
||||
|
||||
// before deleting anything, remove musicbrainz ids for things we are deleting
|
||||
_audioTagService.RemoveMusicBrainzTags(existingReleases);
|
||||
|
||||
_releaseService.DeleteMany(existingReleases);
|
||||
_releaseService.UpdateMany(updateReleaseList);
|
||||
_releaseService.InsertMany(newReleaseList);
|
||||
|
||||
// if we have updated a monitored release, refresh all file tags
|
||||
forceUpdateFileTags |= updateReleaseList.Any(x => x.Monitored);
|
||||
updated |= existingReleases.Any() || updateReleaseList.Any() || newReleaseList.Any();
|
||||
|
||||
updated |= _refreshTrackService.RefreshTrackInfo(album, forceUpdateFileTags);
|
||||
_albumService.UpdateMany(new List<Album>{album});
|
||||
|
||||
_logger.Debug("Finished album refresh for {0}", album.Title);
|
||||
|
||||
return updated;
|
||||
return RefreshEntityInfo(album, remoteAlbums, true, forceUpdateFileTags);
|
||||
}
|
||||
|
||||
public void Execute(RefreshAlbumCommand message)
|
||||
|
@ -251,7 +327,7 @@ namespace NzbDrone.Core.Music
|
|||
{
|
||||
var album = _albumService.GetAlbum(message.AlbumId.Value);
|
||||
var artist = _artistService.GetArtistByMetadataId(album.ArtistMetadataId);
|
||||
var updated = RefreshAlbumInfo(album, false);
|
||||
var updated = RefreshAlbumInfo(album, null, false);
|
||||
if (updated)
|
||||
{
|
||||
_eventAggregator.PublishEvent(new ArtistUpdatedEvent(artist));
|
||||
|
|
|
@ -13,21 +13,22 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NzbDrone.Core.ImportLists.Exclusions;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using NzbDrone.Core.History;
|
||||
|
||||
namespace NzbDrone.Core.Music
|
||||
{
|
||||
public class RefreshArtistService : IExecute<RefreshArtistCommand>
|
||||
public class RefreshArtistService : RefreshEntityServiceBase<Artist, Album>, IExecute<RefreshArtistCommand>
|
||||
{
|
||||
private readonly IProvideArtistInfo _artistInfo;
|
||||
private readonly IArtistService _artistService;
|
||||
private readonly IAddAlbumService _addAlbumService;
|
||||
private readonly IArtistMetadataRepository _artistMetadataRepository;
|
||||
private readonly IAlbumService _albumService;
|
||||
private readonly IRefreshAlbumService _refreshAlbumService;
|
||||
private readonly IRefreshTrackService _refreshTrackService;
|
||||
private readonly IAudioTagService _audioTagService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IHistoryService _historyService;
|
||||
private readonly IDiskScanService _diskScanService;
|
||||
private readonly ICheckIfArtistShouldBeRefreshed _checkIfArtistShouldBeRefreshed;
|
||||
private readonly IConfigService _configService;
|
||||
|
@ -36,154 +37,219 @@ namespace NzbDrone.Core.Music
|
|||
|
||||
public RefreshArtistService(IProvideArtistInfo artistInfo,
|
||||
IArtistService artistService,
|
||||
IAddAlbumService addAlbumService,
|
||||
IArtistMetadataRepository artistMetadataRepository,
|
||||
IAlbumService albumService,
|
||||
IRefreshAlbumService refreshAlbumService,
|
||||
IRefreshTrackService refreshTrackService,
|
||||
IAudioTagService audioTagService,
|
||||
IEventAggregator eventAggregator,
|
||||
IMediaFileService mediaFileService,
|
||||
IHistoryService historyService,
|
||||
IDiskScanService diskScanService,
|
||||
ICheckIfArtistShouldBeRefreshed checkIfArtistShouldBeRefreshed,
|
||||
IConfigService configService,
|
||||
IImportListExclusionService importListExclusionService,
|
||||
Logger logger)
|
||||
: base(logger, artistMetadataRepository)
|
||||
{
|
||||
_artistInfo = artistInfo;
|
||||
_artistService = artistService;
|
||||
_addAlbumService = addAlbumService;
|
||||
_artistMetadataRepository = artistMetadataRepository;
|
||||
_albumService = albumService;
|
||||
_refreshAlbumService = refreshAlbumService;
|
||||
_refreshTrackService = refreshTrackService;
|
||||
_audioTagService = audioTagService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_mediaFileService = mediaFileService;
|
||||
_historyService = historyService;
|
||||
_diskScanService = diskScanService;
|
||||
_checkIfArtistShouldBeRefreshed = checkIfArtistShouldBeRefreshed;
|
||||
_configService = configService;
|
||||
_importListExclusionService = importListExclusionService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private bool RefreshArtistInfo(Artist artist, bool forceAlbumRefresh)
|
||||
|
||||
protected override RemoteData GetRemoteData(Artist local, List<Artist> remote)
|
||||
{
|
||||
_logger.ProgressInfo("Updating Info for {0}", artist.Name);
|
||||
bool updated = false;
|
||||
|
||||
Artist artistInfo;
|
||||
|
||||
var result = new RemoteData();
|
||||
try
|
||||
{
|
||||
artistInfo = _artistInfo.GetArtistInfo(artist.Metadata.Value.ForeignArtistId, artist.MetadataProfileId);
|
||||
result.Entity = _artistInfo.GetArtistInfo(local.Metadata.Value.ForeignArtistId, local.MetadataProfileId);
|
||||
result.Metadata = new List<ArtistMetadata> { result.Entity.Metadata.Value };
|
||||
}
|
||||
catch (ArtistNotFoundException)
|
||||
{
|
||||
_logger.Error($"Artist {artist} was not found, it may have been removed from Metadata sources.");
|
||||
return updated;
|
||||
_logger.Error($"Could not find artist with id {local.Metadata.Value.ForeignArtistId}");
|
||||
}
|
||||
|
||||
var forceUpdateFileTags = artist.Name != artistInfo.Name;
|
||||
updated |= forceUpdateFileTags;
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override bool ShouldDelete(Artist local)
|
||||
{
|
||||
return !_mediaFileService.GetFilesByArtist(local.Id).Any();
|
||||
}
|
||||
|
||||
protected override void LogProgress(Artist local)
|
||||
{
|
||||
_logger.ProgressInfo("Updating Info for {0}", local.Name);
|
||||
}
|
||||
|
||||
protected override bool IsMerge(Artist local, Artist remote)
|
||||
{
|
||||
return local.ArtistMetadataId != remote.Metadata.Value.Id;
|
||||
}
|
||||
|
||||
if (artist.Metadata.Value.ForeignArtistId != artistInfo.Metadata.Value.ForeignArtistId)
|
||||
protected override UpdateResult UpdateEntity(Artist local, Artist remote)
|
||||
{
|
||||
UpdateResult result = UpdateResult.None;
|
||||
|
||||
if(!local.Metadata.Value.Equals(remote.Metadata.Value))
|
||||
{
|
||||
_logger.Warn($"Artist {artist} was replaced with {artistInfo} because the original was a duplicate.");
|
||||
|
||||
// Update list exclusion if one exists
|
||||
var importExclusion = _importListExclusionService.FindByForeignId(artist.Metadata.Value.ForeignArtistId);
|
||||
|
||||
if (importExclusion != null)
|
||||
{
|
||||
importExclusion.ForeignId = artistInfo.Metadata.Value.ForeignArtistId;
|
||||
_importListExclusionService.Update(importExclusion);
|
||||
}
|
||||
|
||||
artist.Metadata.Value.ForeignArtistId = artistInfo.Metadata.Value.ForeignArtistId;
|
||||
forceUpdateFileTags = true;
|
||||
updated = true;
|
||||
result = UpdateResult.UpdateTags;
|
||||
}
|
||||
|
||||
artist.Metadata.Value.ApplyChanges(artistInfo.Metadata.Value);
|
||||
artist.CleanName = artistInfo.CleanName;
|
||||
artist.SortName = artistInfo.SortName;
|
||||
artist.LastInfoSync = DateTime.UtcNow;
|
||||
local.CleanName = remote.CleanName;
|
||||
local.SortName = remote.SortName;
|
||||
local.LastInfoSync = DateTime.UtcNow;
|
||||
|
||||
try
|
||||
{
|
||||
artist.Path = new DirectoryInfo(artist.Path).FullName;
|
||||
artist.Path = artist.Path.GetActualCasing();
|
||||
local.Path = new DirectoryInfo(local.Path).FullName;
|
||||
local.Path = local.Path.GetActualCasing();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warn(e, "Couldn't update artist path for " + artist.Path);
|
||||
_logger.Warn(e, "Couldn't update artist path for " + local.Path);
|
||||
}
|
||||
|
||||
var remoteAlbums = artistInfo.Albums.Value.DistinctBy(m => m.ForeignAlbumId).ToList();
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override UpdateResult MoveEntity(Artist local, Artist remote)
|
||||
{
|
||||
_logger.Debug($"Updating MusicBrainz id for {local} to {remote}");
|
||||
|
||||
// Get list of DB current db albums for artist
|
||||
var existingAlbums = _albumService.GetAlbumsForRefresh(artist.ArtistMetadataId, remoteAlbums.Select(x => x.ForeignAlbumId));
|
||||
var newAlbumsList = new List<Album>();
|
||||
var updateAlbumsList = new List<Album>();
|
||||
// We are moving from one metadata to another (will already have been poplated)
|
||||
local.ArtistMetadataId = remote.Metadata.Value.Id;
|
||||
local.Metadata = remote.Metadata.Value;
|
||||
|
||||
// Cycle thru albums
|
||||
foreach (var album in remoteAlbums)
|
||||
// Update list exclusion if one exists
|
||||
var importExclusion = _importListExclusionService.FindByForeignId(local.Metadata.Value.ForeignArtistId);
|
||||
|
||||
if (importExclusion != null)
|
||||
{
|
||||
// Check for album in existing albums, if not set properties and add to new list
|
||||
var albumToRefresh = existingAlbums.SingleOrDefault(s => s.ForeignAlbumId == album.ForeignAlbumId);
|
||||
|
||||
if (albumToRefresh != null)
|
||||
{
|
||||
albumToRefresh.Artist = artist;
|
||||
existingAlbums.Remove(albumToRefresh);
|
||||
updateAlbumsList.Add(albumToRefresh);
|
||||
}
|
||||
else
|
||||
{
|
||||
album.Artist = artist;
|
||||
newAlbumsList.Add(album);
|
||||
}
|
||||
importExclusion.ForeignId = remote.Metadata.Value.ForeignArtistId;
|
||||
_importListExclusionService.Update(importExclusion);
|
||||
}
|
||||
|
||||
_logger.Debug("{0} Deleting {1}, Updating {2}, Adding {3} albums",
|
||||
artist, existingAlbums.Count, updateAlbumsList.Count, newAlbumsList.Count);
|
||||
|
||||
// before deleting anything, remove musicbrainz ids for things we are deleting
|
||||
_audioTagService.RemoveMusicBrainzTags(existingAlbums);
|
||||
|
||||
// Delete old albums first - this avoids errors if albums have been merged and we'll
|
||||
// end up trying to duplicate an existing release under a new album
|
||||
_albumService.DeleteMany(existingAlbums);
|
||||
// Do the standard update
|
||||
UpdateEntity(local, remote);
|
||||
|
||||
// Update new albums with artist info and correct monitored status
|
||||
newAlbumsList = UpdateAlbums(artist, newAlbumsList);
|
||||
_addAlbumService.AddAlbums(newAlbumsList);
|
||||
|
||||
updated |= existingAlbums.Any() || newAlbumsList.Any();
|
||||
|
||||
updated |= _refreshAlbumService.RefreshAlbumInfo(updateAlbumsList, forceAlbumRefresh, forceUpdateFileTags);
|
||||
|
||||
// Do this last so artist only marked as refreshed if refresh of tracks / albums completed successfully
|
||||
_artistService.UpdateArtist(artist);
|
||||
|
||||
_eventAggregator.PublishEvent(new AlbumInfoRefreshedEvent(artist, newAlbumsList, updateAlbumsList));
|
||||
|
||||
if (updated)
|
||||
{
|
||||
_eventAggregator.PublishEvent(new ArtistUpdatedEvent(artist));
|
||||
}
|
||||
|
||||
_logger.Debug("Finished artist refresh for {0}", artist.Name);
|
||||
|
||||
return updated;
|
||||
// We know we need to update tags as artist id has changed
|
||||
return UpdateResult.UpdateTags;
|
||||
}
|
||||
|
||||
private List<Album> UpdateAlbums(Artist artist, List<Album> albumsToUpdate)
|
||||
protected override UpdateResult MergeEntity(Artist local, Artist target, Artist remote)
|
||||
{
|
||||
foreach (var album in albumsToUpdate)
|
||||
_logger.Warn($"Artist {local} was replaced with {remote} because the original was a duplicate.");
|
||||
|
||||
// Update list exclusion if one exists
|
||||
var importExclusionLocal = _importListExclusionService.FindByForeignId(local.Metadata.Value.ForeignArtistId);
|
||||
|
||||
if (importExclusionLocal != null)
|
||||
{
|
||||
album.ProfileId = artist.QualityProfileId;
|
||||
album.Monitored = artist.Monitored;
|
||||
var importExclusionTarget = _importListExclusionService.FindByForeignId(target.Metadata.Value.ForeignArtistId);
|
||||
if (importExclusionTarget == null)
|
||||
{
|
||||
importExclusionLocal.ForeignId = remote.Metadata.Value.ForeignArtistId;
|
||||
_importListExclusionService.Update(importExclusionLocal);
|
||||
}
|
||||
}
|
||||
|
||||
return albumsToUpdate;
|
||||
// move any albums over to the new artist and remove the local artist
|
||||
var albums = _albumService.GetAlbumsByArtist(local.Id);
|
||||
albums.ForEach(x => x.ArtistMetadataId = target.ArtistMetadataId);
|
||||
_albumService.UpdateMany(albums);
|
||||
_artistService.DeleteArtist(local.Id, false);
|
||||
|
||||
// Update history entries to new id
|
||||
var items = _historyService.GetByArtist(local.Id, null);
|
||||
items.ForEach(x => x.ArtistId = target.Id);
|
||||
_historyService.UpdateMany(items);
|
||||
|
||||
// We know we need to update tags as artist id has changed
|
||||
return UpdateResult.UpdateTags;
|
||||
}
|
||||
|
||||
protected override Artist GetEntityByForeignId(Artist local)
|
||||
{
|
||||
return _artistService.FindById(local.ForeignArtistId);
|
||||
}
|
||||
|
||||
protected override void SaveEntity(Artist local)
|
||||
{
|
||||
_artistService.UpdateArtist(local);
|
||||
}
|
||||
|
||||
protected override void DeleteEntity(Artist local, bool deleteFiles)
|
||||
{
|
||||
_artistService.DeleteArtist(local.Id, true);
|
||||
}
|
||||
|
||||
protected override List<Album> GetRemoteChildren(Artist remote)
|
||||
{
|
||||
return remote.Albums.Value.DistinctBy(m => m.ForeignAlbumId).ToList();
|
||||
}
|
||||
|
||||
protected override List<Album> GetLocalChildren(Artist entity, List<Album> remoteChildren)
|
||||
{
|
||||
return _albumService.GetAlbumsForRefresh(entity.ArtistMetadataId,
|
||||
remoteChildren.Select(x => x.ForeignAlbumId)
|
||||
.Concat(remoteChildren.SelectMany(x => x.OldForeignAlbumIds)));
|
||||
}
|
||||
|
||||
protected override Tuple<Album, List<Album> > GetMatchingExistingChildren(List<Album> existingChildren, Album remote)
|
||||
{
|
||||
var existingChild = existingChildren.SingleOrDefault(x => x.ForeignAlbumId == remote.ForeignAlbumId);
|
||||
var mergeChildren = existingChildren.Where(x => remote.OldForeignAlbumIds.Contains(x.ForeignAlbumId)).ToList();
|
||||
return Tuple.Create(existingChild, mergeChildren);
|
||||
}
|
||||
|
||||
protected override void PrepareNewChild(Album child, Artist entity)
|
||||
{
|
||||
child.Artist = entity;
|
||||
child.ArtistMetadata = entity.Metadata.Value;
|
||||
child.ArtistMetadataId = entity.Metadata.Value.Id;
|
||||
child.Added = DateTime.UtcNow;
|
||||
child.LastInfoSync = DateTime.MinValue;
|
||||
child.ProfileId = entity.QualityProfileId;
|
||||
child.Monitored = entity.Monitored;
|
||||
}
|
||||
|
||||
protected override void PrepareExistingChild(Album local, Album remote, Artist entity)
|
||||
{
|
||||
local.Artist = entity;
|
||||
local.ArtistMetadata = entity.Metadata.Value;
|
||||
local.ArtistMetadataId = entity.Metadata.Value.Id;
|
||||
}
|
||||
|
||||
protected override void AddChildren(List<Album> children)
|
||||
{
|
||||
_albumService.InsertMany(children);
|
||||
}
|
||||
|
||||
protected override bool RefreshChildren(SortedChildren localChildren, List<Album> remoteChildren, bool forceChildRefresh, bool forceUpdateFileTags)
|
||||
{
|
||||
// we always want to end up refreshing the albums since we don't get have proper data
|
||||
Ensure.That(localChildren.UpToDate.Count, () => localChildren.UpToDate.Count).IsLessThanOrEqualTo(0);
|
||||
return _refreshAlbumService.RefreshAlbumInfo(localChildren.All, remoteChildren, forceChildRefresh, forceUpdateFileTags);
|
||||
}
|
||||
|
||||
protected override void PublishEntityUpdatedEvent(Artist entity)
|
||||
{
|
||||
_eventAggregator.PublishEvent(new ArtistUpdatedEvent(entity));
|
||||
}
|
||||
|
||||
protected override void PublishChildrenUpdatedEvent(Artist entity, List<Album> newChildren, List<Album> updateChildren)
|
||||
{
|
||||
_eventAggregator.PublishEvent(new AlbumInfoRefreshedEvent(entity, newChildren, updateChildren));
|
||||
}
|
||||
|
||||
private void RescanArtist(Artist artist, bool isNew, CommandTrigger trigger, bool infoUpdated)
|
||||
|
@ -239,7 +305,7 @@ namespace NzbDrone.Core.Music
|
|||
bool updated = false;
|
||||
try
|
||||
{
|
||||
updated = RefreshArtistInfo(artist, true);
|
||||
updated = RefreshEntityInfo(artist, null, true, false);
|
||||
RescanArtist(artist, isNew, trigger, updated);
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@ -262,7 +328,7 @@ namespace NzbDrone.Core.Music
|
|||
bool updated = false;
|
||||
try
|
||||
{
|
||||
updated = RefreshArtistInfo(artist, manualTrigger);
|
||||
updated = RefreshEntityInfo(artist, null, manualTrigger, false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -271,7 +337,6 @@ namespace NzbDrone.Core.Music
|
|||
|
||||
RescanArtist(artist, false, trigger, updated);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
_logger.Info("Skipping refresh of artist: {0}", artist.Name);
|
||||
|
|
|
@ -0,0 +1,277 @@
|
|||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace NzbDrone.Core.Music
|
||||
{
|
||||
public abstract class RefreshEntityServiceBase<Entity, Child>
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
private readonly IArtistMetadataRepository _artistMetadataRepository;
|
||||
|
||||
public RefreshEntityServiceBase(Logger logger,
|
||||
IArtistMetadataRepository artistMetadataRepository)
|
||||
{
|
||||
_logger = logger;
|
||||
_artistMetadataRepository = artistMetadataRepository;
|
||||
}
|
||||
|
||||
public enum UpdateResult
|
||||
{
|
||||
None,
|
||||
Standard,
|
||||
UpdateTags
|
||||
};
|
||||
|
||||
public class SortedChildren
|
||||
{
|
||||
public SortedChildren()
|
||||
{
|
||||
UpToDate = new List<Child>();
|
||||
Added = new List<Child>();
|
||||
Updated = new List<Child>();
|
||||
Merged = new List<Tuple<Child, Child> >();
|
||||
Deleted = new List<Child>();
|
||||
}
|
||||
|
||||
public List<Child> UpToDate { get; set; }
|
||||
public List<Child> Added { get; set; }
|
||||
public List<Child> Updated { get; set; }
|
||||
public List<Tuple<Child, Child> > Merged { get; set; }
|
||||
public List<Child> Deleted { get; set; }
|
||||
|
||||
public List<Child> All => UpToDate.Concat(Added).Concat(Updated).Concat(Merged.Select(x => x.Item1)).Concat(Deleted).ToList();
|
||||
public List<Child> Future => UpToDate.Concat(Added).Concat(Updated).ToList();
|
||||
public List<Child> Old => Merged.Select(x => x.Item1).Concat(Deleted).ToList();
|
||||
}
|
||||
|
||||
public class RemoteData
|
||||
{
|
||||
public Entity Entity { get; set; }
|
||||
public List<ArtistMetadata> Metadata { get; set; }
|
||||
}
|
||||
|
||||
protected virtual void LogProgress(Entity local)
|
||||
{
|
||||
}
|
||||
|
||||
protected abstract RemoteData GetRemoteData(Entity local, List<Entity> remote);
|
||||
|
||||
protected virtual void EnsureNewParent(Entity local, Entity remote)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
protected abstract bool IsMerge(Entity local, Entity remote);
|
||||
|
||||
protected virtual bool ShouldDelete(Entity local)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected abstract UpdateResult UpdateEntity(Entity local, Entity remote);
|
||||
|
||||
protected virtual UpdateResult MoveEntity(Entity local, Entity remote)
|
||||
{
|
||||
return UpdateEntity(local, remote);
|
||||
}
|
||||
|
||||
protected virtual UpdateResult MergeEntity(Entity local, Entity target, Entity remote)
|
||||
{
|
||||
DeleteEntity(local, true);
|
||||
return UpdateResult.UpdateTags;
|
||||
}
|
||||
|
||||
protected abstract Entity GetEntityByForeignId(Entity local);
|
||||
protected abstract void SaveEntity(Entity local);
|
||||
protected abstract void DeleteEntity(Entity local, bool deleteFiles);
|
||||
|
||||
protected abstract List<Child> GetRemoteChildren(Entity remote);
|
||||
protected abstract List<Child> GetLocalChildren(Entity entity, List<Child> remoteChildren);
|
||||
protected abstract Tuple<Child, List<Child> > GetMatchingExistingChildren(List<Child> existingChildren, Child remote);
|
||||
|
||||
protected abstract void PrepareNewChild(Child remoteChild, Entity entity);
|
||||
protected abstract void PrepareExistingChild(Child existingChild, Child remoteChild, Entity entity);
|
||||
protected abstract void AddChildren(List<Child> children);
|
||||
protected abstract bool RefreshChildren(SortedChildren localChildren, List<Child> remoteChildren, bool forceChildRefresh, bool forceUpdateFileTags);
|
||||
|
||||
protected virtual void PublishEntityUpdatedEvent(Entity entity)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void PublishChildrenUpdatedEvent(Entity entity, List<Child> newChildren, List<Child> updateChildren)
|
||||
{
|
||||
}
|
||||
|
||||
public bool RefreshEntityInfo(Entity local, List<Entity> remoteList, bool forceChildRefresh, bool forceUpdateFileTags)
|
||||
{
|
||||
bool updated = false;
|
||||
|
||||
LogProgress(local);
|
||||
|
||||
var data = GetRemoteData(local, remoteList);
|
||||
var remote = data.Entity;
|
||||
|
||||
if (remote == null)
|
||||
{
|
||||
if (ShouldDelete(local))
|
||||
{
|
||||
_logger.Warn($"{typeof(Entity).Name} {local} not found in metadata and is being deleted");
|
||||
DeleteEntity(local, true);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error($"{typeof(Entity).Name} {local} was not found, it may have been removed from Metadata sources.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.Metadata != null)
|
||||
{
|
||||
var metadataResult = UpdateArtistMetadata(data.Metadata);
|
||||
updated |= metadataResult >= UpdateResult.Standard;
|
||||
forceUpdateFileTags |= metadataResult == UpdateResult.UpdateTags;
|
||||
}
|
||||
|
||||
// Validate that the parent object exists (remote data might specify a different one)
|
||||
EnsureNewParent(local, remote);
|
||||
|
||||
UpdateResult result;
|
||||
if (IsMerge(local, remote))
|
||||
{
|
||||
// get entity we're merging into
|
||||
var target = GetEntityByForeignId(remote);
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
_logger.Trace($"Moving {typeof(Entity).Name} {local} to {remote}");
|
||||
result = MoveEntity(local, remote);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Trace($"Merging {typeof(Entity).Name} {local} into {target}");
|
||||
result = MergeEntity(local, target, remote);
|
||||
|
||||
// having merged local into target, do update for target using remote
|
||||
local = target;
|
||||
}
|
||||
|
||||
// Save the entity early so that children see the updated ids
|
||||
SaveEntity(local);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Trace($"Updating {typeof(Entity).Name} {local}");
|
||||
result = UpdateEntity(local, remote);
|
||||
}
|
||||
|
||||
updated |= result >= UpdateResult.Standard;
|
||||
forceUpdateFileTags |= result == UpdateResult.UpdateTags;
|
||||
|
||||
_logger.Trace($"updated: {updated} forceUpdateFileTags: {forceUpdateFileTags}");
|
||||
|
||||
var remoteChildren = GetRemoteChildren(remote);
|
||||
updated |= SortChildren(local, remoteChildren, forceChildRefresh, forceUpdateFileTags);
|
||||
|
||||
// Do this last so entity only marked as refreshed if refresh of children completed successfully
|
||||
_logger.Trace($"Saving {typeof(Entity).Name} {local}");
|
||||
SaveEntity(local);
|
||||
|
||||
if (updated)
|
||||
{
|
||||
PublishEntityUpdatedEvent(local);
|
||||
}
|
||||
|
||||
_logger.Debug($"Finished {typeof(Entity).Name} refresh for {local}");
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
public UpdateResult UpdateArtistMetadata(List<ArtistMetadata> data)
|
||||
{
|
||||
var remoteMetadata = data.DistinctBy(x => x.ForeignArtistId).ToList();
|
||||
var updated = _artistMetadataRepository.UpsertMany(data);
|
||||
return updated ? UpdateResult.UpdateTags : UpdateResult.None;
|
||||
}
|
||||
|
||||
public bool RefreshEntityInfo(List<Entity> localList, List<Entity> remoteList, bool forceChildRefresh, bool forceUpdateFileTags)
|
||||
{
|
||||
bool updated = false;
|
||||
foreach (var entity in localList)
|
||||
{
|
||||
updated |= RefreshEntityInfo(entity, remoteList, forceChildRefresh, forceUpdateFileTags);
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
protected bool SortChildren(Entity entity, List<Child> remoteChildren, bool forceChildRefresh, bool forceUpdateFileTags)
|
||||
{
|
||||
// Get existing children (and children to be) from the database
|
||||
var localChildren = GetLocalChildren(entity, remoteChildren);
|
||||
|
||||
var sortedChildren = new SortedChildren();
|
||||
sortedChildren.Deleted.AddRange(localChildren);
|
||||
|
||||
// Cycle through children
|
||||
foreach (var remoteChild in remoteChildren)
|
||||
{
|
||||
// Check for child in existing children, if not set properties and add to new list
|
||||
var tuple = GetMatchingExistingChildren(localChildren, remoteChild);
|
||||
var existingChild = tuple.Item1;
|
||||
var mergedChildren = tuple.Item2;
|
||||
|
||||
if (existingChild != null)
|
||||
{
|
||||
sortedChildren.Deleted.Remove(existingChild);
|
||||
|
||||
PrepareExistingChild(existingChild, remoteChild, entity);
|
||||
|
||||
if (existingChild.Equals(remoteChild))
|
||||
{
|
||||
sortedChildren.UpToDate.Add(existingChild);
|
||||
}
|
||||
else
|
||||
{
|
||||
sortedChildren.Updated.Add(existingChild);
|
||||
}
|
||||
|
||||
// note the children that are going to be merged into existingChild
|
||||
foreach (var child in mergedChildren)
|
||||
{
|
||||
sortedChildren.Merged.Add(Tuple.Create(child, existingChild));
|
||||
sortedChildren.Deleted.Remove(child);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PrepareNewChild(remoteChild, entity);
|
||||
sortedChildren.Added.Add(remoteChild);
|
||||
|
||||
// note the children that will be merged into remoteChild (once added)
|
||||
foreach (var child in mergedChildren)
|
||||
{
|
||||
sortedChildren.Merged.Add(Tuple.Create(child, existingChild));
|
||||
sortedChildren.Deleted.Remove(child);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Debug("{0} {1} {2}s up to date. Adding {3}, Updating {4}, Merging {5}, Deleting {6}.",
|
||||
entity, sortedChildren.UpToDate.Count, typeof(Child).Name.ToLower(),
|
||||
sortedChildren.Added.Count, sortedChildren.Updated.Count, sortedChildren.Merged.Count, sortedChildren.Deleted.Count);
|
||||
|
||||
// Add in the new children (we have checked that foreign IDs don't clash)
|
||||
AddChildren(sortedChildren.Added);
|
||||
|
||||
// now trigger updates
|
||||
var updated = RefreshChildren(sortedChildren, remoteChildren, forceChildRefresh, forceUpdateFileTags);
|
||||
|
||||
PublishChildrenUpdatedEvent(entity, sortedChildren.Added, sortedChildren.Updated);
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -10,127 +8,70 @@ namespace NzbDrone.Core.Music
|
|||
{
|
||||
public interface IRefreshTrackService
|
||||
{
|
||||
bool RefreshTrackInfo(Album rg, bool forceUpdateFileTags);
|
||||
bool RefreshTrackInfo(List<Track> add, List<Track> update, List<Tuple<Track, Track> > merge, List<Track> delete, List<Track> upToDate, List<Track> remoteTracks, bool forceUpdateFileTags);
|
||||
}
|
||||
|
||||
public class RefreshTrackService : IRefreshTrackService
|
||||
{
|
||||
private readonly ITrackService _trackService;
|
||||
private readonly IAlbumService _albumService;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IAudioTagService _audioTagService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public RefreshTrackService(ITrackService trackService,
|
||||
IAlbumService albumService,
|
||||
IMediaFileService mediaFileService,
|
||||
IAudioTagService audioTagService,
|
||||
IEventAggregator eventAggregator,
|
||||
Logger logger)
|
||||
{
|
||||
_trackService = trackService;
|
||||
_albumService = albumService;
|
||||
_mediaFileService = mediaFileService;
|
||||
_audioTagService = audioTagService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public bool RefreshTrackInfo(Album album, bool forceUpdateFileTags)
|
||||
public bool RefreshTrackInfo(List<Track> add, List<Track> update, List<Tuple<Track, Track> > merge, List<Track> delete, List<Track> upToDate, List<Track> remoteTracks, bool forceUpdateFileTags)
|
||||
{
|
||||
_logger.Info("Starting track info refresh for: {0}", album);
|
||||
var successCount = 0;
|
||||
var failCount = 0;
|
||||
bool updated = false;
|
||||
var updateList = new List<Track>();
|
||||
|
||||
foreach (var release in album.AlbumReleases.Value)
|
||||
// for tracks that need updating, just grab the remote track and set db ids
|
||||
foreach (var trackToUpdate in update)
|
||||
{
|
||||
var remoteTracks = release.Tracks.Value.DistinctBy(m => m.ForeignTrackId).ToList();
|
||||
var existingTracks = _trackService.GetTracksForRefresh(release.Id, remoteTracks.Select(x => x.ForeignTrackId));
|
||||
var track = remoteTracks.Single(e => e.ForeignTrackId == trackToUpdate.ForeignTrackId);
|
||||
|
||||
var updateList = new List<Track>();
|
||||
var newList = new List<Track>();
|
||||
var upToDateList = new List<Track>();
|
||||
// copy across the db keys to the remote track and check if we need to update
|
||||
track.Id = trackToUpdate.Id;
|
||||
track.TrackFileId = trackToUpdate.TrackFileId;
|
||||
// make sure title is not null
|
||||
track.Title = track.Title ?? "Unknown";
|
||||
updateList.Add(track);
|
||||
}
|
||||
|
||||
// Move trackfiles from merged entities into new one
|
||||
foreach (var item in merge)
|
||||
{
|
||||
var trackToMerge = item.Item1;
|
||||
var mergeTarget = item.Item2;
|
||||
|
||||
foreach (var track in remoteTracks)
|
||||
if (mergeTarget.TrackFileId == 0)
|
||||
{
|
||||
track.AlbumRelease = release;
|
||||
track.AlbumReleaseId = release.Id;
|
||||
// the artist metadata will have been inserted by RefreshAlbumInfo so the Id will now be populated
|
||||
track.ArtistMetadataId = track.ArtistMetadata.Value.Id;
|
||||
|
||||
try
|
||||
{
|
||||
var trackToUpdate = existingTracks.SingleOrDefault(e => e.ForeignTrackId == track.ForeignTrackId);
|
||||
if (trackToUpdate != null)
|
||||
{
|
||||
existingTracks.Remove(trackToUpdate);
|
||||
|
||||
// populate albumrelease for later
|
||||
trackToUpdate.AlbumRelease = release;
|
||||
|
||||
// copy across the db keys to the remote track and check if we need to update
|
||||
track.Id = trackToUpdate.Id;
|
||||
track.TrackFileId = trackToUpdate.TrackFileId;
|
||||
// make sure title is not null
|
||||
track.Title = track.Title ?? "Unknown";
|
||||
|
||||
if (!trackToUpdate.Equals(track))
|
||||
{
|
||||
updateList.Add(track);
|
||||
}
|
||||
else
|
||||
{
|
||||
upToDateList.Add(track);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newList.Add(track);
|
||||
}
|
||||
|
||||
successCount++;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Fatal(e, "An error has occurred while updating track info for album {0}. {1}", album, track);
|
||||
failCount++;
|
||||
}
|
||||
mergeTarget.TrackFileId = trackToMerge.TrackFileId;
|
||||
}
|
||||
|
||||
// if any tracks with files are deleted, strip out the MB tags from the metadata
|
||||
// so that we stand a chance of matching next time
|
||||
_audioTagService.RemoveMusicBrainzTags(existingTracks);
|
||||
|
||||
var tagsToUpdate = updateList;
|
||||
if (forceUpdateFileTags)
|
||||
if (!updateList.Contains(mergeTarget))
|
||||
{
|
||||
_logger.Debug("Forcing tag update due to Artist/Album/Release updates");
|
||||
tagsToUpdate = updateList.Concat(upToDateList).ToList();
|
||||
updateList.Add(mergeTarget);
|
||||
}
|
||||
_audioTagService.SyncTags(tagsToUpdate);
|
||||
}
|
||||
|
||||
var tagsToUpdate = updateList;
|
||||
if (forceUpdateFileTags)
|
||||
{
|
||||
_logger.Debug("Forcing tag update due to Artist/Album/Release updates");
|
||||
tagsToUpdate = updateList.Concat(upToDate).ToList();
|
||||
}
|
||||
_audioTagService.SyncTags(tagsToUpdate);
|
||||
|
||||
_logger.Debug($"{release}: {upToDateList.Count} tracks up to date; Deleting {existingTracks.Count}, Updating {updateList.Count}, Adding {newList.Count} tracks.");
|
||||
_trackService.DeleteMany(delete.Concat(merge.Select(x => x.Item1)).ToList());
|
||||
_trackService.UpdateMany(updateList);
|
||||
|
||||
_trackService.DeleteMany(existingTracks);
|
||||
_trackService.UpdateMany(updateList);
|
||||
_trackService.InsertMany(newList);
|
||||
|
||||
updated |= existingTracks.Any() || updateList.Any() || newList.Any();
|
||||
}
|
||||
|
||||
if (failCount != 0)
|
||||
{
|
||||
_logger.Info("Finished track refresh for album: {0}. Successful: {1} - Failed: {2} ",
|
||||
album.Title, successCount, failCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Info("Finished track refresh for album: {0}.", album);
|
||||
}
|
||||
|
||||
return updated;
|
||||
return delete.Any() || updateList.Any() || merge.Any();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ namespace NzbDrone.Core.Music
|
|||
|
||||
// These are retained for compatibility
|
||||
// TODO: Remove set, bodged in because tests expect this to be writable
|
||||
public int AlbumId { get { return AlbumRelease.Value?.Album.Value?.Id ?? 0; } set { /* empty */ } }
|
||||
public int AlbumId { get { return AlbumRelease?.Value?.Album?.Value?.Id ?? 0; } set { /* empty */ } }
|
||||
public Album Album { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
|
|
|
@ -847,7 +847,6 @@
|
|||
<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\AlbumAddedService.cs" />
|
||||
<Compile Include="Music\AlbumEditedService.cs" />
|
||||
<Compile Include="Music\AlbumRepository.cs" />
|
||||
|
@ -898,8 +897,10 @@
|
|||
<Compile Include="Music\Events\ArtistUpdatedEvent.cs" />
|
||||
<Compile Include="Music\Events\AlbumInfoRefreshedEvent.cs" />
|
||||
<Compile Include="Music\Ratings.cs" />
|
||||
<Compile Include="Music\RefreshEntityServiceBase.cs" />
|
||||
<Compile Include="Music\RefreshArtistService.cs" />
|
||||
<Compile Include="Music\RefreshAlbumService.cs" />
|
||||
<Compile Include="Music\RefreshAlbumReleaseService.cs" />
|
||||
<Compile Include="Music\RefreshTrackService.cs" />
|
||||
<Compile Include="Music\ShouldRefreshArtist.cs" />
|
||||
<Compile Include="Music\Track.cs" />
|
||||
|
|
Loading…
Reference in New Issue