From 729509546019d2708c8a8932455f05440748ca43 Mon Sep 17 00:00:00 2001 From: Steven Hildreth Date: Mon, 1 Jul 2019 18:11:10 -0500 Subject: [PATCH] Implemented similar artists and fixed bug with deleting associated. --- Roadie.Api.Library.Tests/ImageHasherTests.cs | 4 +- Roadie.Api.Library/Data/Artist.cs | 4 + Roadie.Api.Library/Data/ArtistSimiliar.cs | 27 ++++++ Roadie.Api.Library/Data/IRoadieDbContext.cs | 1 + Roadie.Api.Library/Data/RoadieDbContext.cs | 1 + Roadie.Api.Library/Factories/ArtistFactory.cs | 2 + Roadie.Api.Library/Models/Artist.cs | 6 +- Roadie.Api.Services/AdminService.cs | 4 +- Roadie.Api.Services/ArtistService.cs | 97 ++++++++++++++++++- Roadie.Api.Services/SubsonicService.cs | 2 +- Upgrade0003.sql | 14 ++- 11 files changed, 154 insertions(+), 8 deletions(-) create mode 100644 Roadie.Api.Library/Data/ArtistSimiliar.cs diff --git a/Roadie.Api.Library.Tests/ImageHasherTests.cs b/Roadie.Api.Library.Tests/ImageHasherTests.cs index c93bd73..684469e 100644 --- a/Roadie.Api.Library.Tests/ImageHasherTests.cs +++ b/Roadie.Api.Library.Tests/ImageHasherTests.cs @@ -25,8 +25,8 @@ namespace Roadie.Library.Tests Assert.True(secondHash > 0); Assert.Equal(hash, secondHash); - var similiar = ImageHasher.Similarity(imageFilename, secondImagFilename); - Assert.Equal(100d, similiar); + var similar = ImageHasher.Similarity(imageFilename, secondImagFilename); + Assert.Equal(100d, similar); Assert.True(ImageHasher.ImagesAreSame(imageFilename, secondImagFilename)); diff --git a/Roadie.Api.Library/Data/Artist.cs b/Roadie.Api.Library/Data/Artist.cs index 97b14f0..1d385a3 100644 --- a/Roadie.Api.Library/Data/Artist.cs +++ b/Roadie.Api.Library/Data/Artist.cs @@ -19,6 +19,9 @@ namespace Roadie.Library.Data [InverseProperty("Artist")] public ICollection AssociatedArtists { get; set; } + [InverseProperty("Artist")] + public ICollection SimilarArtists { get; set; } + [Column("bandStatus", TypeName = "enum")] public BandStatus? BandStatus { get; set; } @@ -89,6 +92,7 @@ namespace Roadie.Library.Data this.Releases = new HashSet(); this.Genres = new HashSet(); this.AssociatedArtists = new HashSet(); + this.SimilarArtists = new HashSet(); this.Comments = new HashSet(); this.Rating = 0; this.Status = Statuses.Ok; diff --git a/Roadie.Api.Library/Data/ArtistSimiliar.cs b/Roadie.Api.Library/Data/ArtistSimiliar.cs new file mode 100644 index 0000000..571b514 --- /dev/null +++ b/Roadie.Api.Library/Data/ArtistSimiliar.cs @@ -0,0 +1,27 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Roadie.Library.Data +{ + [Table("artistSimilar")] + public partial class ArtistSimilar + { + //[ForeignKey("artistId")] + public Artist Artist { get; set; } + + [Column("artistId")] + [Required] + public int ArtistId { get; set; } + + //[ForeignKey("associatedArtistId")] + public Artist SimilarArtist { get; set; } + + [Column("similarArtistId")] + public int SimilarArtistId { get; set; } + + [Key] + [Column("id")] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + } +} \ No newline at end of file diff --git a/Roadie.Api.Library/Data/IRoadieDbContext.cs b/Roadie.Api.Library/Data/IRoadieDbContext.cs index 47f938e..60a1291 100644 --- a/Roadie.Api.Library/Data/IRoadieDbContext.cs +++ b/Roadie.Api.Library/Data/IRoadieDbContext.cs @@ -13,6 +13,7 @@ namespace Roadie.Library.Data public interface IRoadieDbContext : IDisposable, IInfrastructure, IDbContextDependencies, IDbSetCache, IDbQueryCache, IDbContextPoolable { DbSet ArtistAssociations { get; set; } + DbSet ArtistSimilar { get; set; } DbSet ArtistGenres { get; set; } DbSet Artists { get; set; } DbSet Bookmarks { get; set; } diff --git a/Roadie.Api.Library/Data/RoadieDbContext.cs b/Roadie.Api.Library/Data/RoadieDbContext.cs index 9f16288..114813d 100644 --- a/Roadie.Api.Library/Data/RoadieDbContext.cs +++ b/Roadie.Api.Library/Data/RoadieDbContext.cs @@ -8,6 +8,7 @@ namespace Roadie.Library.Data public class RoadieDbContext : DbContext, IRoadieDbContext { public DbSet ArtistAssociations { get; set; } + public DbSet ArtistSimilar { get; set; } public DbSet ArtistGenres { get; set; } public DbSet Artists { get; set; } public DbSet Bookmarks { get; set; } diff --git a/Roadie.Api.Library/Factories/ArtistFactory.cs b/Roadie.Api.Library/Factories/ArtistFactory.cs index fe31dad..15b13ac 100644 --- a/Roadie.Api.Library/Factories/ArtistFactory.cs +++ b/Roadie.Api.Library/Factories/ArtistFactory.cs @@ -375,6 +375,7 @@ namespace Roadie.Library.Factories var artistGenreTables = Artist.Genres.Select(x => new ArtistGenre { ArtistId = Artist.Id, GenreId = x.GenreId }).ToList(); var artistAssociatedWith = Artist.AssociatedArtists.Select(x => new ArtistAssociation { ArtistId = Artist.Id, AssociatedArtistId = x.AssociatedArtistId }).ToList(); + var similarArtists = Artist.SimilarArtists.Select(x => new ArtistSimilar { ArtistId = Artist.Id, SimilarArtistId = x.SimilarArtistId }).ToList(); var result = true; var now = DateTime.UtcNow; @@ -393,6 +394,7 @@ namespace Roadie.Library.Factories where at.ArtistId == Artist.Id select at)); Artist.AssociatedArtists = artistAssociatedWith; + Artist.SimilarArtists = similarArtists; await this.DbContext.SaveChangesAsync(); var existingImageIds = (from ai in ArtistImages diff --git a/Roadie.Api.Library/Models/Artist.cs b/Roadie.Api.Library/Models/Artist.cs index 39d4276..b479904 100644 --- a/Roadie.Api.Library/Models/Artist.cs +++ b/Roadie.Api.Library/Models/Artist.cs @@ -15,7 +15,7 @@ namespace Roadie.Library.Models [Serializable] public class Artist : EntityModelBase { - public const string DefaultIncludes = "stats,images,associatedartists,comments,collections,playlists,contributions,labels"; + public const string DefaultIncludes = "stats,images,associatedartists,similarartists,comments,collections,playlists,contributions,labels"; public IEnumerable ArtistContributionReleases; public IEnumerable ArtistLabels; @@ -29,6 +29,10 @@ namespace Roadie.Library.Models public IEnumerable AssociatedArtistsTokens { get; set; } + public IEnumerable SimilarArtists { get; set; } + + public IEnumerable SimilarArtistsTokens { get; set; } + public string BandStatus { get; set; } [MaxLength(65535)] diff --git a/Roadie.Api.Services/AdminService.cs b/Roadie.Api.Services/AdminService.cs index f483c48..f85ab41 100644 --- a/Roadie.Api.Services/AdminService.cs +++ b/Roadie.Api.Services/AdminService.cs @@ -667,8 +667,8 @@ namespace Roadie.Api.Services { CollectionId = collection.Id, Position = csvRelease.Position, - Artist = csvRelease.Artist, - Release = searchName + Artist = searchName, + Release = csvRelease.Release.NormalizeName() }); continue; } diff --git a/Roadie.Api.Services/ArtistService.cs b/Roadie.Api.Services/ArtistService.cs index 3316378..9d6641a 100644 --- a/Roadie.Api.Services/ArtistService.cs +++ b/Roadie.Api.Services/ArtistService.cs @@ -557,10 +557,48 @@ namespace Roadie.Api.Services }); } } + } else if (model.AssociatedArtistsTokens == null || !model.AssociatedArtistsTokens.Any()) { - artist.AssociatedArtists.Clear(); + var associatedArtists = DbContext.ArtistAssociations.Include(x => x.AssociatedArtist).Where(x => x.ArtistId == artist.Id || x.AssociatedArtistId == artist.Id).ToList(); + DbContext.ArtistAssociations.RemoveRange(associatedArtists); + } + + if(model.SimilarArtistsTokens != null && model.SimilarArtistsTokens.Any()) + { + var similarArtists = DbContext.ArtistSimilar.Include(x => x.SimilarArtist) + .Where(x => x.ArtistId == artist.Id).ToList(); + + // Remove existing AssociatedArtists not in model list + foreach (var similarArtist in similarArtists) + { + var doesExistInModel = model.SimilarArtistsTokens.Any(x => + SafeParser.ToGuid(x.Value) == similarArtist.SimilarArtist.RoadieId); + if (!doesExistInModel) DbContext.ArtistSimilar.Remove(similarArtist); + } + + // Add new SimilarArtists in model not in data + foreach (var similarArtist in model.SimilarArtistsTokens) + { + var similarArtistId = SafeParser.ToGuid(similarArtist.Value); + var doesExistInData = similarArtists.Any(x => x.SimilarArtist.RoadieId == similarArtistId); + if (!doesExistInData) + { + var a = DbContext.Artists.FirstOrDefault(x => x.RoadieId == similarArtistId); + if (a != null) + DbContext.ArtistSimilar.Add(new data.ArtistSimilar + { + ArtistId = artist.Id, + SimilarArtistId = a.Id + }); + } + } + } + else if (model.SimilarArtistsTokens == null || !model.SimilarArtistsTokens.Any()) + { + var similarArtists = DbContext.ArtistSimilar.Include(x => x.SimilarArtist).Where(x => x.ArtistId == artist.Id || x.SimilarArtistId == artist.Id).ToList(); + DbContext.ArtistSimilar.RemoveRange(similarArtists); } if (model.Images != null && model.Images.Any()) @@ -816,6 +854,63 @@ namespace Roadie.Api.Services timings.Add("associatedartists", tsw.ElapsedMilliseconds); } + if (includes.Contains("similarartists")) + { + tsw.Restart(); + var similarWithArtists = (from aa in DbContext.ArtistSimilar + join a in DbContext.Artists on aa.SimilarArtistId equals a.Id + where aa.ArtistId == artist.Id + select new ArtistList + { + DatabaseId = a.Id, + Id = a.RoadieId, + Artist = new DataToken + { + Text = a.Name, + Value = a.RoadieId.ToString() + }, + Thumbnail = MakeArtistThumbnailImage(a.RoadieId), + Rating = a.Rating, + Rank = a.Rank, + CreatedDate = a.CreatedDate, + LastUpdated = a.LastUpdated, + LastPlayed = a.LastPlayed, + PlayedCount = a.PlayedCount, + ReleaseCount = a.ReleaseCount, + TrackCount = a.TrackCount, + SortName = a.SortName + }).ToArray(); + + var similarArtists = (from aa in DbContext.ArtistSimilar + join a in DbContext.Artists on aa.ArtistId equals a.Id + where aa.SimilarArtistId == artist.Id + select new ArtistList + { + DatabaseId = a.Id, + Id = a.RoadieId, + Artist = new DataToken + { + Text = a.Name, + Value = a.RoadieId.ToString() + }, + Thumbnail = MakeArtistThumbnailImage(a.RoadieId), + Rating = a.Rating, + Rank = a.Rank, + CreatedDate = a.CreatedDate, + LastUpdated = a.LastUpdated, + LastPlayed = a.LastPlayed, + PlayedCount = a.PlayedCount, + ReleaseCount = a.ReleaseCount, + TrackCount = a.TrackCount, + SortName = a.SortName + }).ToArray(); + result.SimilarArtists = similarWithArtists.Union(similarArtists, new ArtistListComparer()) + .OrderBy(x => x.SortName); + result.SimilarArtistsTokens = result.SimilarArtists.Select(x => x.Artist).ToArray(); + tsw.Stop(); + timings.Add("similarartists", tsw.ElapsedMilliseconds); + } + if (includes.Contains("collections")) { tsw.Restart(); diff --git a/Roadie.Api.Services/SubsonicService.cs b/Roadie.Api.Services/SubsonicService.cs index f0ce9e1..b8d41f4 100644 --- a/Roadie.Api.Services/SubsonicService.cs +++ b/Roadie.Api.Services/SubsonicService.cs @@ -1269,7 +1269,7 @@ namespace Roadie.Api.Services public Task> GetSimliarSongs(subsonic.Request request, User roadieUser, subsonic.SimilarSongsVersion version, int? count = 50) { - // TODO How to determine similiar songs? Perhaps by genre? + // TODO How to determine similar songs? Perhaps by genre? switch (version) { diff --git a/Upgrade0003.sql b/Upgrade0003.sql index 084907b..be74297 100644 --- a/Upgrade0003.sql +++ b/Upgrade0003.sql @@ -8,4 +8,16 @@ CREATE TABLE `collectionMissing` ( `release` varchar(1000) COLLATE utf8mb4_unicode_ci NOT NULL, PRIMARY KEY (`id`), KEY `ix_collection_collectionId` (`collectionId`) -) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; \ No newline at end of file +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- Create artistSimilar table for < 1.0.2.0 database +CREATE TABLE `artistSimilar` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `artistId` int(11) NOT NULL, + `similarArtistId` int(11) NOT NULL, + PRIMARY KEY (`id`), + KEY `similarArtistId` (`similarArtistId`), + KEY `idx_artistSimilar` (`artistId`,`similarArtistId`), + CONSTRAINT `artistSimilar_ibfk_1` FOREIGN KEY (`artistId`) REFERENCES `artist` (`id`) ON DELETE CASCADE, + CONSTRAINT `artistSimilar_ibfk_2` FOREIGN KEY (`similarArtistId`) REFERENCES `artist` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci