This commit is contained in:
Steven Hildreth 2019-07-18 10:52:00 -05:00
parent 0c1b276cda
commit 1448b0dbb3
8 changed files with 321 additions and 78 deletions

View file

@ -7,6 +7,7 @@ using Roadie.Library.Processors;
using Roadie.Library.SearchEngines.MetaData.Discogs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
@ -15,6 +16,183 @@ namespace Roadie.Library.Tests
{
public class ExtensionTests
{
[Fact]
public void Shuffle_Unique_Order()
{
var tracks = new List<Roadie.Library.Models.TrackList>
{
new Models.TrackList
{
Track = new Models.DataToken { Value ="A1_R1", Text = "A1_R1" },
Artist = new Models.ArtistList
{
Artist = new Models.DataToken { Value = "A1", Text = "A1" }
},
Release = new Models.Releases.ReleaseList
{
Release = new Models.DataToken { Value = "R1", Text = "R1" }
}
},
new Models.TrackList
{
Track = new Models.DataToken { Value = "A2_R4", Text = "A2_R4" },
Artist = new Models.ArtistList
{
Artist = new Models.DataToken { Value = "A2", Text = "A2" }
},
Release = new Models.Releases.ReleaseList
{
Release = new Models.DataToken { Value = "R4", Text = "R4" }
}
},
new Models.TrackList
{
Track = new Models.DataToken { Value = "A1_R5", Text = "A1_R5" },
Artist = new Models.ArtistList
{
Artist = new Models.DataToken { Value = "A1", Text = "A1" }
},
Release = new Models.Releases.ReleaseList
{
Release = new Models.DataToken { Value = "R5", Text = "R5" }
}
},
new Models.TrackList
{
Track = new Models.DataToken { Value = "A1_R1", Text = "A1_R1" },
Artist = new Models.ArtistList
{
Artist = new Models.DataToken { Value = "A1", Text = "A1" }
},
Release = new Models.Releases.ReleaseList
{
Release = new Models.DataToken { Value = "R1", Text = "R1" }
}
},
new Models.TrackList
{
Track = new Models.DataToken { Value = "A3_R6", Text = "A3_R6" },
Artist = new Models.ArtistList
{
Artist = new Models.DataToken { Value = "A3", Text = "A3" }
},
Release = new Models.Releases.ReleaseList
{
Release = new Models.DataToken { Value = "R6", Text = "R6" }
}
},
new Models.TrackList
{
Track = new Models.DataToken { Value = "A2_R5", Text = "A2_R5" },
Artist = new Models.ArtistList
{
Artist = new Models.DataToken { Value = "A2", Text = "A2" }
},
Release = new Models.Releases.ReleaseList
{
Release = new Models.DataToken { Value = "R5", Text = "R5" }
}
},
new Models.TrackList
{
Track = new Models.DataToken { Value = "A2_R6", Text = "A2_R6" },
Artist = new Models.ArtistList
{
Artist = new Models.DataToken { Value = "A2", Text = "A2" }
},
Release = new Models.Releases.ReleaseList
{
Release = new Models.DataToken { Value = "R6", Text = "R6" }
}
},
new Models.TrackList
{
Track = new Models.DataToken { Value = "A4_R7", Text = "A4_R7" },
Artist = new Models.ArtistList
{
Artist = new Models.DataToken { Value = "A4", Text = "A4" }
},
Release = new Models.Releases.ReleaseList
{
Release = new Models.DataToken { Value = "R7", Text = "R7" }
}
},
new Models.TrackList
{
Track = new Models.DataToken { Value = "A5_R8", Text = "A5_R8" },
Artist = new Models.ArtistList
{
Artist = new Models.DataToken { Value = "A5", Text = "A5" }
},
Release = new Models.Releases.ReleaseList
{
Release = new Models.DataToken { Value = "R8", Text = "R8" }
}
},
new Models.TrackList
{
Track = new Models.DataToken { Value = "A6_R9", Text = "A6_R9" },
Artist = new Models.ArtistList
{
Artist = new Models.DataToken { Value = "A6", Text = "A6" }
},
Release = new Models.Releases.ReleaseList
{
Release = new Models.DataToken { Value = "R9", Text = "R9" }
}
},
new Models.TrackList
{
Track = new Models.DataToken { Value = "A7_R10", Text = "A7_R10" },
Artist = new Models.ArtistList
{
Artist = new Models.DataToken { Value = "A7", Text = "A7" }
},
Release = new Models.Releases.ReleaseList
{
Release = new Models.DataToken { Value = "R10", Text = "R10" }
}
},
new Models.TrackList
{
Track = new Models.DataToken { Value = "A8_R11", Text = "A8_R11" },
Artist = new Models.ArtistList
{
Artist = new Models.DataToken { Value = "A8", Text = "A8" }
},
Release = new Models.Releases.ReleaseList
{
Release = new Models.DataToken { Value = "R11", Text = "R11" }
}
},
new Models.TrackList
{
Track = new Models.DataToken { Value ="A9_R12", Text = "A9_R12" },
Artist = new Models.ArtistList
{
Artist = new Models.DataToken { Value = "A9", Text = "A9" }
},
Release = new Models.Releases.ReleaseList
{
Release = new Models.DataToken { Value = "R12", Text = "R12" }
}
}
};
var shuffledTracks = Models.TrackList.Shuffle(tracks);
var lastTrack = shuffledTracks.First();
foreach(var track in shuffledTracks.Skip(1))
{
Assert.NotEqual(track.Artist.Artist.Text, lastTrack.Artist.Artist.Text);
Assert.NotEqual(track.Release.Release.Text, lastTrack.Release.Release.Text);
lastTrack = track;
}
Assert.Equal(tracks.Count(), shuffledTracks.Count());
}
[Fact]
public void From_Unix_Time()
{

View file

@ -12,20 +12,32 @@ namespace Roadie.Library.Extensions
return source.OrderBy(item => rnd.Next());
}
public static void Shuffle<T>(this IList<T> list)
//public static void Shuffle<T>(this IList<T> list)
//{
// var n = list.Count;
// var rnd = new Random();
// while (n > 1)
// {
// var k = rnd.Next(0, n) % n;
// n--;
// var value = list[k];
// list[k] = list[n];
// list[n] = value;
// }
//}
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
{
var n = list.Count;
var rnd = new Random();
while (n > 1)
T[] elements = source.ToArray();
for (int i = elements.Length - 1; i >= 0; i--)
{
var k = rnd.Next(0, n) % n;
n--;
var value = list[k];
list[k] = list[n];
list[n] = value;
int swapIndex = rng.Next(i + 1);
yield return elements[swapIndex];
elements[swapIndex] = elements[i];
}
}
public static string ToDelimitedList<T>(this IList<T> list, char delimiter = '|')
{
return ((ICollection<T>)list).ToDelimitedList(delimiter);

View file

@ -33,15 +33,15 @@ namespace Roadie.Library.Inspect.Plugins.Directory
var deletedFiles = new List<string>();
var fileExtensionsToDelete = Configuration.FileExtensionsToDelete ?? new string[0];
foreach (var file in directory.GetFiles("*.*", SearchOption.AllDirectories))
{
if (fileExtensionsToDelete.Any(x => x.Equals(file.Extension, StringComparison.OrdinalIgnoreCase)))
{
if (!Configuration.Inspector.IsInReadOnlyMode) file.Delete();
deletedFiles.Add(file.Name);
Console.WriteLine($" X Deleted File [{file}], Was found in in FileExtensionsToDelete");
}
}
result.Data = $"Deleted [{deletedFiles.Count()}] unwanted files";
;
result.IsSuccess = true;
return result;
}

View file

@ -39,7 +39,7 @@ namespace Roadie.Library.Inspect.Plugins.File
}
if (!string.IsNullOrEmpty(result) && !string.IsNullOrEmpty(trackArtist))
{
result = result.Replace(splitCharacter + trackArtist + splitCharacter, "",StringComparison.OrdinalIgnoreCase);
result = result.Replace(splitCharacter + trackArtist + splitCharacter, "", StringComparison.OrdinalIgnoreCase);
result = result.Replace(trackArtist + splitCharacter, "", StringComparison.OrdinalIgnoreCase);
result = result.Replace(splitCharacter + trackArtist, "", StringComparison.OrdinalIgnoreCase);
result = result.Replace(trackArtist, "", StringComparison.OrdinalIgnoreCase);

View file

@ -3,6 +3,7 @@ using Roadie.Library.Models.Releases;
using Roadie.Library.Models.Users;
using Roadie.Library.Utility;
using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
@ -68,6 +69,43 @@ namespace Roadie.Library.Models
}
}
public int GetArtistReleaseHashCode() => this.Artist?.Artist?.Value.GetHashCode() + this.Release?.Release?.Value.GetHashCode() ?? 0;
public override int GetHashCode() => this.Artist?.Artist?.Value.GetHashCode() + this.Release?.Release?.Value.GetHashCode() + this.Track?.Value.GetHashCode() ?? 0;
/// <summary>
/// Ensure that the given list is sorted so that Artist and Release don't repeat in sequence.
/// </summary>
public static IEnumerable<TrackList> Shuffle(IEnumerable<TrackList> tracks)
{
var shuffledTracks = new List<TrackList>(tracks);
Models.TrackList lastTrack = shuffledTracks.Skip(1).First();
foreach (var track in tracks)
{
if (lastTrack?.Artist?.Artist?.Value == track?.Artist?.Artist?.Value ||
lastTrack?.Release?.Release?.Value == track?.Release?.Release?.Value)
{
shuffledTracks.Remove(track);
var insertAt = shuffledTracks.Count() - 1;
foreach (var st in shuffledTracks)
{
if (st?.Artist?.Artist?.Value != track?.Artist?.Artist?.Value &&
st?.Release?.Release?.Value != track?.Release?.Release?.Value)
{
break;
}
insertAt--;
}
shuffledTracks.Insert(insertAt, track);
lastTrack = track;
continue;
}
lastTrack = track;
}
return shuffledTracks;
}
public static TrackList FromDataTrack(string trackPlayUrl,
Data.Track track,
int releaseMediaNumber,
@ -107,5 +145,7 @@ namespace Roadie.Library.Models
Thumbnail = trackThumbnail
};
}
}
}

View file

@ -96,9 +96,16 @@ namespace Roadie.Library.Scrobble
if (track.ArtistId.HasValue)
{
trackArtist = DbContext.Artists.FirstOrDefault(x => x.Id == track.ArtistId);
trackArtist.LastPlayed = now;
trackArtist.PlayedCount = (trackArtist.PlayedCount ?? 0) + 1;
CacheManager.ClearRegion(trackArtist.CacheRegion);
if (trackArtist != null)
{
trackArtist.LastPlayed = now;
trackArtist.PlayedCount = (trackArtist.PlayedCount ?? 0) + 1;
CacheManager.ClearRegion(trackArtist.CacheRegion);
}
else
{
Logger.LogWarning($"Invalid Track Artist [{ track.ArtistId }] on Track [{ track.Id }]");
}
}
await DbContext.SaveChangesAsync();

View file

@ -5,8 +5,7 @@ namespace Roadie.Library.Utility
{
public static class StaticRandom
{
private static readonly ThreadLocal<Random> threadLocal = new ThreadLocal<Random>
(() => new Random(Interlocked.Increment(ref seed)));
private static readonly ThreadLocal<Random> threadLocal = new ThreadLocal<Random> (() => new Random(Interlocked.Increment(ref seed)));
private static int seed;
public static Random Instance => threadLocal.Value;

View file

@ -34,14 +34,9 @@ namespace Roadie.Api.Services
private IBookmarkService BookmarkService { get; }
public TrackService(IRoadieSettings configuration,
IHttpEncoder httpEncoder,
IHttpContext httpContext,
data.IRoadieDbContext dbContext,
ICacheManager cacheManager,
ILogger<TrackService> logger,
IBookmarkService bookmarkService,
IAdminService adminService)
public TrackService(IRoadieSettings configuration, IHttpEncoder httpEncoder,IHttpContext httpContext,
data.IRoadieDbContext dbContext, ICacheManager cacheManager,ILogger<TrackService> logger,
IBookmarkService bookmarkService, IAdminService adminService)
: base(configuration, httpEncoder, dbContext, cacheManager, logger, httpContext)
{
BookmarkService = bookmarkService;
@ -169,8 +164,7 @@ namespace Roadie.Api.Services
};
}
public Task<Library.Models.Pagination.PagedResult<TrackList>> List(PagedRequest request, User roadieUser,
bool? doRandomize = false, Guid? releaseId = null)
public Task<Library.Models.Pagination.PagedResult<TrackList>> List(PagedRequest request, User roadieUser, bool? doRandomize = false, Guid? releaseId = null)
{
try
{
@ -256,47 +250,56 @@ namespace Roadie.Api.Services
var randomLimit = roadieUser?.RandomReleaseLimit ?? request.Limit ?? 50;
var userId = roadieUser?.Id ?? -1;
if (!request.FilterFavoriteOnly)
{
// Select random tracks that are not disliked Artist, Release or Track by user.
var dislikedArtistIds = (from ua in DbContext.UserArtists
where ua.UserId == userId
where ua.IsDisliked == true
select ua.ArtistId).ToArray();
var dislikedReleaseIds = (from ur in DbContext.UserReleases
where ur.UserId == userId
where ur.IsDisliked == true
select ur.ReleaseId).ToArray();
var dislikedTrackIds = (from ut in DbContext.UserTracks
where ut.UserId == userId
where ut.IsDisliked == true
select ut.TrackId).ToArray();
randomTrackIds = (from t in DbContext.Tracks
join rm in DbContext.ReleaseMedias on t.ReleaseMediaId equals rm.Id
join r in DbContext.Releases on rm.ReleaseId equals r.Id
where !request.FilterRatedOnly || request.FilterRatedOnly && t.Rating > 0
where !dislikedArtistIds.Contains(r.ArtistId)
where !dislikedArtistIds.Contains(t.ArtistId ?? 0)
where !dislikedReleaseIds.Contains(r.Id)
where !dislikedTrackIds.Contains(t.Id)
where t.Hash != null
select new TrackList
{
DatabaseId = t.Id
})
.OrderBy(x => x.RandomSortId)
.Take(randomLimit)
.Select(x => x.DatabaseId)
.ToArray();
}
// Select random tracks that are not disliked Artist, Release or Track by user.
var dislikedArtistIds = (from ua in DbContext.UserArtists
where ua.UserId == userId
where ua.IsDisliked == true
select ua.ArtistId).ToArray();
var dislikedReleaseIds = (from ur in DbContext.UserReleases
where ur.UserId == userId
where ur.IsDisliked == true
select ur.ReleaseId).ToArray();
var dislikedTrackIds = (from ut in DbContext.UserTracks
where ut.UserId == userId
where ut.IsDisliked == true
select ut.TrackId).ToArray();
int[] favoritedTrackIds = null;
if (request.FilterFavoriteOnly)
{
rowCount = favoriteTrackIds.Count();
}
else
{
rowCount = DbContext.Tracks.Where(x => x.Hash != null).Count();
favoritedTrackIds = (from ut in DbContext.UserTracks
where ut.UserId == userId
where ut.IsFavorite == true
select ut.TrackId).ToArray();
favoriteTrackIds = new int[0].AsQueryable();
request.FilterFavoriteOnly = false;
}
randomTrackIds = TrackList.Shuffle((from t in DbContext.Tracks
join rm in DbContext.ReleaseMedias on t.ReleaseMediaId equals rm.Id
join r in DbContext.Releases on rm.ReleaseId equals r.Id
join a in DbContext.Artists on r.ArtistId equals a.Id
where !request.FilterRatedOnly || request.FilterRatedOnly && t.Rating > 0
where !dislikedArtistIds.Contains(r.ArtistId)
where !dislikedArtistIds.Contains(t.ArtistId ?? 0)
where !dislikedReleaseIds.Contains(r.Id)
where !dislikedTrackIds.Contains(t.Id)
where favoritedTrackIds == null || favoritedTrackIds.Contains(t.Id)
where t.Hash != null
select new TrackList
{
DatabaseId = t.Id,
Artist = new ArtistList
{
Artist = new DataToken { Value = a.RoadieId.ToString(), Text = a.Name }
},
Release = new ReleaseList
{
Release = new DataToken { Value = r.RoadieId.ToString(), Text = r.Title}
}
})
.OrderBy(x => x.RandomSortId)
.Take(randomLimit))
.Select(x => x.DatabaseId)
.ToArray();
}
Guid?[] filterToTrackIds = null;
@ -348,18 +351,12 @@ namespace Roadie.Api.Services
where filterToTrackIds == null || filterToTrackIds.Contains(t.RoadieId)
where releaseId == null || releaseId != null && r.RoadieId == releaseId
where request.FilterMinimumRating == null || t.Rating >= request.FilterMinimumRating.Value
where request.FilterValue == "" || t.Title.Contains(request.FilterValue) ||
t.AlternateNames.Contains(request.FilterValue) ||
t.AlternateNames.Contains(normalizedFilterValue) || t.PartTitles.Contains(request.FilterValue)
where !isEqualFilter || t.Title.Equals(request.FilterValue) ||
t.AlternateNames.Equals(request.FilterValue) ||
t.AlternateNames.Equals(normalizedFilterValue) || t.PartTitles.Equals(request.FilterValue)
where request.FilterValue == "" || t.Title.Contains(request.FilterValue) || t.AlternateNames.Contains(request.FilterValue) || t.AlternateNames.Contains(normalizedFilterValue) || t.PartTitles.Contains(request.FilterValue)
where !isEqualFilter || t.Title.Equals(request.FilterValue) || t.AlternateNames.Equals(request.FilterValue) || t.AlternateNames.Equals(normalizedFilterValue) || t.PartTitles.Equals(request.FilterValue)
where !request.FilterFavoriteOnly || favoriteTrackIds.Contains(t.Id)
where request.FilterToPlaylistId == null || playlistTrackIds.Contains(t.Id)
where !request.FilterTopPlayedOnly || topTrackids.Contains(t.Id)
where request.FilterToArtistId == null || request.FilterToArtistId != null &&
(t.TrackArtist != null && t.TrackArtist.RoadieId == request.FilterToArtistId ||
r.Artist.RoadieId == request.FilterToArtistId)
where request.FilterToArtistId == null || request.FilterToArtistId != null && (t.TrackArtist != null && t.TrackArtist.RoadieId == request.FilterToArtistId || r.Artist.RoadieId == request.FilterToArtistId)
where !request.IsHistoryRequest || t.PlayedCount > 0
where request.FilterToCollectionId == null || collectionTrackIds.Contains(t.Id)
select new
@ -506,14 +503,24 @@ namespace Roadie.Api.Services
}
else
{
sortBy = string.IsNullOrEmpty(request.Sort)
? request.OrderValue(new Dictionary<string, string>{{"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<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)
{
rows = result.ToArray();
rows = result.OrderBy(x => x.Artist.RandomSortId)
.ThenBy(x => x.RandomSortId)
.ToArray();
}
else
{