diff --git a/src/NzbDrone.Core.Test/Lidarr.Core.Test.csproj b/src/NzbDrone.Core.Test/Lidarr.Core.Test.csproj index 16efb130f..e314a2c58 100644 --- a/src/NzbDrone.Core.Test/Lidarr.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/Lidarr.Core.Test.csproj @@ -6,6 +6,7 @@ + diff --git a/src/NzbDrone.Core.Test/MusicTests/EntityFixture.cs b/src/NzbDrone.Core.Test/MusicTests/EntityFixture.cs new file mode 100644 index 000000000..ba5fb83b5 --- /dev/null +++ b/src/NzbDrone.Core.Test/MusicTests/EntityFixture.cs @@ -0,0 +1,298 @@ +using NUnit.Framework; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Music; +using NzbDrone.Test.Common; +using FluentAssertions; +using System.Collections; +using System.Reflection; +using AutoFixture; +using System.Linq; +using Equ; +using Marr.Data; + +namespace NzbDrone.Core.Test.MusicTests +{ + [TestFixture] + public class EntityFixture : LoggingTest + { + + Fixture fixture = new Fixture(); + + private static bool IsNotMarkedAsIgnore(PropertyInfo propertyInfo) + { + return !propertyInfo.GetCustomAttributes(typeof(MemberwiseEqualityIgnoreAttribute), true).Any(); + } + + public class EqualityPropertySource + { + public static IEnumerable TestCases + { + get + { + foreach (var property in typeof(T).GetProperties().Where(x => x.CanRead && x.CanWrite && IsNotMarkedAsIgnore(x))) + { + yield return new TestCaseData(property).SetName($"{{m}}_{property.Name}"); + } + } + } + } + + public class IgnoredPropertySource + { + public static IEnumerable TestCases + { + get + { + foreach (var property in typeof(T).GetProperties().Where(x => x.CanRead && x.CanWrite && !IsNotMarkedAsIgnore(x))) + { + yield return new TestCaseData(property).SetName($"{{m}}_{property.Name}"); + } + } + } + } + + [Test] + public void two_equivalent_artist_metadata_should_be_equal() + { + var item1 = fixture.Create(); + var item2 = item1.JsonClone(); + + item1.Should().NotBeSameAs(item2); + item1.Should().Be(item2); + } + + [Test, TestCaseSource(typeof(EqualityPropertySource), "TestCases")] + public void two_different_artist_metadata_should_not_be_equal(PropertyInfo prop) + { + var item1 = fixture.Create(); + var item2 = item1.JsonClone(); + var different = fixture.Create(); + + // make item2 different in the property under consideration + var differentEntry = prop.GetValue(different); + prop.SetValue(item2, differentEntry); + + item1.Should().NotBeSameAs(item2); + item1.Should().NotBe(item2); + } + + [Test] + public void metadata_and_db_fields_should_replicate_artist_metadata() + { + var item1 = fixture.Create(); + var item2 = fixture.Create(); + + item1.Should().NotBe(item2); + + item1.UseMetadataFrom(item2); + item1.UseDbFieldsFrom(item2); + item1.Should().Be(item2); + } + + private Track GivenTrack() + { + return fixture.Build() + .Without(x => x.AlbumRelease) + .Without(x => x.ArtistMetadata) + .Without(x => x.TrackFile) + .Without(x => x.Artist) + .Without(x => x.AlbumId) + .Without(x => x.Album) + .Create(); + } + + [Test] + public void two_equivalent_track_should_be_equal() + { + var item1 = GivenTrack(); + var item2 = item1.JsonClone(); + + item1.Should().NotBeSameAs(item2); + item1.Should().Be(item2); + } + + [Test, TestCaseSource(typeof(EqualityPropertySource), "TestCases")] + public void two_different_tracks_should_not_be_equal(PropertyInfo prop) + { + var item1 = GivenTrack(); + var item2 = item1.JsonClone(); + var different = GivenTrack(); + + // make item2 different in the property under consideration + var differentEntry = prop.GetValue(different); + prop.SetValue(item2, differentEntry); + + item1.Should().NotBeSameAs(item2); + item1.Should().NotBe(item2); + } + + [Test] + public void metadata_and_db_fields_should_replicate_track() + { + var item1 = GivenTrack(); + var item2 = GivenTrack(); + + item1.Should().NotBe(item2); + + item1.UseMetadataFrom(item2); + item1.UseDbFieldsFrom(item2); + item1.Should().Be(item2); + } + + private AlbumRelease GivenAlbumRelease() + { + return fixture.Build() + .Without(x => x.Album) + .Without(x => x.Tracks) + .Create(); + } + + [Test] + public void two_equivalent_album_releases_should_be_equal() + { + var item1 = GivenAlbumRelease(); + var item2 = item1.JsonClone(); + + item1.Should().NotBeSameAs(item2); + item1.Should().Be(item2); + } + + [Test, TestCaseSource(typeof(EqualityPropertySource), "TestCases")] + public void two_different_album_releases_should_not_be_equal(PropertyInfo prop) + { + var item1 = GivenAlbumRelease(); + var item2 = item1.JsonClone(); + var different = GivenAlbumRelease(); + + // make item2 different in the property under consideration + var differentEntry = prop.GetValue(different); + prop.SetValue(item2, differentEntry); + + item1.Should().NotBeSameAs(item2); + item1.Should().NotBe(item2); + } + + [Test] + public void metadata_and_db_fields_should_replicate_release() + { + var item1 = GivenAlbumRelease(); + var item2 = GivenAlbumRelease(); + + item1.Should().NotBe(item2); + + item1.UseMetadataFrom(item2); + item1.UseDbFieldsFrom(item2); + item1.Should().Be(item2); + } + + private Album GivenAlbum() + { + return fixture.Build() + .Without(x => x.ArtistMetadata) + .Without(x => x.AlbumReleases) + .Without(x => x.Artist) + .Without(x => x.ArtistId) + .Create(); + } + + [Test] + public void two_equivalent_albums_should_be_equal() + { + var item1 = GivenAlbum(); + var item2 = item1.JsonClone(); + + item1.Should().NotBeSameAs(item2); + item1.Should().Be(item2); + } + + [Test, TestCaseSource(typeof(EqualityPropertySource), "TestCases")] + public void two_different_albums_should_not_be_equal(PropertyInfo prop) + { + var item1 = GivenAlbum(); + var item2 = item1.JsonClone(); + var different = GivenAlbum(); + + // make item2 different in the property under consideration + if (prop.PropertyType == typeof(bool)) + { + prop.SetValue(item2, !(bool)prop.GetValue(item1)); + } + else + { + prop.SetValue(item2, prop.GetValue(different)); + } + + item1.Should().NotBeSameAs(item2); + item1.Should().NotBe(item2); + } + + [Test] + public void metadata_and_db_fields_should_replicate_album() + { + var item1 = GivenAlbum(); + var item2 = GivenAlbum(); + + item1.Should().NotBe(item2); + + item1.UseMetadataFrom(item2); + item1.UseDbFieldsFrom(item2); + item1.Should().Be(item2); + } + + private Artist GivenArtist() + { + return fixture.Build() + .With(x => x.Metadata, new LazyLoaded(fixture.Create())) + .Without(x => x.QualityProfile) + .Without(x => x.MetadataProfile) + .Without(x => x.Albums) + .Without(x => x.Name) + .Without(x => x.ForeignArtistId) + .Create(); + } + + [Test] + public void two_equivalent_artists_should_be_equal() + { + var item1 = GivenArtist(); + var item2 = item1.JsonClone(); + + item1.Should().NotBeSameAs(item2); + item1.Should().Be(item2); + } + + [Test, TestCaseSource(typeof(EqualityPropertySource), "TestCases")] + public void two_different_artists_should_not_be_equal(PropertyInfo prop) + { + var item1 = GivenArtist(); + var item2 = item1.JsonClone(); + var different = GivenArtist(); + + // make item2 different in the property under consideration + if (prop.PropertyType == typeof(bool)) + { + prop.SetValue(item2, !(bool)prop.GetValue(item1)); + } + else + { + prop.SetValue(item2, prop.GetValue(different)); + } + + item1.Should().NotBeSameAs(item2); + item1.Should().NotBe(item2); + } + + [Test] + public void metadata_and_db_fields_should_replicate_artist() + { + var item1 = GivenArtist(); + var item2 = GivenArtist(); + + item1.Should().NotBe(item2); + + item1.UseMetadataFrom(item2); + item1.UseDbFieldsFrom(item2); + item1.Should().Be(item2); + } + } +} diff --git a/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumReleaseServiceFixture.cs b/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumReleaseServiceFixture.cs new file mode 100644 index 000000000..9dfaa7af0 --- /dev/null +++ b/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumReleaseServiceFixture.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FizzWare.NBuilder; +using Moq; +using NUnit.Framework; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Exceptions; +using NzbDrone.Core.MetadataSource; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Music; +using NzbDrone.Test.Common; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.History; + +namespace NzbDrone.Core.Test.MusicTests +{ + [TestFixture] + public class RefreshAlbumReleaseServiceFixture : CoreTest + { + private AlbumRelease _release; + private List _tracks; + private ArtistMetadata _metadata; + + [SetUp] + public void Setup() + { + + _release = Builder + .CreateNew() + .With(s => s.Media = new List { new Medium { Number = 1 } }) + .With(s => s.ForeignReleaseId = "xxx-xxx-xxx-xxx") + .With(s => s.Monitored = true) + .With(s => s.TrackCount = 10) + .Build(); + + _metadata = Builder.CreateNew().Build(); + + _tracks = Builder + .CreateListOfSize(10) + .All() + .With(x => x.AlbumReleaseId = _release.Id) + .With(x => x.ArtistMetadata = _metadata) + .With(x => x.ArtistMetadataId = _metadata.Id) + .BuildList(); + + Mocker.GetMock() + .Setup(s => s.GetTracksForRefresh(_release.Id, It.IsAny>())) + .Returns(_tracks); + + } + + [Test] + public void should_update_if_musicbrainz_id_changed_and_no_clash() + { + var newInfo = _release.JsonClone(); + newInfo.ForeignReleaseId = _release.ForeignReleaseId + 1; + newInfo.OldForeignReleaseIds = new List { _release.ForeignReleaseId }; + newInfo.Tracks = _tracks; + + Subject.RefreshEntityInfo(_release, new List { newInfo }, false, false); + + Mocker.GetMock() + .Verify(v => v.UpdateMany(It.Is>(s => s.First().ForeignReleaseId == newInfo.ForeignReleaseId))); + } + + [Test] + public void should_merge_if_musicbrainz_id_changed_and_new_already_exists() + { + var existing = _release; + + var clash = existing.JsonClone(); + clash.Id = 100; + clash.ForeignReleaseId = clash.ForeignReleaseId + 1; + + clash.Tracks = Builder.CreateListOfSize(10) + .All() + .With(x => x.AlbumReleaseId = clash.Id) + .With(x => x.ArtistMetadata = _metadata) + .With(x => x.ArtistMetadataId = _metadata.Id) + .BuildList(); + + Mocker.GetMock() + .Setup(x => x.GetReleaseByForeignReleaseId(clash.ForeignReleaseId, false)) + .Returns(clash); + + Mocker.GetMock() + .Setup(x => x.GetTracksForRefresh(It.IsAny(), It.IsAny>())) + .Returns(_tracks); + + var newInfo = existing.JsonClone(); + newInfo.ForeignReleaseId = _release.ForeignReleaseId + 1; + newInfo.OldForeignReleaseIds = new List { _release.ForeignReleaseId }; + newInfo.Tracks = _tracks; + + Subject.RefreshEntityInfo(new List { clash, existing }, new List { newInfo }, false, false); + + // check old album is deleted + Mocker.GetMock() + .Verify(v => v.DeleteMany(It.Is>(x => x.First().ForeignReleaseId == existing.ForeignReleaseId))); + + // check that clash gets updated + Mocker.GetMock() + .Verify(v => v.UpdateMany(It.Is>(s => s.First().ForeignReleaseId == newInfo.ForeignReleaseId))); + + } + + [Test] + public void child_merge_targets_should_not_be_null_if_target_is_new() + { + var oldTrack = Builder + .CreateNew() + .With(x => x.AlbumReleaseId = _release.Id) + .With(x => x.ArtistMetadata = _metadata) + .With(x => x.ArtistMetadataId = _metadata.Id) + .Build(); + _release.Tracks = new List { oldTrack }; + + var newInfo = _release.JsonClone(); + var newTrack = oldTrack.JsonClone(); + newTrack.ArtistMetadata = _metadata; + newTrack.ArtistMetadataId = _metadata.Id; + newTrack.ForeignTrackId = "new id"; + newTrack.OldForeignTrackIds = new List { oldTrack.ForeignTrackId }; + newInfo.Tracks = new List { newTrack }; + + Mocker.GetMock() + .Setup(s => s.GetTracksForRefresh(_release.Id, It.IsAny>())) + .Returns(new List { oldTrack }); + + Subject.RefreshEntityInfo(_release, new List { newInfo }, false, false); + + Mocker.GetMock() + .Verify(v => v.RefreshTrackInfo(It.IsAny>(), + It.IsAny>(), + It.Is>>(x => x.All(y => y.Item2 != null)), + It.IsAny>(), + It.IsAny>(), + It.IsAny>(), + It.IsAny())); + + Mocker.GetMock() + .Verify(v => v.UpdateMany(It.Is>(s => s.First().ForeignReleaseId == newInfo.ForeignReleaseId))); + + } + } +} diff --git a/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumServiceFixture.cs b/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumServiceFixture.cs index c6e454ca5..d1a44e647 100644 --- a/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumServiceFixture.cs @@ -9,10 +9,7 @@ using NzbDrone.Core.Exceptions; using NzbDrone.Core.MetadataSource; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Music; -using NzbDrone.Core.Music.Commands; using NzbDrone.Test.Common; -using FluentAssertions; -using NzbDrone.Common.Serializer; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.History; @@ -181,51 +178,6 @@ namespace NzbDrone.Core.Test.MusicTests ExceptionVerification.ExpectedWarns(1); } - [Test] - public void two_equivalent_albums_should_be_equal() - { - var album = Builder.CreateNew().Build(); - var album2 = Builder.CreateNew().Build(); - - ReferenceEquals(album, album2).Should().BeFalse(); - album.Equals(album2).Should().BeTrue(); - } - - [Test] - public void two_equivalent_releases_should_be_equal() - { - var release = Builder.CreateNew().Build(); - var release2 = Builder.CreateNew().Build(); - - ReferenceEquals(release, release2).Should().BeFalse(); - release.Equals(release2).Should().BeTrue(); - - release.Label?.ToJson().Should().Be(release2.Label?.ToJson()); - release.Country?.ToJson().Should().Be(release2.Country?.ToJson()); - release.Media?.ToJson().Should().Be(release2.Media?.ToJson()); - - } - - [Test] - public void two_equivalent_tracks_should_be_equal() - { - var track = Builder.CreateNew().Build(); - var track2 = Builder.CreateNew().Build(); - - ReferenceEquals(track, track2).Should().BeFalse(); - track.Equals(track2).Should().BeTrue(); - } - - [Test] - public void two_equivalent_metadata_should_be_equal() - { - var meta = Builder.CreateNew().Build(); - var meta2 = Builder.CreateNew().Build(); - - ReferenceEquals(meta, meta2).Should().BeFalse(); - meta.Equals(meta2).Should().BeTrue(); - } - [Test] public void should_not_add_duplicate_releases() { diff --git a/src/NzbDrone.Core.Test/MusicTests/RefreshTrackServiceFixture.cs b/src/NzbDrone.Core.Test/MusicTests/RefreshTrackServiceFixture.cs new file mode 100644 index 000000000..7ff5a21f0 --- /dev/null +++ b/src/NzbDrone.Core.Test/MusicTests/RefreshTrackServiceFixture.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FizzWare.NBuilder; +using Moq; +using NUnit.Framework; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Music; +using NzbDrone.Core.MediaFiles; + +namespace NzbDrone.Core.Test.MusicTests +{ + [TestFixture] + public class RefreshTrackServiceFixture : CoreTest + { + private AlbumRelease _release; + private List _allTracks; + + [SetUp] + public void Setup() + { + _release = Builder.CreateNew().Build(); + _allTracks = Builder.CreateListOfSize(20) + .All() + .BuildList(); + } + + [Test] + public void updated_track_should_not_have_null_album_release() + { + var add = new List(); + var update = new List(); + var merge = new List>(); + var delete = new List(); + var upToDate = new List(); + + upToDate.AddRange(_allTracks.Take(10)); + + var toUpdate = _allTracks[10].JsonClone(); + toUpdate.Title = "title to update"; + toUpdate.AlbumRelease = _release; + + update.Add(toUpdate); + + Subject.RefreshTrackInfo(add, update, merge, delete, upToDate, _allTracks, false); + + Mocker.GetMock() + .Verify(v => v.SyncTags(It.Is>(x => x.Count == 1 && + x[0].AlbumRelease != null && + x[0].AlbumRelease.IsLoaded == true))); + + } + } +} diff --git a/src/NzbDrone.Core/Lidarr.Core.csproj b/src/NzbDrone.Core/Lidarr.Core.csproj index f1ab32a5c..7d3bdafa9 100644 --- a/src/NzbDrone.Core/Lidarr.Core.csproj +++ b/src/NzbDrone.Core/Lidarr.Core.csproj @@ -16,6 +16,7 @@ + diff --git a/src/NzbDrone.Core/MediaCover/MediaCover.cs b/src/NzbDrone.Core/MediaCover/MediaCover.cs index bdca37a68..4e5c42b7d 100644 --- a/src/NzbDrone.Core/MediaCover/MediaCover.cs +++ b/src/NzbDrone.Core/MediaCover/MediaCover.cs @@ -1,5 +1,6 @@ using System.IO; using NzbDrone.Common.Extensions; +using Equ; using NzbDrone.Core.Datastore; namespace NzbDrone.Core.MediaCover @@ -24,7 +25,7 @@ namespace NzbDrone.Core.MediaCover Album = 1 } - public class MediaCover : IEmbeddedDocument + public class MediaCover : MemberwiseEquatable, IEmbeddedDocument { private string _url; public string Url diff --git a/src/NzbDrone.Core/Music/Album.cs b/src/NzbDrone.Core/Music/Album.cs index 6eec0f258..2ece54b34 100644 --- a/src/NzbDrone.Core/Music/Album.cs +++ b/src/NzbDrone.Core/Music/Album.cs @@ -1,26 +1,26 @@ using NzbDrone.Common.Extensions; -using NzbDrone.Core.Datastore; using System; using System.Collections.Generic; using Marr.Data; +using Equ; using System.Linq; -using NzbDrone.Common.Serializer; namespace NzbDrone.Core.Music { - public class Album : ModelBase, IEquatable + public class Album : Entity { public Album() { - Genres = new List(); + OldForeignAlbumIds = new List(); + Images = new List(); Links = new List(); + Genres = new List(); + SecondaryTypes = new List(); Ratings = new Ratings(); Artist = new Artist(); - OldForeignAlbumIds = new List(); - } - public const string RELEASE_DATE_FORMAT = "yyyy-MM-dd"; + } // These correspond to columns in the Albums table // These are metadata entries @@ -45,14 +45,19 @@ namespace NzbDrone.Core.Music public bool AnyReleaseOk { get; set; } public DateTime? LastInfoSync { get; set; } public DateTime Added { get; set; } + [MemberwiseEqualityIgnore] public AddArtistOptions AddOptions { get; set; } // These are dynamically queried from other tables + [MemberwiseEqualityIgnore] public LazyLoaded ArtistMetadata { get; set; } + [MemberwiseEqualityIgnore] public LazyLoaded> AlbumReleases { get; set; } + [MemberwiseEqualityIgnore] public LazyLoaded Artist { get; set; } //compatibility properties with old version of Album + [MemberwiseEqualityIgnore] public int ArtistId { get { return Artist?.Value?.Id ?? 0; } set { Artist.Value.Id = value; } } public override string ToString() @@ -60,80 +65,42 @@ namespace NzbDrone.Core.Music return string.Format("[{0}][{1}]", ForeignAlbumId, Title.NullSafe()); } - public void ApplyChanges(Album otherAlbum) + public override void UseMetadataFrom(Album other) { - ForeignAlbumId = otherAlbum.ForeignAlbumId; - ProfileId = otherAlbum.ProfileId; - AddOptions = otherAlbum.AddOptions; - Monitored = otherAlbum.Monitored; - AnyReleaseOk = otherAlbum.AnyReleaseOk; + ForeignAlbumId = other.ForeignAlbumId; + OldForeignAlbumIds = other.OldForeignAlbumIds; + Title = other.Title; + Overview = other.Overview.IsNullOrWhiteSpace() ? Overview : other.Overview; + Disambiguation = other.Disambiguation; + ReleaseDate = other.ReleaseDate; + Images = other.Images.Any() ? other.Images : Images; + Links = other.Links; + Genres = other.Genres; + AlbumType = other.AlbumType; + SecondaryTypes = other.SecondaryTypes; + Ratings = other.Ratings; + CleanTitle = other.CleanTitle; } - public bool Equals(Album other) + public override void UseDbFieldsFrom(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); - } + Id = other.Id; + ArtistMetadataId = other.ArtistMetadataId; + ProfileId = other.ProfileId; + Monitored = other.Monitored; + AnyReleaseOk = other.AnyReleaseOk; + LastInfoSync = other.LastInfoSync; + Added = other.Added; + AddOptions = other.AddOptions; } - public override int GetHashCode() + public override void ApplyChanges(Album otherAlbum) { - 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; - } + ForeignAlbumId = otherAlbum.ForeignAlbumId; + ProfileId = otherAlbum.ProfileId; + AddOptions = otherAlbum.AddOptions; + Monitored = otherAlbum.Monitored; + AnyReleaseOk = otherAlbum.AnyReleaseOk; } } } diff --git a/src/NzbDrone.Core/Music/Artist.cs b/src/NzbDrone.Core/Music/Artist.cs index 75d57df9f..77636b27e 100644 --- a/src/NzbDrone.Core/Music/Artist.cs +++ b/src/NzbDrone.Core/Music/Artist.cs @@ -1,16 +1,14 @@ using Marr.Data; using NzbDrone.Common.Extensions; -using NzbDrone.Core.Datastore; using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Profiles.Metadata; using System; using System.Collections.Generic; -using System.Linq; -using System.Text; +using Equ; namespace NzbDrone.Core.Music { - public class Artist : ModelBase + public class Artist : Entity { public Artist() { @@ -18,8 +16,8 @@ namespace NzbDrone.Core.Music Metadata = new ArtistMetadata(); } + // These correspond to columns in the Artists table public int ArtistMetadataId { get; set; } - public LazyLoaded Metadata { get; set; } public string CleanName { get; set; } public string SortName { get; set; } public bool Monitored { get; set; } @@ -29,25 +27,62 @@ namespace NzbDrone.Core.Music public string RootFolderPath { get; set; } public DateTime Added { get; set; } public int QualityProfileId { get; set; } + public int MetadataProfileId { get; set; } + public HashSet Tags { get; set; } + [MemberwiseEqualityIgnore] + public AddArtistOptions AddOptions { get; set; } + + // Dynamically loaded from DB + [MemberwiseEqualityIgnore] + public LazyLoaded Metadata { get; set; } + [MemberwiseEqualityIgnore] public LazyLoaded QualityProfile { get; set; } - public int MetadataProfileId { get; set; } + [MemberwiseEqualityIgnore] public LazyLoaded MetadataProfile { get; set; } + [MemberwiseEqualityIgnore] public LazyLoaded> Albums { get; set; } - public HashSet Tags { get; set; } - public AddArtistOptions AddOptions { get; set; } + + //compatibility properties + [MemberwiseEqualityIgnore] + public string Name { get { return Metadata.Value.Name; } set { Metadata.Value.Name = value; } } + [MemberwiseEqualityIgnore] + public string ForeignArtistId { get { return Metadata.Value.ForeignArtistId; } set { Metadata.Value.ForeignArtistId = value; } } public override string ToString() { - return string.Format("[{0}][{1}]", Metadata.Value.ForeignArtistId, Metadata.Value.Name.NullSafe()); + return string.Format("[{0}][{1}]", Metadata.Value.ForeignArtistId.NullSafe(), Metadata.Value.Name.NullSafe()); } - public void ApplyChanges(Artist otherArtist) + public override void UseMetadataFrom(Artist other) + { + CleanName = other.CleanName; + SortName = other.SortName; + } + + public override void UseDbFieldsFrom(Artist other) + { + Id = other.Id; + ArtistMetadataId = other.ArtistMetadataId; + Monitored = other.Monitored; + AlbumFolder = other.AlbumFolder; + LastInfoSync = other.LastInfoSync; + Path = other.Path; + RootFolderPath = other.RootFolderPath; + Added = other.Added; + QualityProfileId = other.QualityProfileId; + MetadataProfileId = other.MetadataProfileId; + Tags = other.Tags; + AddOptions = other.AddOptions; + } + + public override void ApplyChanges(Artist otherArtist) { Path = otherArtist.Path; QualityProfileId = otherArtist.QualityProfileId; QualityProfile = otherArtist.QualityProfile; MetadataProfileId = otherArtist.MetadataProfileId; + MetadataProfile = otherArtist.MetadataProfile; Albums = otherArtist.Albums; Tags = otherArtist.Tags; @@ -57,10 +92,5 @@ namespace NzbDrone.Core.Music AlbumFolder = otherArtist.AlbumFolder; } - - //compatibility properties - public string Name { get { return Metadata.Value.Name; } set { Metadata.Value.Name = value; } } - public string ForeignArtistId { get { return Metadata.Value.ForeignArtistId; } set { Metadata.Value.ForeignArtistId = value; } } - } } diff --git a/src/NzbDrone.Core/Music/ArtistMetadata.cs b/src/NzbDrone.Core/Music/ArtistMetadata.cs index 45732c7c4..bb56bf7c9 100644 --- a/src/NzbDrone.Core/Music/ArtistMetadata.cs +++ b/src/NzbDrone.Core/Music/ArtistMetadata.cs @@ -1,13 +1,10 @@ using NzbDrone.Common.Extensions; -using NzbDrone.Common.Serializer; -using NzbDrone.Core.Datastore; -using System; using System.Collections.Generic; using System.Linq; namespace NzbDrone.Core.Music { - public class ArtistMetadata : ModelBase, IEquatable + public class ArtistMetadata : Entity { public ArtistMetadata() { @@ -38,90 +35,21 @@ namespace NzbDrone.Core.Music return string.Format("[{0}][{1}]", ForeignArtistId, Name.NullSafe()); } - public void ApplyChanges(ArtistMetadata otherArtist) + public override void UseMetadataFrom(ArtistMetadata other) { - ForeignArtistId = otherArtist.ForeignArtistId; - OldForeignArtistIds = otherArtist.OldForeignArtistIds; - Name = otherArtist.Name; - Aliases = otherArtist.Aliases; - Overview = otherArtist.Overview.IsNullOrWhiteSpace() ? Overview : otherArtist.Overview; - Disambiguation = otherArtist.Disambiguation; - Type = otherArtist.Type; - Status = otherArtist.Status; - Images = otherArtist.Images.Any() ? otherArtist.Images : Images; - Links = otherArtist.Links; - Genres = otherArtist.Genres; - Ratings = otherArtist.Ratings; - Members = otherArtist.Members; - } - - public bool Equals(ArtistMetadata other) - { - if (other == null) - { - return false; - } - - if (Id == other.Id && - ForeignArtistId == other.ForeignArtistId && - (OldForeignArtistIds?.SequenceEqual(other.OldForeignArtistIds) ?? true) && - Name == other.Name && - (Aliases?.SequenceEqual(other.Aliases) ?? true) && - Overview == other.Overview && - Disambiguation == other.Disambiguation && - Type == other.Type && - Status == other.Status && - Images?.ToJson() == other.Images?.ToJson() && - Links?.ToJson() == other.Links?.ToJson() && - (Genres?.SequenceEqual(other.Genres) ?? true) && - Ratings?.ToJson() == other.Ratings?.ToJson() && - Members?.ToJson() == other.Members?.ToJson()) - { - return true; - } - - return false; - } - - public override bool Equals(object obj) - { - if (obj == null) - { - return false; - } - - var other = obj as ArtistMetadata; - if (other == null) - { - return false; - } - else - { - return Equals(other); - } - } - - public override int GetHashCode() - { - unchecked - { - int hash = 17; - hash = hash * 23 + Id; - hash = hash * 23 + ForeignArtistId.GetHashCode(); - hash = hash * 23 + OldForeignArtistIds.GetHashCode(); - hash = hash * 23 + Name?.GetHashCode() ?? 0; - hash = hash * 23 + Aliases?.GetHashCode() ?? 0; - hash = hash * 23 + Overview?.GetHashCode() ?? 0; - hash = hash * 23 + Disambiguation?.GetHashCode() ?? 0; - hash = hash * 23 + Type?.GetHashCode() ?? 0; - hash = hash * 23 + (int)Status; - hash = hash * 23 + Images?.GetHashCode() ?? 0; - hash = hash * 23 + Links?.GetHashCode() ?? 0; - hash = hash * 23 + Genres?.GetHashCode() ?? 0; - hash = hash * 23 + Ratings?.GetHashCode() ?? 0; - hash = hash * 23 + Members?.GetHashCode() ?? 0; - return hash; - } + ForeignArtistId = other.ForeignArtistId; + OldForeignArtistIds = other.OldForeignArtistIds; + Name = other.Name; + Aliases = other.Aliases; + Overview = other.Overview.IsNullOrWhiteSpace() ? Overview : other.Overview; + Disambiguation = other.Disambiguation; + Type = other.Type; + Status = other.Status; + Images = other.Images.Any() ? other.Images : Images; + Links = other.Links; + Genres = other.Genres; + Ratings = other.Ratings; + Members = other.Members; } } } diff --git a/src/NzbDrone.Core/Music/ArtistMetadataRepository.cs b/src/NzbDrone.Core/Music/ArtistMetadataRepository.cs index 2890f4dee..8ef16f41a 100644 --- a/src/NzbDrone.Core/Music/ArtistMetadataRepository.cs +++ b/src/NzbDrone.Core/Music/ArtistMetadataRepository.cs @@ -39,7 +39,7 @@ namespace NzbDrone.Core.Music var existing = existingMetadata.SingleOrDefault(x => x.ForeignArtistId == meta.ForeignArtistId); if (existing != null) { - meta.Id = existing.Id; + meta.UseDbFieldsFrom(existing); if (!meta.Equals(existing)) { updateMetadataList.Add(meta); diff --git a/src/NzbDrone.Core/Music/Entity.cs b/src/NzbDrone.Core/Music/Entity.cs new file mode 100644 index 000000000..b0dc4d822 --- /dev/null +++ b/src/NzbDrone.Core/Music/Entity.cs @@ -0,0 +1,37 @@ +using NzbDrone.Core.Datastore; +using System; +using Equ; + +namespace NzbDrone.Core.Music +{ + public abstract class Entity : ModelBase, IEquatable + where T : Entity + { + private static readonly MemberwiseEqualityComparer _comparer = + MemberwiseEqualityComparer.ByProperties; + + public virtual void UseDbFieldsFrom(T other) + { + Id = other.Id; + } + + public virtual void UseMetadataFrom(T other) { } + + public virtual void ApplyChanges(T other) { } + + public bool Equals(T other) + { + return _comparer.Equals(this as T, other); + } + + public override bool Equals(object obj) + { + return Equals(obj as T); + } + + public override int GetHashCode() + { + return _comparer.GetHashCode(this as T); + } + } +} diff --git a/src/NzbDrone.Core/Music/Links.cs b/src/NzbDrone.Core/Music/Links.cs index abf47eafc..19df7f5fd 100644 --- a/src/NzbDrone.Core/Music/Links.cs +++ b/src/NzbDrone.Core/Music/Links.cs @@ -1,8 +1,9 @@ +using Equ; using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Music { - public class Links : IEmbeddedDocument + public class Links : MemberwiseEquatable, IEmbeddedDocument { public string Url { get; set; } public string Name { get; set; } diff --git a/src/NzbDrone.Core/Music/Medium.cs b/src/NzbDrone.Core/Music/Medium.cs index 02ada9896..63a9a72c4 100644 --- a/src/NzbDrone.Core/Music/Medium.cs +++ b/src/NzbDrone.Core/Music/Medium.cs @@ -1,9 +1,9 @@ -using System.Collections.Generic; +using Equ; using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Music { - public class Medium : IEmbeddedDocument + public class Medium : MemberwiseEquatable, IEmbeddedDocument { public int Number { get; set; } public string Name { get; set; } diff --git a/src/NzbDrone.Core/Music/Member.cs b/src/NzbDrone.Core/Music/Member.cs index e948c9936..34f3bcbc2 100644 --- a/src/NzbDrone.Core/Music/Member.cs +++ b/src/NzbDrone.Core/Music/Member.cs @@ -1,9 +1,10 @@ using System.Collections.Generic; +using Equ; using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Music { - public class Member : IEmbeddedDocument + public class Member : MemberwiseEquatable, IEmbeddedDocument { public Member() { diff --git a/src/NzbDrone.Core/Music/Ratings.cs b/src/NzbDrone.Core/Music/Ratings.cs index 530e5a168..ae3a0526a 100644 --- a/src/NzbDrone.Core/Music/Ratings.cs +++ b/src/NzbDrone.Core/Music/Ratings.cs @@ -1,8 +1,9 @@ -using NzbDrone.Core.Datastore; +using Equ; +using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Music { - public class Ratings : IEmbeddedDocument + public class Ratings : MemberwiseEquatable, IEmbeddedDocument { public int Votes { get; set; } public decimal Value { get; set; } diff --git a/src/NzbDrone.Core/Music/RefreshAlbumReleaseService.cs b/src/NzbDrone.Core/Music/RefreshAlbumReleaseService.cs index ce96500c1..ef8cec763 100644 --- a/src/NzbDrone.Core/Music/RefreshAlbumReleaseService.cs +++ b/src/NzbDrone.Core/Music/RefreshAlbumReleaseService.cs @@ -54,18 +54,9 @@ namespace NzbDrone.Core.Music { 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; - + + local.UseMetadataFrom(remote); + return UpdateResult.UpdateTags; } diff --git a/src/NzbDrone.Core/Music/RefreshAlbumService.cs b/src/NzbDrone.Core/Music/RefreshAlbumService.cs index bcc214d45..8aa740d07 100644 --- a/src/NzbDrone.Core/Music/RefreshAlbumService.cs +++ b/src/NzbDrone.Core/Music/RefreshAlbumService.cs @@ -120,7 +120,8 @@ namespace NzbDrone.Core.Music QualityProfileId = oldArtist.QualityProfileId, RootFolderPath = oldArtist.RootFolderPath, Monitored = oldArtist.Monitored, - AlbumFolder = oldArtist.AlbumFolder + AlbumFolder = oldArtist.AlbumFolder, + Tags = oldArtist.Tags }; _logger.Debug($"Adding missing parent artist {addArtist}"); _addArtistService.AddArtist(addArtist); @@ -162,27 +163,16 @@ namespace NzbDrone.Core.Music } // Force update and fetch covers if images have changed so that we can write them into tags - if (remote.Images.Any() && !local.Images.Select(x => x.Url).SequenceEqual(remote.Images.Select(x => x.Url))) + if (remote.Images.Any() && !local.Images.SequenceEqual(remote.Images)) { _mediaCoverService.EnsureAlbumCovers(remote); result = UpdateResult.UpdateTags; } - + + local.UseMetadataFrom(remote); + 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(); return result; @@ -274,10 +264,8 @@ namespace NzbDrone.Core.Music { local.AlbumId = entity.Id; local.Album = entity; - remote.Id = local.Id; - remote.Album = entity; - remote.AlbumId = entity.Id; - remote.Monitored = local.Monitored; + + remote.UseDbFieldsFrom(local); } protected override void AddChildren(List children) diff --git a/src/NzbDrone.Core/Music/RefreshArtistService.cs b/src/NzbDrone.Core/Music/RefreshArtistService.cs index 154162aec..5d3dd2fda 100644 --- a/src/NzbDrone.Core/Music/RefreshArtistService.cs +++ b/src/NzbDrone.Core/Music/RefreshArtistService.cs @@ -103,8 +103,8 @@ namespace NzbDrone.Core.Music result = UpdateResult.UpdateTags; } - local.CleanName = remote.CleanName; - local.SortName = remote.SortName; + local.UseMetadataFrom(remote); + local.Metadata = remote.Metadata; local.LastInfoSync = DateTime.UtcNow; try diff --git a/src/NzbDrone.Core/Music/RefreshEntityServiceBase.cs b/src/NzbDrone.Core/Music/RefreshEntityServiceBase.cs index b802bc3bf..574f9140c 100644 --- a/src/NzbDrone.Core/Music/RefreshEntityServiceBase.cs +++ b/src/NzbDrone.Core/Music/RefreshEntityServiceBase.cs @@ -259,7 +259,7 @@ namespace NzbDrone.Core.Music // note the children that will be merged into remoteChild (once added) foreach (var child in mergedChildren) { - sortedChildren.Merged.Add(Tuple.Create(child, existingChild)); + sortedChildren.Merged.Add(Tuple.Create(child, remoteChild)); sortedChildren.Deleted.Remove(child); } diff --git a/src/NzbDrone.Core/Music/RefreshTrackService.cs b/src/NzbDrone.Core/Music/RefreshTrackService.cs index 5e3a3b5b4..cb4ad38f8 100644 --- a/src/NzbDrone.Core/Music/RefreshTrackService.cs +++ b/src/NzbDrone.Core/Music/RefreshTrackService.cs @@ -31,13 +31,11 @@ namespace NzbDrone.Core.Music var updateList = new List(); // for tracks that need updating, just grab the remote track and set db ids - foreach (var trackToUpdate in update) + foreach (var track in update) { - var track = remoteTracks.Single(e => e.ForeignTrackId == trackToUpdate.ForeignTrackId); + var remoteTrack = remoteTracks.Single(e => e.ForeignTrackId == track.ForeignTrackId); + track.UseMetadataFrom(remoteTrack); - // 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); @@ -60,6 +58,9 @@ namespace NzbDrone.Core.Music } } + _trackService.DeleteMany(delete.Concat(merge.Select(x => x.Item1)).ToList()); + _trackService.UpdateMany(updateList); + var tagsToUpdate = updateList; if (forceUpdateFileTags) { @@ -67,9 +68,6 @@ namespace NzbDrone.Core.Music tagsToUpdate = updateList.Concat(upToDate).ToList(); } _audioTagService.SyncTags(tagsToUpdate); - - _trackService.DeleteMany(delete.Concat(merge.Select(x => x.Item1)).ToList()); - _trackService.UpdateMany(updateList); return delete.Any() || updateList.Any() || merge.Any(); } diff --git a/src/NzbDrone.Core/Music/Release.cs b/src/NzbDrone.Core/Music/Release.cs index 54ece47f6..2ad6936af 100644 --- a/src/NzbDrone.Core/Music/Release.cs +++ b/src/NzbDrone.Core/Music/Release.cs @@ -1,18 +1,19 @@ using NzbDrone.Common.Extensions; -using NzbDrone.Core.Datastore; using System; using System.Collections.Generic; -using System.Linq; using Marr.Data; -using NzbDrone.Common.Serializer; +using Equ; namespace NzbDrone.Core.Music { - public class AlbumRelease : ModelBase, IEquatable + public class AlbumRelease : Entity { public AlbumRelease() { OldForeignReleaseIds = new List(); + Label = new List(); + Country = new List(); + Media = new List(); } // These correspond to columns in the AlbumReleases table @@ -31,7 +32,9 @@ namespace NzbDrone.Core.Music public bool Monitored { get; set; } // These are dynamically queried from other tables + [MemberwiseEqualityIgnore] public LazyLoaded Album { get; set; } + [MemberwiseEqualityIgnore] public LazyLoaded> Tracks { get; set; } public override string ToString() @@ -39,73 +42,27 @@ namespace NzbDrone.Core.Music return string.Format("[{0}][{1}]", ForeignReleaseId, Title.NullSafe()); } - public bool Equals (AlbumRelease other) + public override void UseMetadataFrom(AlbumRelease other) { - if (other == null) - { - return false; - } - - if (Id == other.Id && - AlbumId == other.AlbumId && - ForeignReleaseId == other.ForeignReleaseId && - (OldForeignReleaseIds?.SequenceEqual(other.OldForeignReleaseIds) ?? true) && - Title == other.Title && - Status == other.Status && - Duration == other.Duration && - (Label?.SequenceEqual(other.Label) ?? true) && - Disambiguation == other.Disambiguation && - (Country?.SequenceEqual(other.Country) ?? true) && - ReleaseDate == other.ReleaseDate && - ((Media == null && other.Media == null) || (Media?.ToJson() == other.Media?.ToJson())) && - TrackCount == other.TrackCount && - Monitored == other.Monitored) - { - return true; - } - - return false; - } - - public override bool Equals(object obj) - { - if (obj == null) - { - return false; - } - - var other = obj as AlbumRelease; - if (other == null) - { - return false; - } - else - { - return Equals(other); - } + ForeignReleaseId = other.ForeignReleaseId; + OldForeignReleaseIds = other.OldForeignReleaseIds; + Title = other.Title; + Status = other.Status; + Duration = other.Duration; + Label = other.Label; + Disambiguation = other.Disambiguation; + Country = other.Country; + ReleaseDate = other.ReleaseDate; + Media = other.Media; + TrackCount = other.TrackCount; } - public override int GetHashCode() + public override void UseDbFieldsFrom(AlbumRelease other) { - unchecked - { - int hash = 17; - hash = hash * 23 + Id; - hash = hash * 23 + AlbumId; - hash = hash * 23 + ForeignReleaseId.GetHashCode(); - hash = hash * 23 + OldForeignReleaseIds?.GetHashCode() ?? 0; - hash = hash * 23 + Title?.GetHashCode() ?? 0; - hash = hash * 23 + Status?.GetHashCode() ?? 0; - hash = hash * 23 + Duration; - hash = hash * 23 + Label?.GetHashCode() ?? 0; - hash = hash * 23 + Disambiguation?.GetHashCode() ?? 0; - hash = hash * 23 + Country?.GetHashCode() ?? 0; - hash = hash * 23 + ReleaseDate.GetHashCode(); - hash = hash * 23 + Media?.GetHashCode() ?? 0; - hash = hash * 23 + TrackCount; - hash = hash * 23 + Monitored.GetHashCode(); - return hash; - } + Id = other.Id; + AlbumId = other.AlbumId; + Album = other.Album; + Monitored = other.Monitored; } } } diff --git a/src/NzbDrone.Core/Music/Track.cs b/src/NzbDrone.Core/Music/Track.cs index d0a1bae23..ceaabc3d9 100644 --- a/src/NzbDrone.Core/Music/Track.cs +++ b/src/NzbDrone.Core/Music/Track.cs @@ -1,20 +1,18 @@ -using NzbDrone.Core.Datastore; using NzbDrone.Core.MediaFiles; using Marr.Data; using NzbDrone.Common.Extensions; -using System; -using NzbDrone.Common.Serializer; using System.Collections.Generic; -using System.Linq; +using Equ; namespace NzbDrone.Core.Music { - public class Track : ModelBase, IEquatable + public class Track : Entity { public Track() { OldForeignTrackIds = new List(); OldForeignRecordingIds = new List(); + Ratings = new Ratings(); } // These are model fields @@ -32,17 +30,25 @@ namespace NzbDrone.Core.Music public Ratings Ratings { get; set; } public int MediumNumber { get; set; } public int TrackFileId { get; set; } + + [MemberwiseEqualityIgnore] public bool HasFile => TrackFileId > 0; // These are dynamically queried from the DB + [MemberwiseEqualityIgnore] public LazyLoaded AlbumRelease { get; set; } + [MemberwiseEqualityIgnore] public LazyLoaded ArtistMetadata { get; set; } + [MemberwiseEqualityIgnore] public LazyLoaded TrackFile { get; set; } + [MemberwiseEqualityIgnore] public LazyLoaded Artist { get; set; } // These are retained for compatibility // TODO: Remove set, bodged in because tests expect this to be writable + [MemberwiseEqualityIgnore] public int AlbumId { get { return AlbumRelease?.Value?.Album?.Value?.Id ?? 0; } set { /* empty */ } } + [MemberwiseEqualityIgnore] public Album Album { get; set; } public override string ToString() @@ -50,75 +56,27 @@ namespace NzbDrone.Core.Music return string.Format("[{0}]{1}", ForeignTrackId, Title.NullSafe()); } - public bool Equals(Track other) + public override void UseMetadataFrom(Track other) { - if (other == null) - { - return false; - } - - if (Id == other.Id && - ForeignTrackId == other.ForeignTrackId && - (OldForeignTrackIds?.SequenceEqual(other.OldForeignTrackIds) ?? true) && - ForeignRecordingId == other.ForeignRecordingId && - (OldForeignRecordingIds?.SequenceEqual(other.OldForeignRecordingIds) ?? true) && - AlbumReleaseId == other.AlbumReleaseId && - ArtistMetadataId == other.ArtistMetadataId && - TrackNumber == other.TrackNumber && - AbsoluteTrackNumber == other.AbsoluteTrackNumber && - Title == other.Title && - Duration == other.Duration && - Explicit == other.Explicit && - Ratings?.ToJson() == other.Ratings?.ToJson() && - MediumNumber == other.MediumNumber && - TrackFileId == other.TrackFileId) - { - return true; - } - - return false; - } - - public override bool Equals(object obj) - { - if (obj == null) - { - return false; - } - - var other = obj as Track; - if (other == null) - { - return false; - } - else - { - return Equals(other); - } + ForeignTrackId = other.ForeignTrackId; + OldForeignTrackIds = other.OldForeignTrackIds; + ForeignRecordingId = other.ForeignRecordingId; + OldForeignRecordingIds = other.OldForeignRecordingIds; + TrackNumber = other.TrackNumber; + AbsoluteTrackNumber = other.AbsoluteTrackNumber; + Title = other.Title; + Duration = other.Duration; + Explicit = other.Explicit; + Ratings = other.Ratings; + MediumNumber = other.MediumNumber; } - public override int GetHashCode() + public override void UseDbFieldsFrom(Track other) { - unchecked - { - int hash = 17; - hash = hash * 23 + Id; - hash = hash * 23 + ForeignTrackId.GetHashCode(); - hash = hash * 23 + OldForeignTrackIds?.GetHashCode() ?? 0; - hash = hash * 23 + ForeignRecordingId.GetHashCode(); - hash = hash * 23 + OldForeignRecordingIds?.GetHashCode() ?? 0; - hash = hash * 23 + AlbumReleaseId; - hash = hash * 23 + ArtistMetadataId; - hash = hash * 23 + TrackNumber?.GetHashCode() ?? 0; - hash = hash * 23 + AbsoluteTrackNumber; - hash = hash * 23 + Title?.GetHashCode() ?? 0; - hash = hash * 23 + Duration; - hash = hash * 23 + Explicit.GetHashCode(); - hash = hash * 23 + Ratings?.GetHashCode() ?? 0; - hash = hash * 23 + MediumNumber; - hash = hash * 23 + TrackFileId; - return hash; - } + Id = other.Id; + AlbumReleaseId = other.AlbumReleaseId; + ArtistMetadataId = other.ArtistMetadataId; + TrackFileId = other.TrackFileId; } } }