From 97300534a4c6b44d8c872db65ce1c66b6b976ea1 Mon Sep 17 00:00:00 2001 From: Steven Hildreth Date: Sat, 16 Nov 2019 18:08:44 -0600 Subject: [PATCH] work for #18 --- Inspector/Inspector.csproj | 2 +- .../ArtistLookupEngineTests.cs | 168 --------------- Roadie.Api.Library.Tests/ImageHelperTests.cs | 4 +- Roadie.Api.Library/Data/DbContextFactory.cs | 26 +++ Roadie.Api.Library/Data/IRoadieDbContext.cs | 5 +- .../Data/IRoadieDbRandomizer.cs | 17 ++ Roadie.Api.Library/Data/IRoadieDbUserStats.cs | 19 ++ .../Data/MysqlRoadieDbContext.cs | 197 ++++++++++++++++++ Roadie.Api.Library/Data/RoadieDbContext.cs | 7 +- .../Data/RoadieDbContextPartial.cs | 31 +++ Roadie.Api.Library/Roadie.Library.csproj | 2 +- Roadie.Api.Services/ArtistService.cs | 22 +- Roadie.Api.Services/GenreService.cs | 17 +- Roadie.Api.Services/LabelService.cs | 17 +- Roadie.Api.Services/ReleaseService.cs | 22 +- Roadie.Api.Services/TrackService.cs | 76 +++---- Roadie.Api.Services/UserService.cs | 50 +---- Roadie.Api/Roadie.Api.csproj | 2 +- Roadie.Api/Startup.cs | 5 +- Roadie.Dlna.Services/DlnaHostService.cs | 17 +- Roadie.sln | 13 -- .../MySQL/Upgrade0001.sql | 0 .../MySQL/Upgrade0002.sql | 0 .../MySQL/Upgrade0003.sql | 0 .../MySQL/Upgrade0004.sql | 0 .../MySQL/Upgrade0005.sql | 0 .../MySQL/Upgrade0006.sql | 0 .../MySQL/Upgrade0007.sql | 0 .../MySQL/Upgrade0008.sql | 0 roadie.sql => Scripts/MySQL/roadie.sql | 0 30 files changed, 370 insertions(+), 349 deletions(-) create mode 100644 Roadie.Api.Library/Data/DbContextFactory.cs create mode 100644 Roadie.Api.Library/Data/IRoadieDbRandomizer.cs create mode 100644 Roadie.Api.Library/Data/IRoadieDbUserStats.cs create mode 100644 Roadie.Api.Library/Data/MysqlRoadieDbContext.cs create mode 100644 Roadie.Api.Library/Data/RoadieDbContextPartial.cs rename Upgrade0001.sql => Scripts/MySQL/Upgrade0001.sql (100%) rename Upgrade0002.sql => Scripts/MySQL/Upgrade0002.sql (100%) rename Upgrade0003.sql => Scripts/MySQL/Upgrade0003.sql (100%) rename Upgrade0004.sql => Scripts/MySQL/Upgrade0004.sql (100%) rename Upgrade0005.sql => Scripts/MySQL/Upgrade0005.sql (100%) rename Upgrade0006.sql => Scripts/MySQL/Upgrade0006.sql (100%) rename Upgrade0007.sql => Scripts/MySQL/Upgrade0007.sql (100%) rename Upgrade0008.sql => Scripts/MySQL/Upgrade0008.sql (100%) rename roadie.sql => Scripts/MySQL/roadie.sql (100%) diff --git a/Inspector/Inspector.csproj b/Inspector/Inspector.csproj index a79abbe..bdcb767 100644 --- a/Inspector/Inspector.csproj +++ b/Inspector/Inspector.csproj @@ -21,7 +21,7 @@ - + diff --git a/Roadie.Api.Library.Tests/ArtistLookupEngineTests.cs b/Roadie.Api.Library.Tests/ArtistLookupEngineTests.cs index 882acd0..f51da99 100644 --- a/Roadie.Api.Library.Tests/ArtistLookupEngineTests.cs +++ b/Roadie.Api.Library.Tests/ArtistLookupEngineTests.cs @@ -55,173 +55,5 @@ namespace Roadie.Library.Tests { Console.WriteLine($"Log Level [{ e.Level }] Log Message [{ e.Message }] "); } - - - //[Fact] - //public void Update_Genre_Normalized_Name() - //{ - // var optionsBuilder = new DbContextOptionsBuilder(); - // optionsBuilder.UseMySql("server=viking;userid=roadie;password=MenAtW0rk668;persistsecurityinfo=True;database=roadie;ConvertZeroDateTime=true"); - - // using (var context = new RoadieDbContext(optionsBuilder.Options)) - // { - // var now = DateTime.UtcNow; - // foreach (var genre in context.Genres) - // { - // genre.NormalizedName = genre.Name.ToAlphanumericName(); - // genre.LastUpdated = now; - // } - // context.SaveChanges(); - // } - //} - - //[Fact] - //public void Merge_Genres() - //{ - // var optionsBuilder = new DbContextOptionsBuilder(); - // optionsBuilder.UseMySql("server=viking;userid=roadie;password=MenAtW0rk668;persistsecurityinfo=True;database=roadie;ConvertZeroDateTime=true"); - - // using (var context = new RoadieDbContext(optionsBuilder.Options)) - // { - // var addedArtistToGenre = new List>(); - // var addedReleaseToGenre = new List>(); - // var now = DateTime.UtcNow; - // var groupedGenres = context.Genres.GroupBy(x => x.NormalizedName).ToArray(); - // foreach (var genreGroup in groupedGenres) - // { - // var genre = genreGroup.OrderBy(x => x.Id).First(); - // foreach (var gg in genreGroup.OrderBy(x => x.Id).Skip(1)) - // { - // var artistIdsInDups = (from g in context.Genres - // join ag in context.ArtistGenres on g.Id equals ag.GenreId - // where g.Id == gg.Id - // select ag.ArtistId).Distinct().ToArray(); - - // var releaseIdsInDups = (from g in context.Genres - // join rg in context.ReleaseGenres on g.Id equals rg.GenreId - // where g.Id == gg.Id - // select rg.ReleaseId).Distinct().ToArray(); - - // if (artistIdsInDups != null && artistIdsInDups.Any()) - // { - // foreach (var artistIdsInDup in artistIdsInDups) - // { - // if (!addedArtistToGenre.Any(x => x.Key == artistIdsInDup && x.Value == genre.Id)) - // { - // context.ArtistGenres.Add(new ArtistGenre - // { - // ArtistId = artistIdsInDup, - // GenreId = genre.Id - // }); - // addedArtistToGenre.Add(new KeyValuePair(artistIdsInDup, genre.Id)); - // } - // } - // } - - // if (releaseIdsInDups != null && releaseIdsInDups.Any()) - // { - // foreach (var releaseIdsInDup in releaseIdsInDups) - // { - // if (!addedReleaseToGenre.Any(x => x.Key == releaseIdsInDup && x.Value == genre.Id)) - // { - // context.ReleaseGenres.Add(new ReleaseGenre - // { - // ReleaseId = releaseIdsInDup, - // GenreId = genre.Id - // }); - // addedReleaseToGenre.Add(new KeyValuePair(releaseIdsInDup, genre.Id)); - // } - // } - // } - - // context.Genres.Remove(gg); - // context.SaveChanges(); - // } - // } - // } - //} - - - //[Fact] - //public void Update_Releases_Special_Name() - //{ - // var optionsBuilder = new DbContextOptionsBuilder(); - // optionsBuilder.UseMySql("server=viking;userid=roadie;password=MenAtW0rk668;persistsecurityinfo=True;database=roadie;ConvertZeroDateTime=true"); - - // using (var context = new RoadieDbContext(optionsBuilder.Options)) - // { - // var now = DateTime.UtcNow; - // foreach (var release in context.Releases) - // { - // var releaseModel = release.Adapt(); - // var specialReleaseTitle = release.Title.ToAlphanumericName(); - // if (!releaseModel.AlternateNamesList.Contains(specialReleaseTitle, StringComparer.OrdinalIgnoreCase)) - // { - // var alt = new List(releaseModel.AlternateNamesList) - // { - // specialReleaseTitle - // }; - // release.AlternateNames = alt.ToDelimitedList(); - // release.LastUpdated = now; - // } - // } - // context.SaveChanges(); - // } - //} - - //[Fact] - //public void Update_Artist_Special_Name() - //{ - // var optionsBuilder = new DbContextOptionsBuilder(); - // optionsBuilder.UseMySql("server=viking;userid=roadie;password=MenAtW0rk668;persistsecurityinfo=True;database=roadie;ConvertZeroDateTime=true"); - - // using (var context = new RoadieDbContext(optionsBuilder.Options)) - // { - // var now = DateTime.UtcNow; - // foreach (var artist in context.Artists) - // { - // var artistModel = artist.Adapt(); - // var specialArtistName = artist.Name.ToAlphanumericName(); - // if (!artistModel.AlternateNamesList.Contains(specialArtistName, StringComparer.OrdinalIgnoreCase)) - // { - // var alt = new List(artistModel.AlternateNamesList) - // { - // specialArtistName - // }; - // artist.AlternateNames = alt.ToDelimitedList(); - // artist.LastUpdated = now; - // } - // } - // context.SaveChanges(); - // } - //} - - //[Fact] - //public void Update_Label_Special_Name() - //{ - // var optionsBuilder = new DbContextOptionsBuilder(); - // optionsBuilder.UseMySql("server=viking;userid=roadie;password=MenAtW0rk668;persistsecurityinfo=True;database=roadie;ConvertZeroDateTime=true"); - - // using (var context = new RoadieDbContext(optionsBuilder.Options)) - // { - // var now = DateTime.UtcNow; - // foreach (var label in context.Labels) - // { - // var labelModel = label.Adapt(); - // var specialLabelName = labelModel.Name.ToAlphanumericName(); - // if (!labelModel.AlternateNamesList.Contains(specialLabelName, StringComparer.OrdinalIgnoreCase)) - // { - // var alt = new List(labelModel.AlternateNamesList) - // { - // specialLabelName - // }; - // label.AlternateNames = alt.ToDelimitedList(); - // label.LastUpdated = now; - // } - // } - // context.SaveChanges(); - // } - //} - } } diff --git a/Roadie.Api.Library.Tests/ImageHelperTests.cs b/Roadie.Api.Library.Tests/ImageHelperTests.cs index 7acda3f..def9c5a 100644 --- a/Roadie.Api.Library.Tests/ImageHelperTests.cs +++ b/Roadie.Api.Library.Tests/ImageHelperTests.cs @@ -312,8 +312,6 @@ namespace Roadie.Library.Tests { #pragma warning disable CS0618 // Type or member is obsolete var now = DateTime.UtcNow; - var optionsBuilder = new DbContextOptionsBuilder(); - optionsBuilder.UseMySql("server=viking;userid=roadie;password=MenAtW0rk668;persistsecurityinfo=True;database=roadie_dev;ConvertZeroDateTime=true"); var settings = new RoadieSettings(); IConfigurationBuilder configurationBuilder = new ConfigurationBuilder(); @@ -322,7 +320,7 @@ namespace Roadie.Library.Tests configuration.GetSection("RoadieSettings").Bind(settings); settings.ConnectionString = configuration.GetConnectionString("RoadieDatabaseConnection"); - using (var context = new RoadieDbContext(optionsBuilder.Options)) + using (var context = DbContextFactory.Create(settings)) { foreach (var artist in context.Artists.Where(x => x.Thumbnail != null).OrderBy(x => x.SortName ?? x.Name)) { diff --git a/Roadie.Api.Library/Data/DbContextFactory.cs b/Roadie.Api.Library/Data/DbContextFactory.cs new file mode 100644 index 0000000..0db71a7 --- /dev/null +++ b/Roadie.Api.Library/Data/DbContextFactory.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore; +using Pomelo.EntityFrameworkCore.MySql.Infrastructure; +using Roadie.Library.Configuration; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Roadie.Library.Data +{ + public static class DbContextFactory + { + public static IRoadieDbContext Create(IRoadieSettings configuration) + { + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseMySql(configuration.ConnectionString, mySqlOptions => + { + mySqlOptions.ServerVersion(new Version(5, 5), ServerType.MariaDb); + mySqlOptions.EnableRetryOnFailure( + 10, + TimeSpan.FromSeconds(30), + null); + }); + return new MySQLRoadieDbContext(optionsBuilder.Options); + } + } +} diff --git a/Roadie.Api.Library/Data/IRoadieDbContext.cs b/Roadie.Api.Library/Data/IRoadieDbContext.cs index 2ce93a9..8d1c73a 100644 --- a/Roadie.Api.Library/Data/IRoadieDbContext.cs +++ b/Roadie.Api.Library/Data/IRoadieDbContext.cs @@ -10,7 +10,7 @@ using System.Threading.Tasks; namespace Roadie.Library.Data { - public interface IRoadieDbContext : IDisposable, IInfrastructure, IDbContextDependencies, IDbSetCache, IDbContextPoolable + public interface IRoadieDbContext : IRoadieDbRandomizer, IRoadieDbUserStats, IDisposable, IInfrastructure, IDbContextDependencies, IDbSetCache, IDbContextPoolable { DbSet ArtistAssociations { get; set; } DbSet ArtistGenres { get; set; } @@ -105,8 +105,7 @@ namespace Roadie.Library.Data int SaveChanges(); - Task SaveChangesAsync(bool acceptAllChangesOnSuccess, - CancellationToken cancellationToken = default(CancellationToken)); + Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken)); Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)); diff --git a/Roadie.Api.Library/Data/IRoadieDbRandomizer.cs b/Roadie.Api.Library/Data/IRoadieDbRandomizer.cs new file mode 100644 index 0000000..b262580 --- /dev/null +++ b/Roadie.Api.Library/Data/IRoadieDbRandomizer.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace Roadie.Library.Data +{ + public interface IRoadieDbRandomizer + { + SortedDictionary RandomArtistIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false); + + SortedDictionary RandomGenreIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false); + + SortedDictionary RandomLabelIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false); + + SortedDictionary RandomReleaseIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false); + + SortedDictionary RandomTrackIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false); + } +} \ No newline at end of file diff --git a/Roadie.Api.Library/Data/IRoadieDbUserStats.cs b/Roadie.Api.Library/Data/IRoadieDbUserStats.cs new file mode 100644 index 0000000..6efb380 --- /dev/null +++ b/Roadie.Api.Library/Data/IRoadieDbUserStats.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; + +namespace Roadie.Library.Data +{ + public interface IRoadieDbUserStats + { + Task MostPlayedArtist(int userId); + + Task MostPlayedRelease(int userId); + + Task MostPlayedTrack(int userId); + + Task LastPlayedArtist(int userId); + + Task LastPlayedRelease(int userId); + + Task LastPlayedTrack(int userId); + } +} \ No newline at end of file diff --git a/Roadie.Api.Library/Data/MysqlRoadieDbContext.cs b/Roadie.Api.Library/Data/MysqlRoadieDbContext.cs new file mode 100644 index 0000000..fb0f3e5 --- /dev/null +++ b/Roadie.Api.Library/Data/MysqlRoadieDbContext.cs @@ -0,0 +1,197 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Roadie.Library.Data +{ + /// + /// MySQL/MariaDB implementation of DbContext + /// + public sealed class MySQLRoadieDbContext : RoadieDbContext + { + public MySQLRoadieDbContext(DbContextOptions options) + : base(options) + { + } + + public override async Task LastPlayedArtist(int userId) + { + var sql = @"SELECT a.* + FROM `usertrack` ut + join `track` t on (ut.trackId = t.id) + join `releasemedia` rm on (t.releaseMediaId = rm.id) + join `release` r on (rm.releaseId = r.id) + join `artist` a on (r.artistId = a.id) + where ut.userId = {0} + ORDER by ut.lastPlayed desc + LIMIT 1"; + return await Artists.FromSqlRaw(sql, userId).FirstOrDefaultAsync(); + } + + public override async Task LastPlayedRelease(int userId) + { + var sql = @"SELECT r.* + FROM `usertrack` ut + join `track` t on (ut.trackId = t.id) + join `releasemedia` rm on (t.releaseMediaId = rm.id) + join `release` r on (rm.releaseId = r.id) + WHERE ut.userId = {0} + ORDER by ut.lastPlayed desc + LIMIT 1"; + return await Releases.FromSqlRaw(sql, userId) + .Include(x => x.Artist) + .FirstOrDefaultAsync(); + } + + public override async Task LastPlayedTrack(int userId) + { + var sql = @"SELECT t.* + FROM `usertrack` ut + join `track` t on (ut.trackId = t.id) + WHERE ut.userId = {0} + ORDER by ut.lastPlayed desc + LIMIT 1"; + return await Tracks.FromSqlRaw(sql, userId) + .Include(x => x.TrackArtist) + .Include(x => x.ReleaseMedia) + .Include("ReleaseMedia.Release") + .Include("ReleaseMedia.Release.Artist") + .FirstOrDefaultAsync(); + } + + public override async Task MostPlayedArtist(int userId) + { + var sql = @"SELECT a.* + FROM `usertrack` ut + join `track` t on (ut.trackId = t.id) + join `releasemedia` rm on (t.releaseMediaId = rm.id) + join `release` r on (rm.releaseId = r.id) + join `artist` a on (r.artistId = a.id) + where ut.userId = {0} + group by r.id + order by SUM(ut.playedCount) desc + LIMIT 1"; + return await Artists.FromSqlRaw(sql, userId).FirstOrDefaultAsync(); + } + + public override async Task MostPlayedRelease(int userId) + { + var sql = @"SELECT r.* + FROM `usertrack` ut + join `track` t on (ut.trackId = t.id) + join `releasemedia` rm on (t.releaseMediaId = rm.id) + join `release` r on (rm.releaseId = r.id) + WHERE ut.userId = {0} + GROUP by r.id + ORDER by SUM(ut.playedCount) desc + LIMIT 1"; + return await Releases.FromSqlRaw(sql, userId) + .Include(x => x.Artist) + .FirstOrDefaultAsync(); + } + + public override async Task MostPlayedTrack(int userId) + { + var sql = @"SELECT t.* + FROM `usertrack` ut + join `track` t on (ut.trackId = t.id) + WHERE ut.userId = {0} + GROUP by t.id + ORDER by SUM(ut.playedCount) desc + LIMIT 1"; + return await Tracks.FromSqlRaw(sql, userId) + .Include(x => x.TrackArtist) + .Include(x => x.ReleaseMedia) + .Include("ReleaseMedia.Release") + .Include("ReleaseMedia.Release.Artist") + .FirstOrDefaultAsync(); + } + + public override SortedDictionary RandomArtistIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false) + { + var sql = @"SELECT a.id + FROM `artist` a + WHERE(a.id NOT IN(select artistId FROM `userartist` where userId = {1} and isDisliked = 1)) + OR(a.id IN(select artistId FROM `userartist` where userId = {1} and isFavorite = 1) + AND {2} = 1) + order BY RIGHT(HEX((1 << 24) * (1 + RAND())), 6) + LIMIT 0, {0}"; + var ids = Artists.FromSqlRaw(sql, randomLimit, userId, doOnlyFavorites ? "1" : "0").Select(x => x.Id).ToList(); + var dict = ids.Select((id, i) => new { key = i, value = id }).ToDictionary(x => x.key, x => x.value); + return new SortedDictionary(dict); + } + + public override SortedDictionary RandomGenreIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false) + { + var sql = @"SELECT g.id + FROM `genre` g + ORDER BY RIGHT( HEX( (1<<24) * (1+RAND()) ), 6) + LIMIT 0, {0}"; + var ids = Genres.FromSqlRaw(sql, randomLimit).Select(x => x.Id).ToList(); + var dict = ids.Select((id, i) => new { key = i, value = id }).ToDictionary(x => x.key, x => x.value); + return new SortedDictionary(dict); + } + + public override SortedDictionary RandomLabelIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false) + { + var sql = @"SELECT l.id + FROM `label` l + ORDER BY RIGHT( HEX( (1<<24) * (1+RAND()) ), 6) + LIMIT 0, {0}"; + var ids = Labels.FromSqlRaw(sql, randomLimit).Select(x => x.Id).ToList(); + var dict = ids.Select((id, i) => new { key = i, value = id }).ToDictionary(x => x.key, x => x.value); + return new SortedDictionary(dict); + } + + public override SortedDictionary RandomReleaseIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false) + { + var sql = @"SELECT r.id + FROM `release` r + WHERE (r.id NOT IN (select releaseId FROM `userrelease` where userId = {1} and isDisliked = 1)) + OR (r.id IN (select releaseId FROM `userrelease` where userId = {1} and isFavorite = 1) + AND {2} = 1) + ORDER BY RIGHT( HEX( (1<<24) * (1+RAND()) ), 6) + LIMIT 0, {0}"; + var ids = Releases.FromSqlRaw(sql, randomLimit, userId, doOnlyFavorites ? "1" : "0").Select(x => x.Id).ToList(); + var dict = ids.Select((id, i) => new { key = i, value = id }).ToDictionary(x => x.key, x => x.value); + return new SortedDictionary(dict); + } + + public override SortedDictionary RandomTrackIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false) + { + var sql = @"SELECT t.id + FROM `track` t + # Rated filter + WHERE ((t.rating > 0 AND {3} = 1) OR {3} = 0) + # Artist is not disliked + AND ((t.id NOT IN (select tt.id + FROM `track` tt + JOIN `releasemedia` rm on (tt.releaseMediaId = rm.id) + JOIN `userartist` ua on (rm.id = ua.artistId) + WHERE ua.userId = {1} AND ua.isDisliked = 1)) + # Release is not disliked + AND (t.id NOT IN (select tt.id + FROM `track` tt + JOIN `releasemedia` rm on (tt.releaseMediaId = rm.id) + JOIN `userrelease` ur on (rm.releaseId = ur.releaseId) + WHERE ur.userId = {1} AND ur.isDisliked = 1)) + # Track is not disliked + AND (t.id NOT IN (select tt.id + FROM `track` tt + JOIN `usertrack` ut on (tt.id = ut.trackId) + WHERE ut.userId = {1} AND ut.isDisliked = 1))) + # If toggled then only favorites + AND ((t.id IN (select tt.id + FROM `track` tt + JOIN `usertrack` ut on (tt.id = ut.trackId) + WHERE ut.userId = {1} AND ut.isFavorite = 1) AND {2} = 1) OR {2} = 0) + order BY RIGHT( HEX( (1<<24) * (1+RAND()) ), 6) + LIMIT 0, {0}"; + var ids = Releases.FromSqlRaw(sql, randomLimit, userId, doOnlyFavorites ? "1" : "0", doOnlyRated ? "1" : "0").Select(x => x.Id).ToList(); + var dict = ids.Select((id, i) => new { key = i, value = id }).ToDictionary(x => x.key, x => x.value); + return new SortedDictionary(dict); + } + } +} \ No newline at end of file diff --git a/Roadie.Api.Library/Data/RoadieDbContext.cs b/Roadie.Api.Library/Data/RoadieDbContext.cs index cfaa1fb..e7fc100 100644 --- a/Roadie.Api.Library/Data/RoadieDbContext.cs +++ b/Roadie.Api.Library/Data/RoadieDbContext.cs @@ -3,12 +3,13 @@ using Microsoft.EntityFrameworkCore.ChangeTracking; using Roadie.Library.Enums; using Roadie.Library.Identity; using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace Roadie.Library.Data { - public class RoadieDbContext : DbContext, IRoadieDbContext + public abstract partial class RoadieDbContext : DbContext, IRoadieDbContext { public DbSet ArtistAssociations { get; set; } @@ -71,8 +72,8 @@ namespace Roadie.Library.Data public DbSet UserTracks { get; set; } public DbSet InviteTokens { get; set; } - public RoadieDbContext(DbContextOptions options) - : base(options) + public RoadieDbContext(DbContextOptions options) + : base(options) { } diff --git a/Roadie.Api.Library/Data/RoadieDbContextPartial.cs b/Roadie.Api.Library/Data/RoadieDbContextPartial.cs new file mode 100644 index 0000000..0f2ab84 --- /dev/null +++ b/Roadie.Api.Library/Data/RoadieDbContextPartial.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Roadie.Library.Data +{ + public abstract partial class RoadieDbContext : DbContext, IRoadieDbContext + { + public abstract SortedDictionary RandomArtistIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false); + + public abstract SortedDictionary RandomGenreIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false); + + public abstract SortedDictionary RandomLabelIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false); + + public abstract SortedDictionary RandomReleaseIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false); + + public abstract SortedDictionary RandomTrackIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false); + + public abstract Task MostPlayedArtist(int userId); + + public abstract Task MostPlayedRelease(int userId); + + public abstract Task MostPlayedTrack(int userId); + + public abstract Task LastPlayedTrack(int userId); + + public abstract Task LastPlayedArtist(int userId); + + public abstract Task LastPlayedRelease(int userId); + } +} \ No newline at end of file diff --git a/Roadie.Api.Library/Roadie.Library.csproj b/Roadie.Api.Library/Roadie.Library.csproj index 9681da8..3db8e8c 100644 --- a/Roadie.Api.Library/Roadie.Library.csproj +++ b/Roadie.Api.Library/Roadie.Library.csproj @@ -10,7 +10,7 @@ - + diff --git a/Roadie.Api.Services/ArtistService.cs b/Roadie.Api.Services/ArtistService.cs index ddfa0c8..6752dfb 100644 --- a/Roadie.Api.Services/ArtistService.cs +++ b/Roadie.Api.Services/ArtistService.cs @@ -247,21 +247,12 @@ namespace Roadie.Api.Services : null; int[] randomArtistIds = null; + SortedDictionary randomArtistData = null; if (doRandomize ?? false) { var randomLimit = request.Limit ?? roadieUser?.RandomReleaseLimit ?? request.LimitValue; - var userId = roadieUser?.Id ?? -1; - - //// This is MySQL specific but I can't figure out how else to get random without throwing EF local evaluate warnings. - var sql = @"select a.id - FROM `artist` a - WHERE(a.id NOT IN(select artistId FROM `userartist` where userId = {1} and isDisliked = 1)) - OR(a.id IN(select artistId FROM `userartist` where userId = {1} and isFavorite = 1) - AND {2} = 1) - order BY RIGHT(HEX((1 << 24) * (1 + RAND())), 6) - LIMIT 0, {0}"; - randomArtistIds = (from a in DbContext.Artists.FromSqlRaw(sql, randomLimit, userId, request.FilterFavoriteOnly ? "1" : "0") - select a.Id).ToArray(); + randomArtistData = DbContext.RandomArtistIds(roadieUser?.Id ?? -1, randomLimit, request.FilterFavoriteOnly, request.FilterRatedOnly); + randomArtistIds = randomArtistData.Select(x => x.Value).ToArray(); rowCount = DbContext.Artists.Count(); } var result = (from a in DbContext.Artists @@ -311,7 +302,12 @@ namespace Roadie.Api.Services if (doRandomize ?? false) { - rows = result.ToArray(); + var resultData = result.ToArray(); + rows = (from r in resultData + join ra in randomArtistData on r.DatabaseId equals ra.Value + orderby ra.Key + select r + ).ToArray(); } else { diff --git a/Roadie.Api.Services/GenreService.cs b/Roadie.Api.Services/GenreService.cs index 3a712ac..88bc244 100644 --- a/Roadie.Api.Services/GenreService.cs +++ b/Roadie.Api.Services/GenreService.cs @@ -96,16 +96,12 @@ namespace Roadie.Api.Services } int[] randomGenreIds = null; + SortedDictionary randomGenreData = null; if (doRandomize ?? false) { var randomLimit = request.Limit ?? roadieUser?.RandomReleaseLimit ?? request.LimitValue; - // This is MySQL specific but I can't figure out how else to get random without throwing EF local evaluate warnings. - var sql = @"select g.id - FROM `genre` g - order BY RIGHT( HEX( (1<<24) * (1+RAND()) ), 6) - LIMIT 0, {0}"; - randomGenreIds = (from l in DbContext.Genres.FromSqlRaw(sql, randomLimit) - select l.Id).ToArray(); + randomGenreData = DbContext.RandomGenreIds(roadieUser?.Id ?? -1, randomLimit, request.FilterFavoriteOnly, request.FilterRatedOnly); + randomGenreIds = randomGenreData.Select(x => x.Value).ToArray(); rowCount = DbContext.Genres.Count(); } @@ -138,7 +134,12 @@ namespace Roadie.Api.Services rowCount = rowCount ?? result.Count(); if (doRandomize ?? false) { - rows = result.ToArray(); + var resultData = result.ToArray(); + rows = (from r in resultData + join ra in randomGenreData on r.DatabaseId equals ra.Value + orderby ra.Key + select r + ).ToArray(); } else { diff --git a/Roadie.Api.Services/LabelService.cs b/Roadie.Api.Services/LabelService.cs index 489bbe1..e2ed6a6 100644 --- a/Roadie.Api.Services/LabelService.cs +++ b/Roadie.Api.Services/LabelService.cs @@ -131,16 +131,12 @@ namespace Roadie.Api.Services : null; int[] randomLabelIds = null; + SortedDictionary randomLabelData = null; if (doRandomize ?? false) { var randomLimit = request.Limit ?? roadieUser?.RandomReleaseLimit ?? request.LimitValue; - // This is MySQL specific but I can't figure out how else to get random without throwing EF local evaluate warnings. - var sql = @"select l.id - FROM `label` l - order BY RIGHT( HEX( (1<<24) * (1+RAND()) ), 6) - LIMIT 0, {0}"; - randomLabelIds = (from l in DbContext.Labels.FromSqlRaw(sql, randomLimit) - select l.Id).ToArray(); + randomLabelData = DbContext.RandomLabelIds(roadieUser?.Id ?? -1, randomLimit, request.FilterFavoriteOnly, request.FilterRatedOnly); + randomLabelIds = randomLabelData.Select(x => x.Value).ToArray(); rowCount = DbContext.Labels.Count(); } @@ -173,7 +169,12 @@ namespace Roadie.Api.Services rowCount = rowCount ?? result.Count(); if (doRandomize ?? false) { - rows = result.ToArray(); + var resultData = result.ToArray(); + rows = (from r in resultData + join ra in randomLabelData on r.DatabaseId equals ra.Value + orderby ra.Key + select r + ).ToArray(); } else { diff --git a/Roadie.Api.Services/ReleaseService.cs b/Roadie.Api.Services/ReleaseService.cs index c4174e8..5ca77b5 100644 --- a/Roadie.Api.Services/ReleaseService.cs +++ b/Roadie.Api.Services/ReleaseService.cs @@ -481,21 +481,12 @@ namespace Roadie.Api.Services : null; int[] randomReleaseIds = null; + SortedDictionary randomReleaseData = null; if (doRandomize ?? false) { var randomLimit = request.Limit ?? roadieUser?.RandomReleaseLimit ?? request.LimitValue; - var userId = roadieUser?.Id ?? -1; - - // This is MySQL specific but I can't figure out how else to get random without throwing EF local evaluate warnings. - var sql = @"select r.id - FROM `release` r - WHERE (r.id NOT IN (select releaseId FROM `userrelease` where userId = {1} and isDisliked = 1)) - OR (r.id IN (select releaseId FROM `userrelease` where userId = {1} and isFavorite = 1) - AND {2} = 1) - order BY RIGHT( HEX( (1<<24) * (1+RAND()) ), 6) - LIMIT 0, {0}"; - randomReleaseIds = (from a in DbContext.Releases.FromSqlRaw(sql, randomLimit, userId, request.FilterFavoriteOnly ? "1" : "0") - select a.Id).ToArray(); + randomReleaseData = DbContext.RandomReleaseIds(roadieUser?.Id ?? -1, randomLimit, request.FilterFavoriteOnly, request.FilterRatedOnly); + randomReleaseIds = randomReleaseData.Select(x => x.Value).ToArray(); rowCount = DbContext.Releases.Count(); } @@ -556,7 +547,12 @@ namespace Roadie.Api.Services if (doRandomize ?? false) { - rows = result.ToArray(); + var resultData = result.ToArray(); + rows = (from r in resultData + join ra in randomReleaseData on r.DatabaseId equals ra.Value + orderby ra.Key + select r + ).ToArray(); } else { diff --git a/Roadie.Api.Services/TrackService.cs b/Roadie.Api.Services/TrackService.cs index ac7c780..89efc21 100644 --- a/Roadie.Api.Services/TrackService.cs +++ b/Roadie.Api.Services/TrackService.cs @@ -284,45 +284,13 @@ namespace Roadie.Api.Services } int[] randomTrackIds = null; - + SortedDictionary randomTrackData = null; if (doRandomize ?? false) { - var randomLimit = roadieUser?.RandomReleaseLimit ?? request.LimitValue; - var userId = roadieUser?.Id ?? -1; - - // This is MySQL specific but I can't figure out how else to get random without throwing EF local evaluate warnings. - var sql = @"SELECT t.id - FROM `track` t - # Rated filter - WHERE ((t.rating > 0 AND {3} = 1) OR {3} = 0) - # Artist is not disliked - AND ((t.id NOT IN (select tt.id - FROM `track` tt - JOIN `releasemedia` rm on (tt.releaseMediaId = rm.id) - JOIN `userartist` ua on (rm.id = ua.artistId) - WHERE ua.userId = {1} AND ua.isDisliked = 1)) - # Release is not disliked - AND (t.id NOT IN (select tt.id - FROM `track` tt - JOIN `releasemedia` rm on (tt.releaseMediaId = rm.id) - JOIN `userrelease` ur on (rm.releaseId = ur.releaseId) - WHERE ur.userId = {1} AND ur.isDisliked = 1)) - # Track is not disliked - AND (t.id NOT IN (select tt.id - FROM `track` tt - JOIN `usertrack` ut on (tt.id = ut.trackId) - WHERE ut.userId = {1} AND ut.isDisliked = 1))) - # If toggled then only favorites - AND ((t.id IN (select tt.id - FROM `track` tt - JOIN `usertrack` ut on (tt.id = ut.trackId) - WHERE ut.userId = {1} AND ut.isFavorite = 1) AND {2} = 1) OR {2} = 0) - order BY RIGHT( HEX( (1<<24) * (1+RAND()) ), 6) - LIMIT 0, {0};"; - randomTrackIds = (from a in DbContext.Releases.FromSqlRaw(sql, randomLimit, userId, request.FilterFavoriteOnly ? "1" : "0", request.FilterRatedOnly ? "1" : "0") - select a.Id).ToArray(); + var randomLimit = request.Limit ?? roadieUser?.RandomReleaseLimit ?? request.LimitValue; + randomTrackData = DbContext.RandomTrackIds(roadieUser?.Id ?? -1, randomLimit, request.FilterFavoriteOnly, request.FilterRatedOnly); + randomTrackIds = randomTrackData.Select(x => x.Value).ToArray(); rowCount = DbContext.Releases.Count(); - } Guid?[] filterToTrackIds = null; @@ -518,30 +486,38 @@ namespace Roadie.Api.Services rowCount = rowCount ?? result.Count(); TrackList[] rows = null; - if (request.Action == User.ActionKeyUserRated) + if (!doRandomize ?? false) { - sortBy = string.IsNullOrEmpty(request.Sort) - ? request.OrderValue(new Dictionary { { "UserTrack.Rating", "DESC" }, { "MediaNumber", "ASC" }, { "TrackNumber", "ASC" } }) - : request.OrderValue(); - } - else - { - if (request.Sort == "Rating") + if (request.Action == User.ActionKeyUserRated) { - // The request is to sort tracks by Rating if the artist only has a few tracks rated then order by those then order by played (put most popular after top rated) - sortBy = request.OrderValue(new Dictionary { { "Rating", request.Order }, { "PlayedCount", request.Order } }); + sortBy = string.IsNullOrEmpty(request.Sort) + ? request.OrderValue(new Dictionary { { "UserTrack.Rating", "DESC" }, { "MediaNumber", "ASC" }, { "TrackNumber", "ASC" } }) + : request.OrderValue(); } else { - sortBy = string.IsNullOrEmpty(request.Sort) - ? request.OrderValue(new Dictionary { { "Release.Release.Text", "ASC" }, { "MediaNumber", "ASC" }, { "TrackNumber", "ASC" } }) - : request.OrderValue(); + if (request.Sort == "Rating") + { + // The request is to sort tracks by Rating if the artist only has a few tracks rated then order by those then order by played (put most popular after top rated) + sortBy = request.OrderValue(new Dictionary { { "Rating", request.Order }, { "PlayedCount", request.Order } }); + } + else + { + sortBy = string.IsNullOrEmpty(request.Sort) + ? request.OrderValue(new Dictionary { { "Release.Release.Text", "ASC" }, { "MediaNumber", "ASC" }, { "TrackNumber", "ASC" } }) + : request.OrderValue(); + } } } if (doRandomize ?? false) { - rows = TrackList.Shuffle(result).ToArray(); + var resultData = result.ToArray(); + rows = (from r in resultData + join ra in randomTrackData on r.DatabaseId equals ra.Value + orderby ra.Key + select r + ).ToArray(); } else { diff --git a/Roadie.Api.Services/UserService.cs b/Roadie.Api.Services/UserService.cs index 5836c3e..0f6cbc7 100644 --- a/Roadie.Api.Services/UserService.cs +++ b/Roadie.Api.Services/UserService.cs @@ -640,52 +640,10 @@ namespace Roadie.Api.Services var userReleases = DbContext.UserReleases.Include(x => x.Release).Where(x => x.UserId == user.Id).ToArray() ?? new data.UserRelease[0]; var userTracks = DbContext.UserTracks.Include(x => x.Track).Where(x => x.UserId == user.Id).ToArray() ?? new data.UserTrack[0]; - // This is MySQL specific - var sql = @"select a.* - FROM `usertrack` ut - join `track` t on (ut.trackId = t.id) - join `releasemedia` rm on (t.releaseMediaId = rm.id) - join `release` r on (rm.releaseId = r.id) - join `artist` a on (r.artistId = a.id) - where ut.userId = {0} - group by r.id - order by SUM(ut.playedCount) desc - LIMIT 1"; - var mostPlayedArtist = await DbContext.Artists.FromSqlRaw(sql, user.Id).FirstOrDefaultAsync(); - - // This is MySQL specific - sql = @"SELECT r.* - FROM `usertrack` ut - join `track` t on (ut.trackId = t.id) - join `releasemedia` rm on (t.releaseMediaId = rm.id) - join `release` r on (rm.releaseId = r.id) - WHERE ut.userId = {0} - GROUP by r.id - ORDER by SUM(ut.playedCount) desc - LIMIT 1"; - var mostPlayedRelease = await DbContext.Releases.FromSqlRaw(sql, user.Id).FirstOrDefaultAsync(); - var mostPlayedTrackUserTrack = userTracks.OrderByDescending(x => x.PlayedCount) - .FirstOrDefault(); - var lastPlayedTrackUserTrack = userTracks.OrderByDescending(x => x.LastPlayed) - .FirstOrDefault(); - - var lastPlayedTrack = lastPlayedTrackUserTrack == null - ? null - : DbContext.Tracks - .Include(x => x.TrackArtist) - .Include(x => x.ReleaseMedia) - .Include("ReleaseMedia.Release") - .Include("ReleaseMedia.Release.Artist") - .FirstOrDefault(x => x.Id == lastPlayedTrackUserTrack.TrackId); - var mostPlayedTrack = mostPlayedTrackUserTrack == null - ? null - : DbContext.Tracks - .Include(x => x.TrackArtist) - .Include(x => x.ReleaseMedia) - .Include("ReleaseMedia.Release") - .Include("ReleaseMedia.Release.Artist") - .FirstOrDefault(x => x.Id == mostPlayedTrackUserTrack.TrackId); - + var mostPlayedArtist = await DbContext.MostPlayedArtist(user.Id); + var mostPlayedRelease = await DbContext.MostPlayedRelease(user.Id); + var lastPlayedTrack = await DbContext.MostPlayedTrack(user.Id); + var mostPlayedTrack = await DbContext.LastPlayedTrack(user.Id); model.Statistics = new UserStatistics { LastPlayedTrack = lastPlayedTrack == null diff --git a/Roadie.Api/Roadie.Api.csproj b/Roadie.Api/Roadie.Api.csproj index c0e225a..4ec0757 100644 --- a/Roadie.Api/Roadie.Api.csproj +++ b/Roadie.Api/Roadie.Api.csproj @@ -33,7 +33,7 @@ - + diff --git a/Roadie.Api/Startup.cs b/Roadie.Api/Startup.cs index 8cbd716..19c6058 100644 --- a/Roadie.Api/Startup.cs +++ b/Roadie.Api/Startup.cs @@ -127,7 +127,7 @@ namespace Roadie.Api } )); - services.AddDbContextPool( + services.AddDbContextPool( options => options.UseMySql(_configuration.GetConnectionString("RoadieDatabaseConnection"), mySqlOptions => { @@ -173,8 +173,7 @@ namespace Roadie.Api settings.ConnectionString = _configuration.GetConnectionString("RoadieDatabaseConnection"); // This is so 'User Secrets' can be used in Debugging - var integrationKeys = _configuration.GetSection("IntegrationKeys") - .Get(); + var integrationKeys = _configuration.GetSection("IntegrationKeys").Get(); if (integrationKeys != null) settings.Integrations.ApiKeys = new List { diff --git a/Roadie.Dlna.Services/DlnaHostService.cs b/Roadie.Dlna.Services/DlnaHostService.cs index 0a61039..3e7dc3c 100644 --- a/Roadie.Dlna.Services/DlnaHostService.cs +++ b/Roadie.Dlna.Services/DlnaHostService.cs @@ -1,8 +1,6 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Pomelo.EntityFrameworkCore.MySql.Infrastructure; using Roadie.Api.Services; using Roadie.Dlna.Server; using Roadie.Library.Caching; @@ -58,18 +56,7 @@ namespace Roadie.Dlna.Services { _authorizer.AddMethod(new UserAgentAuthorizer(Configuration.Dlna.AllowedUserAgents)); } - var types = new DlnaMediaTypes[] { DlnaMediaTypes.Image, DlnaMediaTypes.Audio }; - var optionsBuilder = new DbContextOptionsBuilder(); - optionsBuilder.UseMySql(Configuration.ConnectionString, mySqlOptions => - { - mySqlOptions.ServerVersion(new Version(5, 5), ServerType.MariaDb); - mySqlOptions.EnableRetryOnFailure( - 10, - TimeSpan.FromSeconds(30), - null); - }); - - DbContext = new data.RoadieDbContext(optionsBuilder.Options); + DbContext = data.DbContextFactory.Create(Configuration); var defaultNotFoundImages = new DefaultNotFoundImages(LoggerFactory.CreateLogger("DefaultNotFoundImages"), Configuration); var imageService = new ImageService(Configuration, DbContext, CacheManager, LoggerFactory.CreateLogger("ImageService"), defaultNotFoundImages); diff --git a/Roadie.sln b/Roadie.sln index 6a14477..e3d5e8a 100644 --- a/Roadie.sln +++ b/Roadie.sln @@ -9,19 +9,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roadie.Library", "Roadie.Ap EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roadie.Library.Tests", "Roadie.Api.Library.Tests\Roadie.Library.Tests.csproj", "{52E58F4B-88F0-4336-AD06-1184E857FA2C}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{1BA7115B-6E37-4546-BBD6-C8B0787A3FE0}" - ProjectSection(SolutionItems) = preProject - roadie.sql = roadie.sql - Upgrade0001.sql = Upgrade0001.sql - Upgrade0002.sql = Upgrade0002.sql - Upgrade0003.sql = Upgrade0003.sql - Upgrade0004.sql = Upgrade0004.sql - Upgrade0005.sql = Upgrade0005.sql - Upgrade0006.sql = Upgrade0006.sql - Upgrade0007.sql = Upgrade0007.sql - Upgrade0008.sql = Upgrade0008.sql - EndProjectSection -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roadie.Api.Services", "Roadie.Api.Services\Roadie.Api.Services.csproj", "{7B37031E-F2AE-4BE2-9F6F-005CA7A6FDF1}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roadie.Api.Hubs", "Roadie.Api.Hubs\Roadie.Api.Hubs.csproj", "{E740C89E-3363-4577-873B-0871823E252C}" diff --git a/Upgrade0001.sql b/Scripts/MySQL/Upgrade0001.sql similarity index 100% rename from Upgrade0001.sql rename to Scripts/MySQL/Upgrade0001.sql diff --git a/Upgrade0002.sql b/Scripts/MySQL/Upgrade0002.sql similarity index 100% rename from Upgrade0002.sql rename to Scripts/MySQL/Upgrade0002.sql diff --git a/Upgrade0003.sql b/Scripts/MySQL/Upgrade0003.sql similarity index 100% rename from Upgrade0003.sql rename to Scripts/MySQL/Upgrade0003.sql diff --git a/Upgrade0004.sql b/Scripts/MySQL/Upgrade0004.sql similarity index 100% rename from Upgrade0004.sql rename to Scripts/MySQL/Upgrade0004.sql diff --git a/Upgrade0005.sql b/Scripts/MySQL/Upgrade0005.sql similarity index 100% rename from Upgrade0005.sql rename to Scripts/MySQL/Upgrade0005.sql diff --git a/Upgrade0006.sql b/Scripts/MySQL/Upgrade0006.sql similarity index 100% rename from Upgrade0006.sql rename to Scripts/MySQL/Upgrade0006.sql diff --git a/Upgrade0007.sql b/Scripts/MySQL/Upgrade0007.sql similarity index 100% rename from Upgrade0007.sql rename to Scripts/MySQL/Upgrade0007.sql diff --git a/Upgrade0008.sql b/Scripts/MySQL/Upgrade0008.sql similarity index 100% rename from Upgrade0008.sql rename to Scripts/MySQL/Upgrade0008.sql diff --git a/roadie.sql b/Scripts/MySQL/roadie.sql similarity index 100% rename from roadie.sql rename to Scripts/MySQL/roadie.sql