This commit is contained in:
Steven Hildreth 2019-11-16 18:08:44 -06:00
parent 1d4e051ff1
commit 97300534a4
30 changed files with 370 additions and 349 deletions

View file

@ -21,7 +21,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.4.3" /> <PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.4.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.0" />
</ItemGroup> </ItemGroup>

View file

@ -55,173 +55,5 @@ namespace Roadie.Library.Tests
{ {
Console.WriteLine($"Log Level [{ e.Level }] Log Message [{ e.Message }] "); Console.WriteLine($"Log Level [{ e.Level }] Log Message [{ e.Message }] ");
} }
//[Fact]
//public void Update_Genre_Normalized_Name()
//{
// var optionsBuilder = new DbContextOptionsBuilder<RoadieDbContext>();
// 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<RoadieDbContext>();
// 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<KeyValuePair<int, int>>();
// var addedReleaseToGenre = new List<KeyValuePair<int, int>>();
// 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<int, int>(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<int, int>(releaseIdsInDup, genre.Id));
// }
// }
// }
// context.Genres.Remove(gg);
// context.SaveChanges();
// }
// }
// }
//}
//[Fact]
//public void Update_Releases_Special_Name()
//{
// var optionsBuilder = new DbContextOptionsBuilder<RoadieDbContext>();
// 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<Roadie.Library.Models.Releases.Release>();
// var specialReleaseTitle = release.Title.ToAlphanumericName();
// if (!releaseModel.AlternateNamesList.Contains(specialReleaseTitle, StringComparer.OrdinalIgnoreCase))
// {
// var alt = new List<string>(releaseModel.AlternateNamesList)
// {
// specialReleaseTitle
// };
// release.AlternateNames = alt.ToDelimitedList();
// release.LastUpdated = now;
// }
// }
// context.SaveChanges();
// }
//}
//[Fact]
//public void Update_Artist_Special_Name()
//{
// var optionsBuilder = new DbContextOptionsBuilder<RoadieDbContext>();
// 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<Roadie.Library.Models.Artist>();
// var specialArtistName = artist.Name.ToAlphanumericName();
// if (!artistModel.AlternateNamesList.Contains(specialArtistName, StringComparer.OrdinalIgnoreCase))
// {
// var alt = new List<string>(artistModel.AlternateNamesList)
// {
// specialArtistName
// };
// artist.AlternateNames = alt.ToDelimitedList();
// artist.LastUpdated = now;
// }
// }
// context.SaveChanges();
// }
//}
//[Fact]
//public void Update_Label_Special_Name()
//{
// var optionsBuilder = new DbContextOptionsBuilder<RoadieDbContext>();
// 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<Roadie.Library.Models.Label>();
// var specialLabelName = labelModel.Name.ToAlphanumericName();
// if (!labelModel.AlternateNamesList.Contains(specialLabelName, StringComparer.OrdinalIgnoreCase))
// {
// var alt = new List<string>(labelModel.AlternateNamesList)
// {
// specialLabelName
// };
// label.AlternateNames = alt.ToDelimitedList();
// label.LastUpdated = now;
// }
// }
// context.SaveChanges();
// }
//}
} }
} }

View file

@ -312,8 +312,6 @@ namespace Roadie.Library.Tests
{ {
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var optionsBuilder = new DbContextOptionsBuilder<RoadieDbContext>();
optionsBuilder.UseMySql("server=viking;userid=roadie;password=MenAtW0rk668;persistsecurityinfo=True;database=roadie_dev;ConvertZeroDateTime=true");
var settings = new RoadieSettings(); var settings = new RoadieSettings();
IConfigurationBuilder configurationBuilder = new ConfigurationBuilder(); IConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
@ -322,7 +320,7 @@ namespace Roadie.Library.Tests
configuration.GetSection("RoadieSettings").Bind(settings); configuration.GetSection("RoadieSettings").Bind(settings);
settings.ConnectionString = configuration.GetConnectionString("RoadieDatabaseConnection"); 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)) foreach (var artist in context.Artists.Where(x => x.Thumbnail != null).OrderBy(x => x.SortName ?? x.Name))
{ {

View file

@ -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<MySQLRoadieDbContext>();
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);
}
}
}

View file

@ -10,7 +10,7 @@ using System.Threading.Tasks;
namespace Roadie.Library.Data namespace Roadie.Library.Data
{ {
public interface IRoadieDbContext : IDisposable, IInfrastructure<IServiceProvider>, IDbContextDependencies, IDbSetCache, IDbContextPoolable public interface IRoadieDbContext : IRoadieDbRandomizer, IRoadieDbUserStats, IDisposable, IInfrastructure<IServiceProvider>, IDbContextDependencies, IDbSetCache, IDbContextPoolable
{ {
DbSet<ArtistAssociation> ArtistAssociations { get; set; } DbSet<ArtistAssociation> ArtistAssociations { get; set; }
DbSet<ArtistGenre> ArtistGenres { get; set; } DbSet<ArtistGenre> ArtistGenres { get; set; }
@ -105,8 +105,7 @@ namespace Roadie.Library.Data
int SaveChanges(); int SaveChanges();
Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken));
CancellationToken cancellationToken = default(CancellationToken));
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)); Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken));

View file

@ -0,0 +1,17 @@
using System.Collections.Generic;
namespace Roadie.Library.Data
{
public interface IRoadieDbRandomizer
{
SortedDictionary<int, int> RandomArtistIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false);
SortedDictionary<int, int> RandomGenreIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false);
SortedDictionary<int, int> RandomLabelIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false);
SortedDictionary<int, int> RandomReleaseIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false);
SortedDictionary<int, int> RandomTrackIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false);
}
}

View file

@ -0,0 +1,19 @@
using System.Threading.Tasks;
namespace Roadie.Library.Data
{
public interface IRoadieDbUserStats
{
Task<Artist> MostPlayedArtist(int userId);
Task<Release> MostPlayedRelease(int userId);
Task<Track> MostPlayedTrack(int userId);
Task<Artist> LastPlayedArtist(int userId);
Task<Release> LastPlayedRelease(int userId);
Task<Track> LastPlayedTrack(int userId);
}
}

View file

@ -0,0 +1,197 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Roadie.Library.Data
{
/// <summary>
/// MySQL/MariaDB implementation of DbContext
/// </summary>
public sealed class MySQLRoadieDbContext : RoadieDbContext
{
public MySQLRoadieDbContext(DbContextOptions options)
: base(options)
{
}
public override async Task<Artist> 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<Release> 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<Track> 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<Artist> 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<Release> 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<Track> 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<int, int> 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<int, int>(dict);
}
public override SortedDictionary<int, int> 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<int, int>(dict);
}
public override SortedDictionary<int, int> 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<int, int>(dict);
}
public override SortedDictionary<int, int> 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<int, int>(dict);
}
public override SortedDictionary<int, int> 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<int, int>(dict);
}
}
}

View file

@ -3,12 +3,13 @@ using Microsoft.EntityFrameworkCore.ChangeTracking;
using Roadie.Library.Enums; using Roadie.Library.Enums;
using Roadie.Library.Identity; using Roadie.Library.Identity;
using System; using System;
using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Roadie.Library.Data namespace Roadie.Library.Data
{ {
public class RoadieDbContext : DbContext, IRoadieDbContext public abstract partial class RoadieDbContext : DbContext, IRoadieDbContext
{ {
public DbSet<ArtistAssociation> ArtistAssociations { get; set; } public DbSet<ArtistAssociation> ArtistAssociations { get; set; }
@ -71,8 +72,8 @@ namespace Roadie.Library.Data
public DbSet<UserTrack> UserTracks { get; set; } public DbSet<UserTrack> UserTracks { get; set; }
public DbSet<InviteToken> InviteTokens { get; set; } public DbSet<InviteToken> InviteTokens { get; set; }
public RoadieDbContext(DbContextOptions<RoadieDbContext> options) public RoadieDbContext(DbContextOptions options)
: base(options) : base(options)
{ {
} }

View file

@ -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<int, int> RandomArtistIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false);
public abstract SortedDictionary<int, int> RandomGenreIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false);
public abstract SortedDictionary<int, int> RandomLabelIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false);
public abstract SortedDictionary<int, int> RandomReleaseIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false);
public abstract SortedDictionary<int, int> RandomTrackIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false);
public abstract Task<Artist> MostPlayedArtist(int userId);
public abstract Task<Release> MostPlayedRelease(int userId);
public abstract Task<Track> MostPlayedTrack(int userId);
public abstract Task<Track> LastPlayedTrack(int userId);
public abstract Task<Artist> LastPlayedArtist(int userId);
public abstract Task<Release> LastPlayedRelease(int userId);
}
}

View file

@ -10,7 +10,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AutoCompare.Core" Version="1.0.0" /> <PackageReference Include="AutoCompare.Core" Version="1.0.0" />
<PackageReference Include="CsvHelper" Version="12.2.1" /> <PackageReference Include="CsvHelper" Version="12.2.1" />
<PackageReference Include="EFCore.BulkExtensions" Version="3.0.0" /> <PackageReference Include="EFCore.BulkExtensions" Version="3.0.2" />
<PackageReference Include="FluentFTP" Version="28.0.1" /> <PackageReference Include="FluentFTP" Version="28.0.1" />
<PackageReference Include="Hashids.net" Version="1.3.0" /> <PackageReference Include="Hashids.net" Version="1.3.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.16" /> <PackageReference Include="HtmlAgilityPack" Version="1.11.16" />

View file

@ -247,21 +247,12 @@ namespace Roadie.Api.Services
: null; : null;
int[] randomArtistIds = null; int[] randomArtistIds = null;
SortedDictionary<int, int> randomArtistData = null;
if (doRandomize ?? false) if (doRandomize ?? false)
{ {
var randomLimit = request.Limit ?? roadieUser?.RandomReleaseLimit ?? request.LimitValue; var randomLimit = request.Limit ?? roadieUser?.RandomReleaseLimit ?? request.LimitValue;
var userId = roadieUser?.Id ?? -1; randomArtistData = DbContext.RandomArtistIds(roadieUser?.Id ?? -1, randomLimit, request.FilterFavoriteOnly, request.FilterRatedOnly);
randomArtistIds = randomArtistData.Select(x => x.Value).ToArray();
//// 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();
rowCount = DbContext.Artists.Count(); rowCount = DbContext.Artists.Count();
} }
var result = (from a in DbContext.Artists var result = (from a in DbContext.Artists
@ -311,7 +302,12 @@ namespace Roadie.Api.Services
if (doRandomize ?? false) 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 else
{ {

View file

@ -96,16 +96,12 @@ namespace Roadie.Api.Services
} }
int[] randomGenreIds = null; int[] randomGenreIds = null;
SortedDictionary<int, int> randomGenreData = null;
if (doRandomize ?? false) if (doRandomize ?? false)
{ {
var randomLimit = request.Limit ?? roadieUser?.RandomReleaseLimit ?? request.LimitValue; 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. randomGenreData = DbContext.RandomGenreIds(roadieUser?.Id ?? -1, randomLimit, request.FilterFavoriteOnly, request.FilterRatedOnly);
var sql = @"select g.id randomGenreIds = randomGenreData.Select(x => x.Value).ToArray();
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();
rowCount = DbContext.Genres.Count(); rowCount = DbContext.Genres.Count();
} }
@ -138,7 +134,12 @@ namespace Roadie.Api.Services
rowCount = rowCount ?? result.Count(); rowCount = rowCount ?? result.Count();
if (doRandomize ?? false) 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 else
{ {

View file

@ -131,16 +131,12 @@ namespace Roadie.Api.Services
: null; : null;
int[] randomLabelIds = null; int[] randomLabelIds = null;
SortedDictionary<int, int> randomLabelData = null;
if (doRandomize ?? false) if (doRandomize ?? false)
{ {
var randomLimit = request.Limit ?? roadieUser?.RandomReleaseLimit ?? request.LimitValue; 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. randomLabelData = DbContext.RandomLabelIds(roadieUser?.Id ?? -1, randomLimit, request.FilterFavoriteOnly, request.FilterRatedOnly);
var sql = @"select l.id randomLabelIds = randomLabelData.Select(x => x.Value).ToArray();
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();
rowCount = DbContext.Labels.Count(); rowCount = DbContext.Labels.Count();
} }
@ -173,7 +169,12 @@ namespace Roadie.Api.Services
rowCount = rowCount ?? result.Count(); rowCount = rowCount ?? result.Count();
if (doRandomize ?? false) 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 else
{ {

View file

@ -481,21 +481,12 @@ namespace Roadie.Api.Services
: null; : null;
int[] randomReleaseIds = null; int[] randomReleaseIds = null;
SortedDictionary<int, int> randomReleaseData = null;
if (doRandomize ?? false) if (doRandomize ?? false)
{ {
var randomLimit = request.Limit ?? roadieUser?.RandomReleaseLimit ?? request.LimitValue; var randomLimit = request.Limit ?? roadieUser?.RandomReleaseLimit ?? request.LimitValue;
var userId = roadieUser?.Id ?? -1; randomReleaseData = DbContext.RandomReleaseIds(roadieUser?.Id ?? -1, randomLimit, request.FilterFavoriteOnly, request.FilterRatedOnly);
randomReleaseIds = randomReleaseData.Select(x => x.Value).ToArray();
// 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();
rowCount = DbContext.Releases.Count(); rowCount = DbContext.Releases.Count();
} }
@ -556,7 +547,12 @@ namespace Roadie.Api.Services
if (doRandomize ?? false) 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 else
{ {

View file

@ -284,45 +284,13 @@ namespace Roadie.Api.Services
} }
int[] randomTrackIds = null; int[] randomTrackIds = null;
SortedDictionary<int, int> randomTrackData = null;
if (doRandomize ?? false) if (doRandomize ?? false)
{ {
var randomLimit = roadieUser?.RandomReleaseLimit ?? request.LimitValue; var randomLimit = request.Limit ?? roadieUser?.RandomReleaseLimit ?? request.LimitValue;
var userId = roadieUser?.Id ?? -1; randomTrackData = DbContext.RandomTrackIds(roadieUser?.Id ?? -1, randomLimit, request.FilterFavoriteOnly, request.FilterRatedOnly);
randomTrackIds = randomTrackData.Select(x => x.Value).ToArray();
// 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();
rowCount = DbContext.Releases.Count(); rowCount = DbContext.Releases.Count();
} }
Guid?[] filterToTrackIds = null; Guid?[] filterToTrackIds = null;
@ -518,30 +486,38 @@ namespace Roadie.Api.Services
rowCount = rowCount ?? result.Count(); rowCount = rowCount ?? result.Count();
TrackList[] rows = null; TrackList[] rows = null;
if (request.Action == User.ActionKeyUserRated) if (!doRandomize ?? false)
{ {
sortBy = string.IsNullOrEmpty(request.Sort) if (request.Action == User.ActionKeyUserRated)
? request.OrderValue(new Dictionary<string, string> { { "UserTrack.Rating", "DESC" }, { "MediaNumber", "ASC" }, { "TrackNumber", "ASC" } })
: request.OrderValue();
}
else
{
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 = string.IsNullOrEmpty(request.Sort)
sortBy = request.OrderValue(new Dictionary<string, string> { { "Rating", request.Order }, { "PlayedCount", request.Order } }); ? request.OrderValue(new Dictionary<string, string> { { "UserTrack.Rating", "DESC" }, { "MediaNumber", "ASC" }, { "TrackNumber", "ASC" } })
: request.OrderValue();
} }
else else
{ {
sortBy = string.IsNullOrEmpty(request.Sort) if (request.Sort == "Rating")
? request.OrderValue(new Dictionary<string, string> { { "Release.Release.Text", "ASC" }, { "MediaNumber", "ASC" }, { "TrackNumber", "ASC" } }) {
: request.OrderValue(); // 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<string, string> { { "Rating", request.Order }, { "PlayedCount", request.Order } });
}
else
{
sortBy = string.IsNullOrEmpty(request.Sort)
? request.OrderValue(new Dictionary<string, string> { { "Release.Release.Text", "ASC" }, { "MediaNumber", "ASC" }, { "TrackNumber", "ASC" } })
: request.OrderValue();
}
} }
} }
if (doRandomize ?? false) 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 else
{ {

View file

@ -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 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]; var userTracks = DbContext.UserTracks.Include(x => x.Track).Where(x => x.UserId == user.Id).ToArray() ?? new data.UserTrack[0];
// This is MySQL specific var mostPlayedArtist = await DbContext.MostPlayedArtist(user.Id);
var sql = @"select a.* var mostPlayedRelease = await DbContext.MostPlayedRelease(user.Id);
FROM `usertrack` ut var lastPlayedTrack = await DbContext.MostPlayedTrack(user.Id);
join `track` t on (ut.trackId = t.id) var mostPlayedTrack = await DbContext.LastPlayedTrack(user.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);
model.Statistics = new UserStatistics model.Statistics = new UserStatistics
{ {
LastPlayedTrack = lastPlayedTrack == null LastPlayedTrack = lastPlayedTrack == null

View file

@ -33,7 +33,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.0.0" /> <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Serilog.AspNetCore" Version="3.1.0" /> <PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.1.3" /> <PackageReference Include="Serilog.Enrichers.Environment" Version="2.1.3" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" /> <PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
<PackageReference Include="Serilog.Exceptions" Version="5.3.1" /> <PackageReference Include="Serilog.Exceptions" Version="5.3.1" />

View file

@ -127,7 +127,7 @@ namespace Roadie.Api
} }
)); ));
services.AddDbContextPool<IRoadieDbContext, RoadieDbContext>( services.AddDbContextPool<IRoadieDbContext, MySQLRoadieDbContext>(
options => options.UseMySql(_configuration.GetConnectionString("RoadieDatabaseConnection"), options => options.UseMySql(_configuration.GetConnectionString("RoadieDatabaseConnection"),
mySqlOptions => mySqlOptions =>
{ {
@ -173,8 +173,7 @@ namespace Roadie.Api
settings.ConnectionString = _configuration.GetConnectionString("RoadieDatabaseConnection"); settings.ConnectionString = _configuration.GetConnectionString("RoadieDatabaseConnection");
// This is so 'User Secrets' can be used in Debugging // This is so 'User Secrets' can be used in Debugging
var integrationKeys = _configuration.GetSection("IntegrationKeys") var integrationKeys = _configuration.GetSection("IntegrationKeys").Get<IntegrationKey>();
.Get<IntegrationKey>();
if (integrationKeys != null) if (integrationKeys != null)
settings.Integrations.ApiKeys = new List<ApiKey> settings.Integrations.ApiKeys = new List<ApiKey>
{ {

View file

@ -1,8 +1,6 @@
using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
using Roadie.Api.Services; using Roadie.Api.Services;
using Roadie.Dlna.Server; using Roadie.Dlna.Server;
using Roadie.Library.Caching; using Roadie.Library.Caching;
@ -58,18 +56,7 @@ namespace Roadie.Dlna.Services
{ {
_authorizer.AddMethod(new UserAgentAuthorizer(Configuration.Dlna.AllowedUserAgents)); _authorizer.AddMethod(new UserAgentAuthorizer(Configuration.Dlna.AllowedUserAgents));
} }
var types = new DlnaMediaTypes[] { DlnaMediaTypes.Image, DlnaMediaTypes.Audio }; DbContext = data.DbContextFactory.Create(Configuration);
var optionsBuilder = new DbContextOptionsBuilder<data.RoadieDbContext>();
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);
var defaultNotFoundImages = new DefaultNotFoundImages(LoggerFactory.CreateLogger("DefaultNotFoundImages"), Configuration); var defaultNotFoundImages = new DefaultNotFoundImages(LoggerFactory.CreateLogger("DefaultNotFoundImages"), Configuration);
var imageService = new ImageService(Configuration, DbContext, CacheManager, LoggerFactory.CreateLogger("ImageService"), defaultNotFoundImages); var imageService = new ImageService(Configuration, DbContext, CacheManager, LoggerFactory.CreateLogger("ImageService"), defaultNotFoundImages);

View file

@ -9,19 +9,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roadie.Library", "Roadie.Ap
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roadie.Library.Tests", "Roadie.Api.Library.Tests\Roadie.Library.Tests.csproj", "{52E58F4B-88F0-4336-AD06-1184E857FA2C}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roadie.Library.Tests", "Roadie.Api.Library.Tests\Roadie.Library.Tests.csproj", "{52E58F4B-88F0-4336-AD06-1184E857FA2C}"
EndProject 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}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roadie.Api.Services", "Roadie.Api.Services\Roadie.Api.Services.csproj", "{7B37031E-F2AE-4BE2-9F6F-005CA7A6FDF1}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roadie.Api.Hubs", "Roadie.Api.Hubs\Roadie.Api.Hubs.csproj", "{E740C89E-3363-4577-873B-0871823E252C}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roadie.Api.Hubs", "Roadie.Api.Hubs\Roadie.Api.Hubs.csproj", "{E740C89E-3363-4577-873B-0871823E252C}"