Performance timings and improvements.

This commit is contained in:
Steven Hildreth 2019-08-04 11:30:19 -05:00
parent 6ff3a0004c
commit 17c29503af
19 changed files with 356 additions and 70 deletions

View file

@ -31,6 +31,7 @@
<ItemGroup>
<ProjectReference Include="..\Roadie.Api.Library\Roadie.Library.csproj" />
<ProjectReference Include="..\Roadie.Api.Services\Roadie.Api.Services.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,31 @@
using Roadie.Api.Services;
using Roadie.Library.Identity;
using Roadie.Library.MetaData.Audio;
using Roadie.Library.MetaData.ID3Tags;
using Roadie.Library.Utility;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Xunit;
namespace Roadie.Library.Tests
{
public class TokenHelperTests
{
[Fact]
public void GeneratePlayToken()
{
var sw = Stopwatch.StartNew();
var token = ServiceBase.TrackPlayToken(new ApplicationUser
{
Id = 1,
CreatedDate = DateTime.UtcNow
}, Guid.NewGuid());
sw.Stop();
Assert.NotNull(token);
}
}
}

View file

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Roadie.Library.Extensions
{
public static class EnumerableExt
{
public static string ToTimings(this IDictionary<string, long> values)
{
if(values == null || !values.Any())
{
return null;
}
var timings = new List<string>
{
$" TOTAL: { values.Sum(x => x.Value) }"
};
foreach (var timing in values.OrderByDescending(x => x.Value).ThenBy(x => x.Key))
{
timings.Add($"{ timing.Key}: { timing.Value }");
}
return string.Join(", ", timings);
}
}
}

View file

@ -365,5 +365,14 @@ namespace Roadie.Library.Extensions
var uri = new Uri(input);
return uri.Segments.Last();
}
public static string ToCSV(this IEnumerable<string> input)
{
if(input == null || !input.Any())
{
return null;
}
return string.Join(",", input);
}
}
}

View file

@ -118,5 +118,10 @@ namespace Roadie.Library.Models
{
BandStatus = "1";
}
public override string ToString()
{
return $"Id [{ Id }], Name [{ Name }]";
}
}
}

View file

@ -79,5 +79,10 @@ namespace Roadie.Library.Models.Releases
public short TrackCount { get; set; }
public UserRelease UserRating { get; set; }
public override string ToString()
{
return $"Id [{ Id }], Title [{ Title }]";
}
}
}

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Roadie.Library.Models.Releases
{
@ -9,6 +10,6 @@ namespace Roadie.Library.Models.Releases
public short? MediaNumber { get; set; }
public string SubTitle { get; set; }
public int? TrackCount { get; set; }
public IEnumerable<TrackList> Tracks { get; set; }
public IEnumerable<TrackList> Tracks { get; set; } = Enumerable.Empty<TrackList>();
}
}

View file

@ -95,5 +95,10 @@ namespace Roadie.Library.Models
public string TrackPlayUrl { get; set; }
public UserTrack UserRating { get; set; }
public override string ToString()
{
return $"Id [{ Id }], Title [{ Title }]";
}
}
}

View file

@ -123,26 +123,27 @@ namespace Roadie.Api.Services
tsw.Restart();
var artist = GetArtist(id);
tsw.Stop();
timings.Add("GetArtist", tsw.ElapsedMilliseconds);
timings.Add("getArtist", tsw.ElapsedMilliseconds);
tsw.Restart();
var userBookmarkResult =
await BookmarkService.List(roadieUser, new PagedRequest(), false, BookmarkType.Artist);
if (userBookmarkResult.IsSuccess)
result.Data.UserBookmarked =
userBookmarkResult?.Rows?.FirstOrDefault(x => x.Bookmark.Value == artist.RoadieId.ToString()) !=
null;
{
result.Data.UserBookmarked = userBookmarkResult?.Rows?.FirstOrDefault(x => x.Bookmark.Value == artist.RoadieId.ToString()) != null;
}
tsw.Stop();
timings.Add("userBookmarkResult", tsw.ElapsedMilliseconds);
tsw.Restart();
var userArtist =
DbContext.UserArtists.FirstOrDefault(x => x.ArtistId == artist.Id && x.UserId == roadieUser.Id);
var userArtist = DbContext.UserArtists.FirstOrDefault(x => x.ArtistId == artist.Id && x.UserId == roadieUser.Id);
if (userArtist != null)
{
result.Data.UserRating = new UserArtist
{
IsDisliked = userArtist.IsDisliked ?? false,
IsFavorite = userArtist.IsFavorite ?? false,
Rating = userArtist.Rating
};
}
tsw.Stop();
timings.Add("userArtist", tsw.ElapsedMilliseconds);
@ -156,21 +157,18 @@ namespace Roadie.Api.Services
select cr).ToArray();
foreach (var comment in result.Data.Comments)
{
var userCommentReaction =
userCommentReactions.FirstOrDefault(x => x.CommentId == comment.DatabaseId);
var userCommentReaction = userCommentReactions.FirstOrDefault(x => x.CommentId == comment.DatabaseId);
comment.IsDisliked = userCommentReaction?.ReactionValue == CommentReaction.Dislike;
comment.IsLiked = userCommentReaction?.ReactionValue == CommentReaction.Like;
}
tsw.Stop();
timings.Add("commentReactions", tsw.ElapsedMilliseconds);
timings.Add("userCommentReactions", tsw.ElapsedMilliseconds);
}
}
sw.Stop();
timings.Add("operation", sw.ElapsedMilliseconds);
Logger.LogDebug("ById Timings: id [{0}], includes [{1}], timings [{3}]", id, includes,
JsonConvert.SerializeObject(timings));
Logger.LogInformation($"ById Artist: `{ result?.Data }`, includes [{ includes.ToCSV() }], timings [{ timings.ToTimings() }]");
return new OperationResult<Artist>(result.Messages)
{
Data = result?.Data,
@ -931,10 +929,10 @@ namespace Roadie.Api.Services
? SafeParser.ToNumber<int?>(DbContext.Artists.Count(x => x.Rank > result.Rank) + 1)
: null;
tsw.Stop();
timings.Add("adaptArtist", tsw.ElapsedMilliseconds);
timings.Add("adapt", tsw.ElapsedMilliseconds);
tsw.Restart();
result.Thumbnail = MakeArtistThumbnailImage(id);
result.MediumThumbnail = MakeThumbnailImage(id, "artist", Configuration.MediumImageSize.Width, Configuration.MediumImageSize.Height);
tsw.Restart();
result.Genres = artist.Genres.Select(x => new DataToken
{
Text = x.Genre.Name,
@ -945,6 +943,7 @@ namespace Roadie.Api.Services
if (includes != null && includes.Any())
{
tsw.Restart();
if (includes.Contains("releases"))
{
var dtoReleases = new List<ReleaseList>();
@ -991,6 +990,8 @@ namespace Roadie.Api.Services
}
result.Releases = dtoReleases;
tsw.Stop();
timings.Add("releases", tsw.ElapsedMilliseconds);
}
if (includes.Contains("stats"))
@ -1277,8 +1278,7 @@ namespace Roadie.Api.Services
}
sw.Stop();
timings.Add("operation", sw.ElapsedMilliseconds);
Logger.LogDebug("ArtistByIdAction Timings: id [{0}], includes [{1}], timings [{3}]", id, includes, JsonConvert.SerializeObject(timings));
Logger.LogInformation($"ByIdAction: Artist `{ artist }`: includes [{includes.ToCSV() }], timings: [{ timings.ToTimings() }]");
return new OperationResult<Artist>
{
Data = result,

View file

@ -113,7 +113,7 @@ namespace Roadie.Api.Services
Text = track.Title,
Value = track.RoadieId.ToString()
};
row.Track = models.TrackList.FromDataTrack(MakeTrackPlayUrl(user, track.Id, track.RoadieId),
row.Track = models.TrackList.FromDataTrack(MakeTrackPlayUrl(user, HttpContext.BaseUrl, track.Id, track.RoadieId),
track,
track.ReleaseMedia.MediaNumber,
track.ReleaseMedia.Release,
@ -126,7 +126,7 @@ namespace Roadie.Api.Services
MakeArtistThumbnailImage(track.TrackArtist == null
? null
: (Guid?)track.TrackArtist.RoadieId));
row.Track.TrackPlayUrl = MakeTrackPlayUrl(user, track.Id, track.RoadieId);
row.Track.TrackPlayUrl = MakeTrackPlayUrl(user, HttpContext.BaseUrl, track.Id, track.RoadieId);
row.Thumbnail = MakeTrackThumbnailImage(track.RoadieId);
row.SortName = track.Title;
break;

View file

@ -1,6 +1,7 @@
using Mapster;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Roadie.Library;
using Roadie.Library.Caching;
using Roadie.Library.Configuration;
@ -331,10 +332,16 @@ namespace Roadie.Api.Services
private Task<OperationResult<Collection>> CollectionByIdAction(Guid id, IEnumerable<string> includes = null)
{
var timings = new Dictionary<string, long>();
var tsw = new Stopwatch();
var sw = Stopwatch.StartNew();
sw.Start();
tsw.Restart();
var collection = GetCollection(id);
tsw.Stop();
timings.Add("getCollection", tsw.ElapsedMilliseconds);
if (collection == null)
{
@ -355,6 +362,8 @@ namespace Roadie.Api.Services
result.CollectionFoundCount = (from crc in DbContext.CollectionReleases
where crc.CollectionId == collection.Id
select crc.Id).Count();
tsw.Stop();
timings.Add("adapt", tsw.ElapsedMilliseconds);
if (includes != null && includes.Any())
{
if (includes.Contains("list"))
@ -369,6 +378,8 @@ namespace Roadie.Api.Services
}
if (includes.Contains("releases"))
{
tsw.Restart();
result.Releases = (from crc in DbContext.CollectionReleases
join r in DbContext.Releases.Include(x => x.Artist) on crc.ReleaseId equals r.Id
where crc.CollectionId == collection.Id
@ -378,9 +389,13 @@ namespace Roadie.Api.Services
ListNumber = crc.ListNumber,
Release = ReleaseList.FromDataRelease(r, r.Artist, HttpContext.BaseUrl, MakeArtistThumbnailImage(r.Artist.RoadieId), MakeReleaseThumbnailImage(r.RoadieId))
}).ToArray();
tsw.Stop();
timings.Add("releases", tsw.ElapsedMilliseconds);
}
if (includes.Contains("stats"))
{
tsw.Restart();
var collectionReleases = from crc in DbContext.CollectionReleases
join r in DbContext.Releases on crc.ReleaseId equals r.Id
where crc.CollectionId == collection.Id
@ -404,10 +419,13 @@ namespace Roadie.Api.Services
TrackCount = collectionReleases.Sum(x => x.TrackCount),
TrackPlayedCount = collectionReleases.Sum(x => x.PlayedCount)
};
tsw.Stop();
timings.Add("stats", tsw.ElapsedMilliseconds);
}
if (includes.Contains("comments"))
{
tsw.Restart();
var collectionComments = DbContext.Comments.Include(x => x.User)
.Where(x => x.CollectionId == collection.Id)
.OrderByDescending(x => x.CreatedDate)
@ -430,9 +448,11 @@ namespace Roadie.Api.Services
}
result.Comments = comments;
}
tsw.Stop();
timings.Add("comments", tsw.ElapsedMilliseconds);
}
}
Logger.LogInformation($"ByIdAction: Collection `{ collection }`: includes [{includes.ToCSV()}], timings: [{ timings.ToTimings() }]");
sw.Stop();
return Task.FromResult(new OperationResult<Collection>
{

View file

@ -2,9 +2,11 @@
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Roadie.Library;
using Roadie.Library.Caching;
using Roadie.Library.Configuration;
using Roadie.Library.Extensions;
using Roadie.Library.Encoding;
using Roadie.Library.Identity;
using Roadie.Library.Imaging;
@ -85,6 +87,9 @@ namespace Roadie.Api.Services
private Task<OperationResult<Genre>> GenreByIdAction(Guid id, IEnumerable<string> includes = null)
{
var timings = new Dictionary<string, long>();
var tsw = new Stopwatch();
var sw = Stopwatch.StartNew();
sw.Start();
@ -93,15 +98,22 @@ namespace Roadie.Api.Services
{
return Task.FromResult(new OperationResult<Genre>(true, string.Format("Genre Not Found [{0}]", id)));
}
tsw.Stop();
timings.Add("getGenre", tsw.ElapsedMilliseconds);
tsw.Restart();
var result = genre.Adapt<Genre>();
result.AlternateNames = genre.AlternateNames;
result.Tags = genre.Tags;
result.Thumbnail = MakeLabelThumbnailImage(genre.RoadieId);
result.MediumThumbnail = MakeThumbnailImage(id, "genre", Configuration.MediumImageSize.Width, Configuration.MediumImageSize.Height);
tsw.Stop();
timings.Add("adapt", tsw.ElapsedMilliseconds);
if (includes != null && includes.Any())
{
if (includes.Contains("stats"))
{
tsw.Restart();
var releaseCount = (from rg in DbContext.ReleaseGenres
where rg.GenreId == genre.Id
select rg.Id).Count();
@ -113,9 +125,12 @@ namespace Roadie.Api.Services
ArtistCount = artistCount,
ReleaseCount = releaseCount
};
tsw.Stop();
timings.Add("stats", tsw.ElapsedMilliseconds);
}
}
sw.Stop();
Logger.LogInformation($"ByIdAction: Genre `{ genre }`: includes [{includes.ToCSV()}], timings: [{ timings.ToTimings() }]");
return Task.FromResult(new OperationResult<Genre>
{
Data = result,

View file

@ -361,24 +361,34 @@ namespace Roadie.Api.Services
private Task<OperationResult<Label>> LabelByIdAction(Guid id, IEnumerable<string> includes = null)
{
var timings = new Dictionary<string, long>();
var tsw = new Stopwatch();
var sw = Stopwatch.StartNew();
sw.Start();
tsw.Restart();
var label = GetLabel(id);
tsw.Stop();
timings.Add("GetLabel", tsw.ElapsedMilliseconds);
if (label == null)
{
return Task.FromResult(new OperationResult<Label>(true, string.Format("Label Not Found [{0}]", id)));
}
tsw.Restart();
var result = label.Adapt<Label>();
result.AlternateNames = label.AlternateNames;
result.Tags = label.Tags;
result.URLs = label.URLs;
result.Thumbnail = MakeLabelThumbnailImage(label.RoadieId);
result.MediumThumbnail = MakeThumbnailImage(id, "label", Configuration.MediumImageSize.Width,Configuration.MediumImageSize.Height);
tsw.Stop();
timings.Add("adapt", tsw.ElapsedMilliseconds);
if (includes != null && includes.Any())
{
if (includes.Contains("stats"))
{
tsw.Restart();
var labelTracks = from l in DbContext.Labels
join rl in DbContext.ReleaseLabels on l.Id equals rl.LabelId into rld
from rl in rld.DefaultIfEmpty()
@ -401,12 +411,17 @@ namespace Roadie.Api.Services
TrackSize = result.DurationTime,
FileSize = labelTracks.Sum(x => (long?)x.FileSize).ToFileSize()
};
tsw.Stop();
timings.Add("stats", tsw.ElapsedMilliseconds);
}
if (includes.Contains("comments"))
{
var labelComments = DbContext.Comments.Include(x => x.User).Where(x => x.LabelId == label.Id)
.OrderByDescending(x => x.CreatedDate).ToArray();
tsw.Restart();
var labelComments = DbContext.Comments.Include(x => x.User)
.Where(x => x.LabelId == label.Id)
.OrderByDescending(x => x.CreatedDate)
.ToArray();
if (labelComments.Any())
{
var comments = new List<Comment>();
@ -418,21 +433,20 @@ namespace Roadie.Api.Services
{
var comment = labelComment.Adapt<Comment>();
comment.DatabaseId = labelComment.Id;
comment.User = UserList.FromDataUser(labelComment.User,
MakeUserThumbnailImage(labelComment.User.RoadieId));
comment.DislikedCount = userCommentReactions.Count(x =>
x.CommentId == labelComment.Id && x.ReactionValue == CommentReaction.Dislike);
comment.LikedCount = userCommentReactions.Count(x =>
x.CommentId == labelComment.Id && x.ReactionValue == CommentReaction.Like);
comment.User = UserList.FromDataUser(labelComment.User, MakeUserThumbnailImage(labelComment.User.RoadieId));
comment.DislikedCount = userCommentReactions.Count(x => x.CommentId == labelComment.Id && x.ReactionValue == CommentReaction.Dislike);
comment.LikedCount = userCommentReactions.Count(x => x.CommentId == labelComment.Id && x.ReactionValue == CommentReaction.Like);
comments.Add(comment);
}
result.Comments = comments;
}
tsw.Stop();
timings.Add("stats", tsw.ElapsedMilliseconds);
}
}
sw.Stop();
Logger.LogInformation($"ByIdAction: Label `{ label }`: includes [{includes.ToCSV()}], timings: [{ timings.ToTimings() }]");
return Task.FromResult(new OperationResult<Label>
{
Data = result,

View file

@ -125,7 +125,7 @@ namespace Roadie.Api.Services
var user = GetUser(roadieUser.UserId);
foreach (var track in result.Data.Tracks)
{
track.Track.TrackPlayUrl = MakeTrackPlayUrl(user, track.Track.DatabaseId, track.Track.Id);
track.Track.TrackPlayUrl = MakeTrackPlayUrl(user, HttpContext.BaseUrl, track.Track.DatabaseId, track.Track.Id);
}
}
@ -444,15 +444,22 @@ namespace Roadie.Api.Services
private Task<OperationResult<Playlist>> PlaylistByIdAction(Guid id, IEnumerable<string> includes = null)
{
var timings = new Dictionary<string, long>();
var tsw = new Stopwatch();
var sw = Stopwatch.StartNew();
sw.Start();
tsw.Restart();
var playlist = GetPlaylist(id);
tsw.Stop();
timings.Add("getPlaylist", tsw.ElapsedMilliseconds);
if (playlist == null)
{
return Task.FromResult(new OperationResult<Playlist>(true, string.Format("Playlist Not Found [{0}]", id)));
}
tsw.Restart();
var result = playlist.Adapt<Playlist>();
result.AlternateNames = playlist.AlternateNames;
result.Tags = playlist.Tags;
@ -461,6 +468,8 @@ namespace Roadie.Api.Services
result.Maintainer = UserList.FromDataUser(maintainer, MakeUserThumbnailImage(maintainer.RoadieId));
result.Thumbnail = MakePlaylistThumbnailImage(playlist.RoadieId);
result.MediumThumbnail = MakeThumbnailImage(id, "playlist", Configuration.MediumImageSize.Width, Configuration.MediumImageSize.Height);
tsw.Stop();
timings.Add("adapt", tsw.ElapsedMilliseconds);
if (includes != null && includes.Any())
{
var playlistTracks = (from pl in DbContext.Playlists
@ -471,6 +480,7 @@ namespace Roadie.Api.Services
if (includes.Contains("stats"))
{
tsw.Restart();
result.Statistics = new ReleaseGroupingStatistics
{
ReleaseCount = result.ReleaseCount,
@ -478,9 +488,12 @@ namespace Roadie.Api.Services
TrackSize = result.DurationTime,
FileSize = playlistTracks.Sum(x => (long?)x.t.FileSize).ToFileSize()
};
tsw.Stop();
timings.Add("stats", tsw.ElapsedMilliseconds);
}
if (includes.Contains("tracks"))
{
tsw.Restart();
result.Tracks = (from plt in playlistTracks
join rm in DbContext.ReleaseMedias on plt.t.ReleaseMediaId equals rm.Id
join r in DbContext.Releases on rm.ReleaseId equals r.Id
@ -502,9 +515,12 @@ namespace Roadie.Api.Services
MakeArtistThumbnailImage(releaseArtist.RoadieId),
MakeArtistThumbnailImage(trackArtist == null ? null : (Guid?)trackArtist.RoadieId))
}).ToArray();
tsw.Stop();
timings.Add("tracks", tsw.ElapsedMilliseconds);
}
if (includes.Contains("comments"))
{
tsw.Restart();
var playlistComments = DbContext.Comments.Include(x => x.User)
.Where(x => x.PlaylistId == playlist.Id)
.OrderByDescending(x => x.CreatedDate)
@ -527,10 +543,13 @@ namespace Roadie.Api.Services
}
result.Comments = comments;
}
tsw.Stop();
timings.Add("comments", tsw.ElapsedMilliseconds);
}
}
sw.Stop();
Logger.LogInformation($"ByIdAction: Playlist `{ playlist }`: includes [{includes.ToCSV()}], timings: [{ timings.ToTimings() }]");
return Task.FromResult(new OperationResult<Playlist>
{
Data = result,

View file

@ -25,6 +25,7 @@ using Roadie.Library.Models.Users;
using Roadie.Library.Processors;
using Roadie.Library.Utility;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
@ -102,13 +103,28 @@ namespace Roadie.Api.Services
public async Task<OperationResult<Release>> ById(User roadieUser, Guid id, IEnumerable<string> includes = null)
{
var timings = new Dictionary<string, long>();
var tsw = new Stopwatch();
var sw = Stopwatch.StartNew();
sw.Start();
var cacheKey = string.Format("urn:release_by_id_operation:{0}:{1}", id, includes == null ? "0" : string.Join("|", includes));
var result = await CacheManager.GetAsync(cacheKey,async () => { return await ReleaseByIdAction(id, includes); }, data.Artist.CacheRegionUrn(id));
var result = await CacheManager.GetAsync(cacheKey,async () =>
{
tsw.Restart();
var rr = await ReleaseByIdAction(id, includes);
tsw.Stop();
timings.Add("ReleaseByIdAction", tsw.ElapsedMilliseconds);
return rr;
}, data.Artist.CacheRegionUrn(id));
if (result?.Data != null && roadieUser != null)
{
tsw.Restart();
var release = GetRelease(id);
tsw.Stop();
timings.Add("getRelease", tsw.ElapsedMilliseconds);
var userBookmarkResult = await BookmarkService.List(roadieUser, new PagedRequest(), false, BookmarkType.Release);
if (userBookmarkResult.IsSuccess)
{
@ -116,19 +132,26 @@ namespace Roadie.Api.Services
}
if (result.Data.Medias != null)
{
tsw.Restart();
var user = GetUser(roadieUser.UserId);
foreach (var media in result.Data.Medias)
tsw.Stop();
timings.Add("getUser", tsw.ElapsedMilliseconds);
tsw.Restart();
Parallel.ForEach(result.Data.Medias.SelectMany(x => x.Tracks), track =>
{
foreach (var track in media.Tracks)
{
track.TrackPlayUrl = MakeTrackPlayUrl(user, track.DatabaseId, track.Id);
}
}
var releaseTrackIds = result.Data.Medias.SelectMany(x => x.Tracks).Select(x => x.Id);
track.TrackPlayUrl = MakeTrackPlayUrl(user, HttpContext.BaseUrl, track.DatabaseId, track.Id);
});
tsw.Stop();
timings.Add("makeTrackPlayUrls", tsw.ElapsedMilliseconds);
tsw.Restart();
var releaseTrackIds = result.Data.Medias.SelectMany(x => x.Tracks).Select(x => x.Id).ToList();
var releaseUserTracks = (from ut in DbContext.UserTracks
join t in DbContext.Tracks on ut.TrackId equals t.Id
where releaseTrackIds.Contains(t.RoadieId)
where ut.UserId == roadieUser.Id
where (from x in releaseTrackIds select x).Contains(t.RoadieId)
select new
{
t,
@ -142,6 +165,7 @@ namespace Roadie.Api.Services
{
var releaseTrack = media.Tracks.FirstOrDefault(x => x.Id == releaseUserTrack.t.RoadieId);
if (releaseTrack != null)
{
releaseTrack.UserRating = new UserTrack
{
Rating = releaseUserTrack.ut.Rating,
@ -150,23 +174,30 @@ namespace Roadie.Api.Services
LastPlayed = releaseUserTrack.ut.LastPlayed,
PlayedCount = releaseUserTrack.ut.PlayedCount
};
}
}
}
}
tsw.Stop();
timings.Add("userTracks", tsw.ElapsedMilliseconds);
}
var userRelease = DbContext.UserReleases.FirstOrDefault(x => x.ReleaseId == release.Id && x.UserId == roadieUser.Id);
if (userRelease != null)
{
tsw.Restart();
result.Data.UserRating = new UserRelease
{
IsDisliked = userRelease.IsDisliked ?? false,
IsFavorite = userRelease.IsFavorite ?? false,
Rating = userRelease.Rating
};
tsw.Stop();
timings.Add("userRelease", tsw.ElapsedMilliseconds);
}
if (result.Data.Comments.Any())
{
tsw.Restart();
var commentIds = result.Data.Comments.Select(x => x.DatabaseId).ToArray();
var userCommentReactions = (from cr in DbContext.CommentReactions
where commentIds.Contains(cr.CommentId)
@ -178,10 +209,13 @@ namespace Roadie.Api.Services
comment.IsDisliked = userCommentReaction?.ReactionValue == CommentReaction.Dislike;
comment.IsLiked = userCommentReaction?.ReactionValue == CommentReaction.Like;
}
tsw.Stop();
timings.Add("userComments", tsw.ElapsedMilliseconds);
}
}
sw.Stop();
Logger.LogInformation($"ById Release: `{ result?.Data }`, includes [{ includes.ToCSV() }], timings [{ timings.ToTimings() }]");
return new OperationResult<Release>(result.Messages)
{
Data = result?.Data,
@ -1659,15 +1693,22 @@ namespace Roadie.Api.Services
private async Task<OperationResult<Release>> ReleaseByIdAction(Guid id, IEnumerable<string> includes = null)
{
var timings = new Dictionary<string, long>();
var tsw = new Stopwatch();
var sw = Stopwatch.StartNew();
sw.Start();
tsw.Restart();
var release = GetRelease(id);
tsw.Stop();
timings.Add("getRelease", tsw.ElapsedMilliseconds);
if (release == null)
{
return new OperationResult<Release>(true, string.Format("Release Not Found [{0}]", id));
}
tsw.Restart();
var result = release.Adapt<Release>();
result.Artist = ArtistList.FromDataArtist(release.Artist, MakeArtistThumbnailImage(release.Artist.RoadieId));
result.Thumbnail = MakeReleaseThumbnailImage(release.RoadieId);
@ -1685,8 +1726,11 @@ namespace Roadie.Api.Services
result.RankPosition = result.Rank > 0
? SafeParser.ToNumber<int?>(DbContext.Releases.Count(x => x.Rank > result.Rank) + 1)
: null;
tsw.Stop();
timings.Add("adapt", tsw.ElapsedMilliseconds);
if (release.SubmissionId.HasValue)
{
tsw.Restart();
var submission = DbContext.Submissions.Include(x => x.User).FirstOrDefault(x => x.Id == release.SubmissionId);
if (submission != null)
{
@ -1704,20 +1748,27 @@ namespace Roadie.Api.Services
};
}
}
tsw.Stop();
timings.Add("submissions", tsw.ElapsedMilliseconds);
}
if (includes != null && includes.Any())
{
if (includes.Contains("genres"))
{
tsw.Restart();
result.Genres = release.Genres.Select(x => new DataToken
{
Text = x.Genre.Name,
Value = x.Genre.RoadieId.ToString()
});
tsw.Stop();
timings.Add("genres", tsw.ElapsedMilliseconds);
}
if (includes.Contains("stats"))
{
tsw.Restart();
var releaseTracks = from r in DbContext.Releases
join rm in DbContext.ReleaseMedias on r.Id equals rm.ReleaseId
join t in DbContext.Tracks on rm.Id equals t.ReleaseMediaId
@ -1750,10 +1801,13 @@ namespace Roadie.Api.Services
result.MaxMediaNumber = releaseMedias.Any() ? releaseMedias.Max(x => x.MediaNumber) : (short)0;
result.Statistics = releaseStats;
result.MediaCount = release.MediaCount ?? (short?)releaseStats?.MediaCount;
tsw.Stop();
timings.Add("stats", tsw.ElapsedMilliseconds);
}
if (includes.Contains("images"))
{
tsw.Restart();
var releaseImages = DbContext.Images.Where(x => x.ReleaseId == release.Id).Select(x => MakeFullsizeImage(x.RoadieId, x.Caption)).ToArray();
if (releaseImages != null && releaseImages.Any())
{
@ -1766,20 +1820,26 @@ namespace Roadie.Api.Services
{
result.Images = result.Images.Concat(releaseImagesInFolder.Select((x, i) => MakeFullsizeSecondaryImage(id, ImageType.ReleaseSecondary, i)));
}
tsw.Stop();
timings.Add("images", tsw.ElapsedMilliseconds);
}
if (includes.Contains("playlists"))
{
tsw.Restart();
var pg = new PagedRequest
{
FilterToReleaseId = release.RoadieId
};
var r = await PlaylistService.List(pg);
if (r.IsSuccess) result.Playlists = r.Rows.ToArray();
tsw.Stop();
timings.Add("playlists", tsw.ElapsedMilliseconds);
}
if (includes.Contains("labels"))
{
tsw.Restart();
var releaseLabels = (from l in DbContext.Labels
join rl in DbContext.ReleaseLabels on l.Id equals rl.LabelId
where rl.ReleaseId == release.Id
@ -1823,10 +1883,13 @@ namespace Roadie.Api.Services
result.Labels = labels;
}
tsw.Stop();
timings.Add("labels", tsw.ElapsedMilliseconds);
}
if (includes.Contains("collections"))
{
tsw.Restart();
var releaseCollections = DbContext.CollectionReleases.Include(x => x.Collection)
.Where(x => x.ReleaseId == release.Id)
.OrderBy(x => x.ListNumber)
@ -1864,10 +1927,13 @@ namespace Roadie.Api.Services
}
result.Collections = collections;
}
tsw.Stop();
timings.Add("collections", tsw.ElapsedMilliseconds);
}
if (includes.Contains("comments"))
{
tsw.Restart();
var releaseComments = DbContext.Comments.Include(x => x.User)
.Where(x => x.ReleaseId == release.Id)
.OrderByDescending(x => x.CreatedDate)
@ -1890,10 +1956,13 @@ namespace Roadie.Api.Services
}
result.Comments = comments;
}
tsw.Stop();
timings.Add("comments", tsw.ElapsedMilliseconds);
}
if (includes.Contains("tracks"))
{
tsw.Restart();
var releaseMedias = new List<ReleaseMediaList>();
foreach (var releaseMedia in release.Medias.OrderBy(x => x.MediaNumber))
{
@ -1919,12 +1988,14 @@ namespace Roadie.Api.Services
rm.Tracks = rmTracks;
releaseMedias.Add(rm);
}
result.Medias = releaseMedias;
tsw.Stop();
timings.Add("tracks", tsw.ElapsedMilliseconds);
}
}
sw.Stop();
Logger.LogInformation($"ByIdAction: Release `{ release }`: includes [{includes.ToCSV()}], timings: [{ timings.ToTimings() }]");
return new OperationResult<Release>
{
Data = result,

View file

@ -22,6 +22,11 @@ namespace Roadie.Api.Services
{
public static string TrackTokenSalt = "B0246908-FBD6-4E12-A96C-AF5B086115B3";
private static readonly Lazy<Hashids> hashIds = new Lazy<Hashids>(() =>
{
return new Hashids(TrackTokenSalt);
});
protected readonly ICacheManager _cacheManager;
protected readonly IRoadieSettings _configuration;
protected readonly data.IRoadieDbContext _dbContext;
@ -54,16 +59,21 @@ namespace Roadie.Api.Services
public static bool ConfirmTrackPlayToken(ApplicationUser user, Guid trackRoadieId, string token)
{
if (string.IsNullOrEmpty(token)) return false;
if (string.IsNullOrEmpty(token))
{
return false;
}
return TrackPlayToken(user, trackRoadieId).Equals(token);
}
public static string TrackPlayToken(ApplicationUser user, Guid trackId)
{
var hashids = new Hashids(TrackTokenSalt);
var trackIdPart = BitConverter.ToInt32(trackId.ToByteArray(), 6);
if (trackIdPart < 0) trackIdPart *= -1;
var token = hashids.Encode(user.Id, SafeParser.ToNumber<int>(user.CreatedDate.Value.ToString("DDHHmmss")), trackIdPart);
if (trackIdPart < 0)
{
trackIdPart *= -1;
}
var token = hashIds.Value.Encode(user.Id, SafeParser.ToNumber<int>(user.CreatedDate.Value.ToString("DDHHmmss")), trackIdPart);
return token;
}
@ -279,10 +289,9 @@ namespace Roadie.Api.Services
return MakeThumbnailImage(id, "release");
}
protected string MakeTrackPlayUrl(ApplicationUser user, int trackId, Guid trackRoadieId)
public static string MakeTrackPlayUrl(ApplicationUser user, string baseUrl, int trackId, Guid trackRoadieId)
{
return
$"{HttpContext.BaseUrl}/play/track/{user.Id}/{TrackPlayToken(user, trackRoadieId)}/{trackRoadieId}.mp3";
return $"{baseUrl}/play/track/{user.Id}/{TrackPlayToken(user, trackRoadieId)}/{trackRoadieId}.mp3";
}
protected Image MakeTrackThumbnailImage(Guid id)

View file

@ -102,28 +102,48 @@ namespace Roadie.Api.Services
public async Task<OperationResult<Track>> ById(User roadieUser, Guid id, IEnumerable<string> includes)
{
var timings = new Dictionary<string, long>();
var tsw = new Stopwatch();
var sw = Stopwatch.StartNew();
sw.Start();
var cacheKey = string.Format("urn:track_by_id_operation:{0}:{1}", id,
includes == null ? "0" : string.Join("|", includes));
var result = await CacheManager.GetAsync(cacheKey,
async () => { return await TrackByIdAction(id, includes); }, data.Track.CacheRegionUrn(id));
var cacheKey = string.Format("urn:track_by_id_operation:{0}:{1}", id, includes == null ? "0" : string.Join("|", includes));
var result = await CacheManager.GetAsync(cacheKey, async () =>
{
tsw.Restart();
var rr = await TrackByIdAction(id, includes);
tsw.Stop();
timings.Add("TrackByIdAction", tsw.ElapsedMilliseconds);
return rr;
}, data.Track.CacheRegionUrn(id));
if (result?.Data != null && roadieUser != null)
{
tsw.Restart();
var user = GetUser(roadieUser.UserId);
tsw.Stop();
timings.Add("getUser", tsw.ElapsedMilliseconds);
tsw.Restart();
var track = GetTrack(id);
result.Data.TrackPlayUrl = MakeTrackPlayUrl(user, track.Id, track.RoadieId);
var userBookmarkResult =
await BookmarkService.List(roadieUser, new PagedRequest(), false, BookmarkType.Track);
tsw.Stop();
timings.Add("getTrack", tsw.ElapsedMilliseconds);
result.Data.TrackPlayUrl = MakeTrackPlayUrl(user, HttpContext.BaseUrl, track.Id, track.RoadieId);
tsw.Restart();
var userBookmarkResult = await BookmarkService.List(roadieUser, new PagedRequest(), false, BookmarkType.Track);
if (userBookmarkResult.IsSuccess)
{
result.Data.UserBookmarked =
userBookmarkResult?.Rows?.FirstOrDefault(x => x.Bookmark.Value == track.RoadieId.ToString()) !=
null;
}
tsw.Stop();
timings.Add("userBookmarks", tsw.ElapsedMilliseconds);
var userTrack =
DbContext.UserTracks.FirstOrDefault(x => x.TrackId == track.Id && x.UserId == roadieUser.Id);
tsw.Restart();
var userTrack = DbContext.UserTracks.FirstOrDefault(x => x.TrackId == track.Id && x.UserId == roadieUser.Id);
if (userTrack != null)
{
result.Data.UserRating = new UserTrack
@ -135,9 +155,13 @@ namespace Roadie.Api.Services
PlayedCount = userTrack.PlayedCount
};
}
tsw.Stop();
timings.Add("userTracks", tsw.ElapsedMilliseconds);
if (result.Data.Comments.Any())
{
tsw.Restart();
var commentIds = result.Data.Comments.Select(x => x.DatabaseId).ToArray();
var userCommentReactions = (from cr in DbContext.CommentReactions
where commentIds.Contains(cr.CommentId)
@ -150,10 +174,13 @@ namespace Roadie.Api.Services
comment.IsDisliked = userCommentReaction?.ReactionValue == CommentReaction.Dislike;
comment.IsLiked = userCommentReaction?.ReactionValue == CommentReaction.Like;
}
tsw.Stop();
timings.Add("userComments", tsw.ElapsedMilliseconds);
}
}
sw.Stop();
Logger.LogInformation($"ById Track: `{ result?.Data }`, includes [{ includes.ToCSV() }], timings [{ timings.ToTimings() }]");
return new OperationResult<Track>(result.Messages)
{
Data = result?.Data,
@ -541,7 +568,7 @@ namespace Roadie.Api.Services
TrackNumber = playListTrackPositions.ContainsKey(x.ti.Id)
? playListTrackPositions[x.ti.Id]
: x.ti.TrackNumber,
TrackPlayUrl = MakeTrackPlayUrl(user, x.ti.Id, x.ti.RoadieId)
TrackPlayUrl = MakeTrackPlayUrl(user, HttpContext.BaseUrl, x.ti.Id, x.ti.RoadieId)
});
string sortBy = null;
@ -926,16 +953,22 @@ namespace Roadie.Api.Services
private Task<OperationResult<Track>> TrackByIdAction(Guid id, IEnumerable<string> includes)
{
var timings = new Dictionary<string, long>();
var tsw = new Stopwatch();
var sw = Stopwatch.StartNew();
sw.Start();
tsw.Restart();
var track = GetTrack(id);
tsw.Stop();
timings.Add("getTrack", tsw.ElapsedMilliseconds);
if (track == null)
{
return Task.FromResult(new OperationResult<Track>(true, string.Format("Track Not Found [{0}]", id)));
}
tsw.Restart();
var result = track.Adapt<Track>();
result.IsLocked = (track.IsLocked ?? false) ||
(track.ReleaseMedia.IsLocked ?? false) ||
@ -952,8 +985,11 @@ namespace Roadie.Api.Services
HttpContext.BaseUrl, MakeArtistThumbnailImage(track.ReleaseMedia.Release.Artist.RoadieId),
MakeReleaseThumbnailImage(track.ReleaseMedia.Release.RoadieId));
result.ReleaseThumbnail = MakeReleaseThumbnailImage(track.ReleaseMedia.Release.RoadieId);
tsw.Stop();
timings.Add("adapt", tsw.ElapsedMilliseconds);
if (track.ArtistId.HasValue)
{
tsw.Restart();
var trackArtist = DbContext.Artists.FirstOrDefault(x => x.Id == track.ArtistId);
if (trackArtist == null)
{
@ -966,12 +1002,15 @@ namespace Roadie.Api.Services
result.TrackArtistToken = result.TrackArtist.Artist;
result.TrackArtistThumbnail = MakeArtistThumbnailImage(trackArtist.RoadieId);
}
tsw.Stop();
timings.Add("trackArtist", tsw.ElapsedMilliseconds);
}
if (includes != null && includes.Any())
{
if (includes.Contains("stats"))
{
tsw.Restart();
result.Statistics = new TrackStatistics
{
FileSizeFormatted = ((long?)track.FileSize).ToFileSize(),
@ -987,10 +1026,13 @@ namespace Roadie.Api.Services
result.Statistics.DislikedCount = userTracks.Count(x => x.IsDisliked ?? false);
result.Statistics.FavoriteCount = userTracks.Count(x => x.IsFavorite ?? false);
}
tsw.Stop();
timings.Add("stats", tsw.ElapsedMilliseconds);
}
if (includes.Contains("comments"))
{
tsw.Restart();
var trackComments = DbContext.Comments.Include(x => x.User).Where(x => x.TrackId == track.Id)
.OrderByDescending(x => x.CreatedDate).ToArray();
if (trackComments.Any())
@ -1015,10 +1057,13 @@ namespace Roadie.Api.Services
result.Comments = comments;
}
tsw.Stop();
timings.Add("comments", tsw.ElapsedMilliseconds);
}
}
sw.Stop();
Logger.LogInformation($"ByIdAction: Track `{ track }`: includes [{includes.ToCSV()}], timings: [{ timings.ToTimings() }]");
return Task.FromResult(new OperationResult<Track>
{
Data = result,

View file

@ -8,6 +8,7 @@ using Roadie.Library.Caching;
using Roadie.Library.Configuration;
using Roadie.Library.Encoding;
using Roadie.Library.Enums;
using Roadie.Library.Extensions;
using Roadie.Library.Identity;
using Roadie.Library.Imaging;
using Roadie.Library.MetaData.LastFm;
@ -52,8 +53,6 @@ namespace Roadie.Api.Services
public async Task<OperationResult<User>> ById(User user, Guid id, IEnumerable<string> includes, bool isAccountSettingsEdit = false)
{
var timings = new Dictionary<string, long>();
var tsw = new Stopwatch();
if(isAccountSettingsEdit)
{
if(user.UserId != id && !user.IsAdmin)
@ -68,10 +67,7 @@ namespace Roadie.Api.Services
var cacheKey = string.Format("urn:user_by_id_operation:{0}:{1}", id, isAccountSettingsEdit);
var result = await CacheManager.GetAsync(cacheKey, async () =>
{
tsw.Restart();
var rr = await UserByIdAction(id, includes);
tsw.Stop();
timings.Add("UserByIdAction", tsw.ElapsedMilliseconds);
return rr;
}, ApplicationUser.CacheRegionUrn(id));
sw.Stop();
@ -84,8 +80,6 @@ namespace Roadie.Api.Services
result.Data.ConcurrencyStamp = null;
}
}
timings.Add("operation", sw.ElapsedMilliseconds);
Logger.LogDebug("ById Timings: id [{0}]", id);
return new OperationResult<User>(result.Messages)
{
Data = result?.Data,
@ -557,19 +551,31 @@ namespace Roadie.Api.Services
private Task<OperationResult<User>> UserByIdAction(Guid id, IEnumerable<string> includes)
{
var timings = new Dictionary<string, long>();
var tsw = new Stopwatch();
tsw.Restart();
var user = GetUser(id);
tsw.Stop();
timings.Add("getUser", tsw.ElapsedMilliseconds);
if (user == null)
{
return Task.FromResult(new OperationResult<User>(true, string.Format("User Not Found [{0}]", id)));
}
tsw.Restart();
var model = user.Adapt<User>();
model.MediumThumbnail = MakeThumbnailImage(id, "user", Configuration.MediumImageSize.Width, Configuration.MediumImageSize.Height);
model.IsAdmin = user.UserRoles?.Any(x => x.Role?.NormalizedName == "ADMIN") ?? false;
model.IsEditor = model.IsAdmin ? true : user.UserRoles?.Any(x => x.Role?.NormalizedName == "EDITOR") ?? false;
tsw.Stop();
timings.Add("adapt", tsw.ElapsedMilliseconds);
if (includes != null && includes.Any())
{
if (includes.Contains("stats"))
{
tsw.Restart();
var userArtists = DbContext.UserArtists.Include(x => x.Artist).Where(x => x.UserId == user.Id).ToArray() ?? new data.UserArtist[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];
@ -631,7 +637,7 @@ namespace Roadie.Api.Services
LastPlayedTrack = lastPlayedTrack == null
? null
: models.TrackList.FromDataTrack(
MakeTrackPlayUrl(user, lastPlayedTrack.Id, lastPlayedTrack.RoadieId),
MakeTrackPlayUrl(user, HttpContext.BaseUrl, lastPlayedTrack.Id, lastPlayedTrack.RoadieId),
lastPlayedTrack,
lastPlayedTrack.ReleaseMedia.MediaNumber,
lastPlayedTrack.ReleaseMedia.Release,
@ -658,7 +664,7 @@ namespace Roadie.Api.Services
MostPlayedTrack = mostPlayedTrack == null
? null
: models.TrackList.FromDataTrack(
MakeTrackPlayUrl(user, mostPlayedTrack.Id, mostPlayedTrack.RoadieId),
MakeTrackPlayUrl(user, HttpContext.BaseUrl, mostPlayedTrack.Id, mostPlayedTrack.RoadieId),
mostPlayedTrack,
mostPlayedTrack.ReleaseMedia.MediaNumber,
mostPlayedTrack.ReleaseMedia.Release,
@ -682,8 +688,11 @@ namespace Roadie.Api.Services
FavoritedTracks = userTracks.Where(x => x.IsFavorite ?? false).Count(),
DislikedTracks = userTracks.Where(x => x.IsDisliked ?? false).Count()
};
tsw.Stop();
timings.Add("stats", tsw.ElapsedMilliseconds);
}
}
Logger.LogInformation($"ByIdAction: User `{ user }`: includes [{includes.ToCSV()}], timings: [{ timings.ToTimings() }]");
return Task.FromResult(new OperationResult<User>
{
IsSuccess = true,

View file

@ -3,7 +3,7 @@
"Roadie.Api": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Production"
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:5123/"
}