Work for Roadie-VueJS #63

This commit is contained in:
Steven Hildreth 2019-08-02 15:59:24 -05:00
parent ed0aa051d6
commit 7710fef6b0
37 changed files with 984 additions and 446 deletions

View file

@ -21,6 +21,7 @@ namespace Roadie.Library.Configuration
string ImageFolder { get; set; }
string LabelImageFolder { get; }
string CollectionImageFolder { get; }
string GenreImageFolder { get; }
string PlaylistImageFolder { get; }
string UserImageFolder { get; }
string ListenAddress { get; set; }
@ -47,5 +48,6 @@ namespace Roadie.Library.Configuration
bool IsRegistrationClosed { get; set; }
bool UseRegistrationTokens { get; set; }
string SearchEngineReposFolder { get; set; }
short DefaultRowsPerPage { get; set; }
}
}

View file

@ -63,6 +63,14 @@ namespace Roadie.Library.Configuration
}
}
public string GenreImageFolder
{
get
{
return Path.Combine(ImageFolder ?? LibraryFolder, "__roadie_images", "genres");
}
}
public string PlaylistImageFolder
{
get
@ -133,6 +141,8 @@ namespace Roadie.Library.Configuration
/// </summary>
public string SearchEngineReposFolder { get; set; }
public short DefaultRowsPerPage { get; set; }
public RoadieSettings()
{
ArtistNameReplace = new Dictionary<string, IEnumerable<string>>
@ -152,6 +162,7 @@ namespace Roadie.Library.Configuration
SiteName = "Roadie";
SmallImageSize = new ImageSize { Width = 160, Height = 160 };
ThumbnailImageSize = new ImageSize { Width = 80, Height = 80 };
DefaultRowsPerPage = 12;
SmtpFromAddress = "noreply@roadie.rocks";
SmtpPort = 587;

View file

@ -13,6 +13,22 @@ namespace Roadie.Library.Data
[Column("name")] [MaxLength(100)] public string Name { get; set; }
[Column("description")]
[MaxLength(4000)]
public string Description { get; set; }
[Column("alternateNames", TypeName = "text")]
[MaxLength(65535)]
public string AlternateNames { get; set; }
[Column("tags", TypeName = "text")]
[MaxLength(65535)]
public string Tags { get; set; }
[Column("thumbnail", TypeName = "blob")]
[MaxLength(65535)]
public byte[] Thumbnail { get; set; }
[Column("normalizedName")] [MaxLength(100)] public string NormalizedName { get; set; }
public ICollection<ReleaseGenre> Releases { get; set; }
@ -22,6 +38,7 @@ namespace Roadie.Library.Data
Releases = new HashSet<ReleaseGenre>();
Artists = new HashSet<ArtistGenre>();
Comments = new HashSet<Comment>();
Status = Enums.Statuses.Ok;
}
}
}

View file

@ -1,4 +1,7 @@
using System;
using Roadie.Library.Configuration;
using Roadie.Library.Extensions;
using System;
using System.IO;
namespace Roadie.Library.Data
{
@ -8,6 +11,14 @@ namespace Roadie.Library.Data
public string CacheRegion => CacheRegionUrn(RoadieId);
/// <summary>
/// Returns a full file path to the Genre Image
/// </summary>
public string PathToImage(IRoadieSettings configuration)
{
return Path.Combine(configuration.GenreImageFolder, $"{ Name.ToFileNameFriendly() } [{ Id }].jpg");
}
public static string CacheRegionUrn(Guid Id)
{
return string.Format("urn:genre:{0}", Id);

View file

@ -118,6 +118,9 @@ namespace Roadie.Library.Identity
[StringLength(50)]
public string Timezone { get; set; }
[Column("defaultRowsPerPage")]
public short? DefaultRowsPerPage { get; set; }
public ICollection<UserTrack> TrackRatings { get; set; }
public ICollection<UserQue> UserQues { get; set; }

View file

@ -11,6 +11,7 @@ namespace Roadie.Library.Imaging
private Image _artist;
private Image _collection;
private Image _label;
private Image _genre;
private Image _playlist;
private Image _release;
private Image _track;
@ -23,6 +24,8 @@ namespace Roadie.Library.Imaging
public Image Label => _label ?? (_label = MakeImageFromFile(MakeImagePath(@"images/label.jpg")));
public Image Genre => _genre ?? (_genre = MakeImageFromFile(MakeImagePath(@"images/genre.jpg")));
public Image Playlist => _playlist ?? (_playlist = MakeImageFromFile(MakeImagePath(@"images/playlist.jpg")));
public Image Release => _release ?? (_release = MakeImageFromFile(MakeImagePath(@"images/release.jpg")));

View file

@ -7,6 +7,7 @@ namespace Roadie.Library.Imaging
Image Artist { get; }
Image Collection { get; }
Image Label { get; }
Image Genre { get; }
Image Playlist { get; }
Image Release { get; }
Image Track { get; }

View file

@ -93,7 +93,7 @@ namespace Roadie.Library.Models
set => _urlsList = value;
}
public bool UserBookmarked { get; set; }
public bool? UserBookmarked { get; set; }
public EntityModelBase()
{

View file

@ -1,4 +1,5 @@
using System;
using Roadie.Library.Models.Statistics;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
@ -7,7 +8,26 @@ namespace Roadie.Library.Models
[Serializable]
public class Genre : EntityModelBase
{
public const string DefaultIncludes = "stats";
public Image Thumbnail { get; set; }
[MaxLength(4000)] public string Description { get; set; }
public IEnumerable<Comment> Comments { get; set; }
[MaxLength(100)] public string Name { get; set; }
[MaxLength(100)] public string NormalizedName { get; set; }
public ReleaseGroupingStatistics Statistics { get; set; }
public static string CacheRegionUrn(Guid Id)
{
return string.Format("urn:genre:{0}", Id);
}
public static string CacheUrn(Guid Id)
{
return $"urn:genre_by_id:{Id}";
}
public Image MediumThumbnail { get; set; }
}
}

View file

@ -8,5 +8,6 @@ namespace Roadie.Library.Models
public int? ArtistCount { get; set; }
public DataToken Genre { get; set; }
public int? ReleaseCount { get; set; }
public Image Thumbnail { get; set; }
}
}

View file

@ -34,6 +34,7 @@ namespace Roadie.Library.Models.Pagination
public Statuses FilterToStatusValue => SafeParser.ToEnum<Statuses>(FilterToStatus);
public Guid? FilterToTrackId { get; set; }
public Guid?[] FilterToTrackIds { get; set; }
public Guid? FilterToGenreId { get; set; }
public int? FilterToYear { get; set; }
public string FilterValue => Filter ?? string.Empty;
public bool IsHistoryRequest { get; set; }

View file

@ -15,6 +15,8 @@ namespace Roadie.Library.Models.Statistics
public ArtistList MostPlayedArtist { get; set; }
public ReleaseList MostPlayedRelease { get; set; }
public TrackList MostPlayedTrack { get; set; }
public TrackList LastPlayedTrack { get; set; }
public int? PlayedTracks { get; set; }
public int? RatedArtists { get; set; }
public int? RatedReleases { get; set; }

View file

@ -7,7 +7,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace Roadie.Library.Models.Users
{
[Serializable]
public class User
public class User : EntityModelBase
{
public const string ActionKeyUserRated = "__userrated__";
public const string DefaultIncludes = "stats";
@ -69,6 +69,10 @@ namespace Roadie.Library.Models.Users
[Required] [MaxLength(20)] public string UserName { get; set; }
public Image MediumThumbnail { get; set; }
public DateTime LastLogin { get; set; }
public DateTime LastApiAccess { get; set; }
public short? DefaultRowsPerPage { get; set; }
public override string ToString()
{
return $"Id [{Id}], RoadieId [{UserId}], UserName [{UserName}]";

View file

@ -9,6 +9,7 @@ namespace Roadie.Library.Models.Users
public class UserList : EntityInfoModelBase
{
public bool IsEditor { get; set; }
public bool IsAdmin { get; set; }
public bool? IsPrivate { get; set; }
public DateTime? LastActivity { get; set; }
public DateTime? LastApiAccessDate { get; set; }
@ -31,13 +32,15 @@ namespace Roadie.Library.Models.Users
Value = user.RoadieId.ToString()
},
IsEditor = user.UserRoles.Any(x => x.Role.Name == "Editor"),
IsAdmin = user.UserRoles.Any(x => x.Role.Name == "Admin"),
IsPrivate = user.IsPrivate,
Thumbnail = thumbnail,
CreatedDate = user.CreatedDate,
LastUpdated = user.LastUpdated,
RegisteredDate = user.RegisteredOn,
LastLoginDate = user.LastLogin,
LastApiAccessDate = user.LastApiAccess
LastApiAccessDate = user.LastApiAccess,
Statistics = new UserStatistics()
};
}
}

View file

@ -42,11 +42,12 @@ namespace Roadie.Api.Services
private IReleaseService ReleaseService { get; }
private ILabelService LabelService { get; }
private IGenreService GenreService { get; }
public AdminService(IRoadieSettings configuration, IHttpEncoder httpEncoder, IHttpContext httpContext,
data.IRoadieDbContext context, ICacheManager cacheManager, ILogger<ArtistService> logger,
IHubContext<ScanActivityHub> scanActivityHub, IFileDirectoryProcessorService fileDirectoryProcessorService, IArtistService artistService,
IReleaseService releaseService, IReleaseLookupEngine releaseLookupEngine, ILabelService labelService
IReleaseService releaseService, IReleaseLookupEngine releaseLookupEngine, ILabelService labelService, IGenreService genreService
)
: base(configuration, httpEncoder, context, cacheManager, logger, httpContext)
{
@ -57,6 +58,7 @@ namespace Roadie.Api.Services
ArtistService = artistService;
ReleaseService = releaseService;
LabelService = labelService;
GenreService = genreService;
ReleaseLookupEngine = releaseLookupEngine;
FileDirectoryProcessorService = fileDirectoryProcessorService;
}
@ -158,6 +160,11 @@ namespace Roadie.Api.Services
Directory.CreateDirectory(Configuration.UserImageFolder);
Logger.LogInformation($"Created User Image Folder [{Configuration.UserImageFolder }]");
}
if (!Directory.Exists(Configuration.GenreImageFolder))
{
Directory.CreateDirectory(Configuration.GenreImageFolder);
Logger.LogInformation($"Created Genre Image Folder [{Configuration.GenreImageFolder }]");
}
if (!Directory.Exists(Configuration.PlaylistImageFolder))
{
Directory.CreateDirectory(Configuration.PlaylistImageFolder);
@ -264,7 +271,7 @@ namespace Roadie.Api.Services
catch (Exception ex)
{
Logger.LogError(ex);
await LogAndPublish("Error deleting artist secondary image.");
await LogAndPublish("Error deleting Label.");
errors.Add(ex);
}
@ -279,6 +286,42 @@ namespace Roadie.Api.Services
};
}
public async Task<OperationResult<bool>> DeleteGenre(ApplicationUser user, Guid genreId)
{
var sw = new Stopwatch();
sw.Start();
var errors = new List<Exception>();
var genre = DbContext.Genres.FirstOrDefault(x => x.RoadieId == genreId);
if (genre == null)
{
await LogAndPublish($"DeleteLabel Unknown Genre [{genreId}]", LogLevel.Warning);
return new OperationResult<bool>(true, $"Genre Not Found [{genreId}]");
}
try
{
await GenreService.Delete(user, genreId);
CacheManager.ClearRegion(genre.CacheRegion);
}
catch (Exception ex)
{
Logger.LogError(ex);
await LogAndPublish("Error deleting Genre.");
errors.Add(ex);
}
sw.Stop();
await LogAndPublish($"DeleteGenre `{genre}`, By User `{user}`", LogLevel.Information);
return new OperationResult<bool>
{
IsSuccess = !errors.Any(),
Data = true,
OperationTime = sw.ElapsedMilliseconds,
Errors = errors
};
}
public async Task<OperationResult<bool>> DeleteArtistSecondaryImage(ApplicationUser user, Guid artistId, int index)
{
var sw = new Stopwatch();

View file

@ -21,7 +21,6 @@ using Roadie.Library.Models.Pagination;
using Roadie.Library.Models.Releases;
using Roadie.Library.Models.Statistics;
using Roadie.Library.Models.Users;
using Roadie.Library.Processors;
using Roadie.Library.Utility;
using System;
using System.Collections.Generic;
@ -45,6 +44,7 @@ namespace Roadie.Api.Services
private ICollectionService CollectionService { get; }
private IFileDirectoryProcessorService FileDirectoryProcessorService { get; }
private IFileNameHelper FileNameHelper { get; }
private IID3TagsHelper ID3TagsHelper { get; }
@ -61,9 +61,7 @@ namespace Roadie.Api.Services
private IReleaseService ReleaseService { get; }
private IFileDirectoryProcessorService FileDirectoryProcessorService { get; }
public ArtistService(IRoadieSettings configuration,
public ArtistService(IRoadieSettings configuration,
IHttpEncoder httpEncoder,
IHttpContext httpContext,
data.IRoadieDbContext dbContext,
@ -214,65 +212,52 @@ namespace Roadie.Api.Services
};
}
private OperationResult<data.Artist> GetByExternalIds(string musicBrainzId = null, string iTunesId = null, string amgId = null, string spotifyId = null)
{
var sw = new Stopwatch();
sw.Start();
var artist = (from a in DbContext.Artists
where a.MusicBrainzId != null && musicBrainzId != null && a.MusicBrainzId == musicBrainzId ||
a.ITunesId != null || iTunesId != null && a.ITunesId == iTunesId || a.AmgId != null ||
amgId != null && a.AmgId == amgId || a.SpotifyId != null ||
spotifyId != null && a.SpotifyId == spotifyId
select a).FirstOrDefault();
sw.Stop();
if (artist == null || !artist.IsValid)
Logger.LogTrace(
"ArtistFactory: Artist Not Found By External Ids: MusicbrainzId [{0}], iTunesIs [{1}], AmgId [{2}], SpotifyId [{3}]",
musicBrainzId, iTunesId, amgId, spotifyId);
return new OperationResult<data.Artist>
{
IsSuccess = artist != null,
OperationTime = sw.ElapsedMilliseconds,
Data = artist
};
}
public async Task<Library.Models.Pagination.PagedResult<ArtistList>> List(User roadieUser, PagedRequest request,
bool? doRandomize = false, bool? onlyIncludeWithReleases = true)
public async Task<Library.Models.Pagination.PagedResult<ArtistList>> List(User roadieUser, PagedRequest request, bool? doRandomize = false, bool? onlyIncludeWithReleases = true)
{
var sw = new Stopwatch();
sw.Start();
IQueryable<int> favoriteArtistIds = null;
if (request.FilterFavoriteOnly)
{
favoriteArtistIds = from a in DbContext.Artists
join ua in DbContext.UserArtists on a.Id equals ua.ArtistId
where ua.IsFavorite ?? false
where roadieUser == null || ua.UserId == roadieUser.Id
select a.Id;
}
IQueryable<int> labelArtistIds = null;
if (request.FilterToLabelId.HasValue)
{
labelArtistIds = (from l in DbContext.Labels
join rl in DbContext.ReleaseLabels on l.Id equals rl.LabelId
join r in DbContext.Releases on rl.ReleaseId equals r.Id
where l.RoadieId == request.FilterToLabelId
select r.ArtistId)
.Distinct();
.Distinct();
}
IQueryable<int> genreArtistIds = null;
var isFilteredToGenre = false;
if (!string.IsNullOrEmpty(request.Filter) &&
request.Filter.StartsWith(":genre", StringComparison.OrdinalIgnoreCase))
if(request.FilterToGenreId.HasValue)
{
genreArtistIds = (from ag in DbContext.ArtistGenres
join g in DbContext.Genres on ag.GenreId equals g.Id
where g.RoadieId == request.FilterToGenreId
select ag.ArtistId)
.Distinct();
isFilteredToGenre = true;
}
else if (!string.IsNullOrEmpty(request.Filter) && request.Filter.StartsWith(":genre", StringComparison.OrdinalIgnoreCase))
{
var genreFilter = request.Filter.Replace(":genre ", "");
genreArtistIds = (from ag in DbContext.ArtistGenres
join g in DbContext.Genres on ag.GenreId equals g.Id
where g.Name.Contains(genreFilter)
select ag.ArtistId)
.Distinct();
.Distinct();
isFilteredToGenre = true;
request.Filter = null;
}
var onlyWithReleases = onlyIncludeWithReleases ?? true;
var isEqualFilter = false;
if (!string.IsNullOrEmpty(request.FilterValue))
@ -285,7 +270,6 @@ namespace Roadie.Api.Services
request.Filter = filter.Substring(1, filter.Length - 2);
}
}
var normalizedFilterValue = !string.IsNullOrEmpty(request.FilterValue)
? request.FilterValue.ToAlphanumericName()
: null;
@ -293,8 +277,18 @@ namespace Roadie.Api.Services
where !onlyWithReleases || a.ReleaseCount > 0
where request.FilterToArtistId == null || a.RoadieId == request.FilterToArtistId
where request.FilterMinimumRating == null || a.Rating >= request.FilterMinimumRating.Value
where request.FilterValue == "" || a.Name.Contains(request.FilterValue) || a.SortName.Contains(request.FilterValue) || a.AlternateNames.Contains(request.FilterValue) || a.AlternateNames.Contains(normalizedFilterValue)
where !isEqualFilter || a.Name.Equals(request.FilterValue) || a.SortName.Equals(request.FilterValue) || a.AlternateNames.Equals(request.FilterValue) || a.AlternateNames.Equals(normalizedFilterValue)
where request.FilterValue == "" ||
a.Name.Contains(request.FilterValue) ||
a.SortName.Contains(request.FilterValue) ||
a.RealName.Contains(request.FilterValue) ||
a.AlternateNames.Contains(request.FilterValue) ||
a.AlternateNames.Contains(normalizedFilterValue)
where !isEqualFilter ||
a.Name.Equals(request.FilterValue) ||
a.SortName.Equals(request.FilterValue) ||
a.RealName.Equals(request.FilterValue) ||
a.AlternateNames.Equals(request.FilterValue) ||
a.AlternateNames.Equals(normalizedFilterValue)
where !request.FilterFavoriteOnly || favoriteArtistIds.Contains(a.Id)
where request.FilterToLabelId == null || labelArtistIds.Contains(a.Id)
where !isFilteredToGenre || genreArtistIds.Contains(a.Id)
@ -323,22 +317,24 @@ namespace Roadie.Api.Services
var rowCount = result.Count();
if (doRandomize ?? false)
{
var randomLimit = roadieUser?.RandomReleaseLimit ?? 100;
var randomLimit = roadieUser?.RandomReleaseLimit ?? request.Limit;
request.Limit = request.LimitValue > randomLimit ? randomLimit : request.LimitValue;
rows = result.OrderBy(x => x.RandomSortId).Skip(request.SkipValue).Take(request.LimitValue).ToArray();
rows = result.OrderBy(x => x.RandomSortId)
.Take(request.LimitValue)
.ToArray();
}
else
{
string sortBy;
if (request.ActionValue == User.ActionKeyUserRated)
{
sortBy = string.IsNullOrEmpty(request.Sort)
sortBy = string.IsNullOrEmpty(request.Sort)
? request.OrderValue(new Dictionary<string, string> { { "Rating", "DESC" }, { "Artist.Text", "ASC" } })
: request.OrderValue();
}
else
{
sortBy = request.OrderValue(new Dictionary<string, string> {{"SortName", "ASC"}, {"Artist.Text", "ASC"}});
sortBy = request.OrderValue(new Dictionary<string, string> { { "SortName", "ASC" }, { "Artist.Text", "ASC" } });
}
rows = result.OrderBy(sortBy).Skip(request.SkipValue).Take(request.LimitValue).ToArray();
}
@ -351,11 +347,11 @@ namespace Roadie.Api.Services
where rowIds.Contains(ua.ArtistId)
select ua).ToArray();
foreach (var userArtistRating in userArtistRatings.Where(x =>
rows.Select(r => r.DatabaseId).Contains(x.ArtistId)))
foreach (var userArtistRating in userArtistRatings.Where(x => rows.Select(r => r.DatabaseId).Contains(x.ArtistId)))
{
var row = rows.FirstOrDefault(x => x.DatabaseId == userArtistRating.ArtistId);
if (row != null)
{
row.UserRating = new UserArtist
{
IsDisliked = userArtistRating.IsDisliked ?? false,
@ -363,11 +359,11 @@ namespace Roadie.Api.Services
Rating = userArtistRating.Rating,
RatedDate = userArtistRating.LastUpdated ?? userArtistRating.CreatedDate
};
}
}
}
sw.Stop();
if (!string.IsNullOrEmpty(request.Filter) && rowCount == 0)
{
if (Configuration.RecordNoResultSearches)
{
// Create request for no artist found
@ -379,7 +375,8 @@ namespace Roadie.Api.Services
DbContext.Requests.Add(req);
await DbContext.SaveChangesAsync();
}
}
sw.Stop();
return new Library.Models.Pagination.PagedResult<ArtistList>
{
TotalCount = rowCount,
@ -450,179 +447,6 @@ namespace Roadie.Api.Services
};
}
async Task<OperationResult<data.Artist>> MergeArtists(ApplicationUser user, data.Artist artistToMerge, data.Artist artistToMergeInto)
{
SimpleContract.Requires<ArgumentNullException>(artistToMerge != null, "Invalid Artist");
SimpleContract.Requires<ArgumentNullException>(artistToMergeInto != null, "Invalid Artist");
var result = false;
var now = DateTime.UtcNow;
var sw = new Stopwatch();
sw.Start();
var artistToMergeFolder = artistToMerge.ArtistFileFolder(Configuration);
var artistToMergeIntoFolder = artistToMergeInto.ArtistFileFolder(Configuration);
artistToMergeInto.RealName = artistToMergeInto.RealName ?? artistToMerge.RealName;
artistToMergeInto.MusicBrainzId = artistToMergeInto.MusicBrainzId ?? artistToMerge.MusicBrainzId;
artistToMergeInto.ITunesId = artistToMergeInto.ITunesId ?? artistToMerge.ITunesId;
artistToMergeInto.AmgId = artistToMergeInto.AmgId ?? artistToMerge.AmgId;
artistToMergeInto.SpotifyId = artistToMergeInto.SpotifyId ?? artistToMerge.SpotifyId;
artistToMergeInto.Thumbnail = artistToMergeInto.Thumbnail ?? artistToMerge.Thumbnail;
artistToMergeInto.Profile = artistToMergeInto.Profile ?? artistToMerge.Profile;
artistToMergeInto.BirthDate = artistToMergeInto.BirthDate ?? artistToMerge.BirthDate;
artistToMergeInto.BeginDate = artistToMergeInto.BeginDate ?? artistToMerge.BeginDate;
artistToMergeInto.EndDate = artistToMergeInto.EndDate ?? artistToMerge.EndDate;
if (!string.IsNullOrEmpty(artistToMerge.ArtistType) && !artistToMerge.ArtistType.Equals("Other", StringComparison.OrdinalIgnoreCase))
{
artistToMergeInto.ArtistType = artistToMergeInto.ArtistType ?? artistToMerge.ArtistType;
}
artistToMergeInto.BioContext = artistToMergeInto.BioContext ?? artistToMerge.BioContext;
artistToMergeInto.DiscogsId = artistToMergeInto.DiscogsId ?? artistToMerge.DiscogsId;
artistToMergeInto.Tags = artistToMergeInto.Tags.AddToDelimitedList(artistToMerge.Tags.ToListFromDelimited());
var altNames = artistToMerge.AlternateNames.ToListFromDelimited().ToList();
altNames.Add(artistToMerge.Name);
altNames.Add(artistToMerge.SortName);
artistToMergeInto.AlternateNames = artistToMergeInto.AlternateNames.AddToDelimitedList(altNames);
artistToMergeInto.URLs = artistToMergeInto.URLs.AddToDelimitedList(artistToMerge.URLs.ToListFromDelimited());
artistToMergeInto.ISNI = artistToMergeInto.ISNI.AddToDelimitedList(artistToMerge.ISNI.ToListFromDelimited());
artistToMergeInto.LastUpdated = now;
try
{
var artistGenres = DbContext.ArtistGenres.Where(x => x.ArtistId == artistToMerge.Id).ToArray();
if (artistGenres != null)
{
var existingArtistGenres = DbContext.ArtistGenres.Where(x => x.ArtistId == artistToMergeInto.Id).ToArray();
foreach (var artistGenre in artistGenres)
{
var existing = existingArtistGenres.FirstOrDefault(x => x.GenreId == artistGenre.GenreId);
// If not exist then add new for artist to merge into
if (existing == null)
{
DbContext.ArtistGenres.Add(new data.ArtistGenre
{
ArtistId = artistToMergeInto.Id,
GenreId = artistGenre.GenreId
});
}
}
}
var artistImages = DbContext.Images.Where(x => x.ArtistId == artistToMerge.Id).ToArray();
if (artistImages != null)
{
foreach (var artistImage in artistImages)
{
artistImage.ArtistId = artistToMergeInto.Id;
}
}
try
{
// Move any Artist and Artist Secondary images from ArtistToMerge into ArtistToMergeInto folder
if (Directory.Exists(artistToMergeFolder))
{
var artistToMergeImages = ImageHelper.FindImageTypeInDirectory(new DirectoryInfo(artistToMergeFolder), ImageType.Artist);
var artistToMergeSecondaryImages = ImageHelper.FindImageTypeInDirectory(new DirectoryInfo(artistToMergeFolder), ImageType.ArtistSecondary).ToList();
// Primary Artist image
if (artistToMergeImages.Any())
{
// If the ArtistToMergeInto already has a primary image then the ArtistToMerge primary image becomes a secondary image
var artistToMergeIntoPrimaryImage = ImageHelper.FindImageTypeInDirectory(new DirectoryInfo(artistToMergeIntoFolder), ImageType.Artist).FirstOrDefault();
if (artistToMergeIntoPrimaryImage != null)
{
artistToMergeSecondaryImages.Add(artistToMergeImages.First());
}
else
{
var artistImageFilename = Path.Combine(artistToMergeIntoFolder, ImageHelper.ArtistImageFilename);
artistToMergeImages.First().MoveTo(artistImageFilename);
}
}
// Secondary Artist images
if (artistToMergeSecondaryImages.Any())
{
var looper = 0;
foreach (var artistSecondaryImage in artistToMergeSecondaryImages)
{
var artistImageFilename = Path.Combine(artistToMergeIntoFolder, string.Format(ImageHelper.ArtistSecondaryImageFilename, looper.ToString("00")));
while (File.Exists(artistImageFilename))
{
looper++;
artistImageFilename = Path.Combine(artistToMergeIntoFolder, string.Format(ImageHelper.ArtistSecondaryImageFilename, looper.ToString("00")));
}
artistSecondaryImage.MoveTo(artistImageFilename);
}
}
}
}
catch (Exception ex)
{
Logger.LogError(ex, "MergeArtists: Error Moving Artist Primary and Secondary Images");
}
var userArtists = DbContext.UserArtists.Where(x => x.ArtistId == artistToMerge.Id).ToArray();
if (artistImages != null)
{
foreach (var userArtist in userArtists)
{
userArtist.ArtistId = artistToMergeInto.Id;
}
}
var artistTracks = DbContext.Tracks.Where(x => x.ArtistId == artistToMerge.Id).ToArray();
if (artistTracks != null)
{
foreach (var artistTrack in artistTracks)
{
artistTrack.ArtistId = artistToMergeInto.Id;
}
}
var artistReleases = DbContext.Releases.Where(x => x.ArtistId == artistToMerge.Id).ToArray();
if (artistReleases != null)
{
foreach (var artistRelease in artistReleases)
{
// See if there is already a release by the same name for the artist to merge into, if so then merge releases
var artistToMergeHasRelease = DbContext.Releases.FirstOrDefault(x => x.ArtistId == artistToMerge.Id && x.Title == artistRelease.Title);
if (artistToMergeHasRelease != null)
{
await ReleaseService.MergeReleases(user, artistRelease, artistToMergeHasRelease, false);
}
else
{
artistRelease.ArtistId = artistToMerge.Id;
}
}
}
}
catch (Exception ex)
{
Logger.LogWarning(ex.ToString());
}
foreach (var release in DbContext.Releases.Include("Artist").Where(x => x.ArtistId == artistToMerge.Id).ToArray())
{
var originalReleaseFolder = release.ReleaseFileFolder(artistToMergeFolder);
await ReleaseService.UpdateRelease(user, release.Adapt<Release>(), originalReleaseFolder);
}
await DbContext.SaveChangesAsync();
await Delete(user, artistToMerge);
result = true;
sw.Stop();
return new OperationResult<data.Artist>
{
Data = artistToMergeInto,
IsSuccess = result,
OperationTime = sw.ElapsedMilliseconds
};
}
public async Task<OperationResult<bool>> RefreshArtistMetadata(ApplicationUser user, Guid artistId)
{
SimpleContract.Requires<ArgumentOutOfRangeException>(artistId != Guid.Empty, "Invalid ArtistId");
@ -687,6 +511,97 @@ namespace Roadie.Api.Services
};
}
public async Task<OperationResult<bool>> ScanArtistReleasesFolders(ApplicationUser user, Guid artistId, string destinationFolder, bool doJustInfo)
{
SimpleContract.Requires<ArgumentOutOfRangeException>(artistId != Guid.Empty, "Invalid ArtistId");
var result = true;
var resultErrors = new List<Exception>();
var sw = new Stopwatch();
sw.Start();
try
{
var artist = DbContext.Artists
.Include("Releases")
.Include("Releases.Labels")
.FirstOrDefault(x => x.RoadieId == artistId);
if (artist == null)
{
Logger.LogWarning("Unable To Find Artist [{0}]", artistId);
return new OperationResult<bool>();
}
var releaseScannedCount = 0;
var artistFolder = artist.ArtistFileFolder(Configuration);
if (!Directory.Exists(artistFolder))
{
Logger.LogDebug($"ScanArtistReleasesFolders: ArtistFolder Not Found [{ artistFolder }] For Artist `{ artist }`");
return new OperationResult<bool>();
}
var scannedArtistFolders = new List<string>();
// Scan known releases for changes
if (artist.Releases != null)
{
foreach (var release in artist.Releases)
try
{
result = result && (await ReleaseService.ScanReleaseFolder(user, Guid.Empty, doJustInfo, release)).Data;
releaseScannedCount++;
scannedArtistFolders.Add(release.ReleaseFileFolder(artistFolder));
}
catch (Exception ex)
{
Logger.LogError(ex, ex.Serialize());
}
}
// Any folder found in Artist folder not already scanned scan
var nonReleaseFolders = from d in Directory.EnumerateDirectories(artistFolder)
where !(from r in scannedArtistFolders select r).Contains(d)
orderby d
select d;
foreach (var folder in nonReleaseFolders)
{
await FileDirectoryProcessorService.Process(user, new DirectoryInfo(folder), doJustInfo);
}
if (!doJustInfo)
{
Services.FileDirectoryProcessorService.DeleteEmptyFolders(new DirectoryInfo(artistFolder), Logger);
}
// Always update artist image if artist image is found on an artist rescan
var imageFiles = ImageHelper.ImageFilesInFolder(artistFolder, SearchOption.AllDirectories);
if (imageFiles != null && imageFiles.Any())
{
var i = new FileInfo(imageFiles.First());
var iName = i.Name.ToLower().Trim();
if (ImageHelper.IsArtistImage(i))
{
// Read image and convert to jpeg
artist.Thumbnail = ImageHelper.ResizeToThumbnail(File.ReadAllBytes(i.FullName), Configuration);
artist.LastUpdated = DateTime.UtcNow;
await DbContext.SaveChangesAsync();
CacheManager.ClearRegion(artist.CacheRegion);
Logger.LogInformation("Update Thumbnail using Artist File [{0}]", iName);
}
}
sw.Stop();
CacheManager.ClearRegion(artist.CacheRegion);
Logger.LogInformation("Scanned Artist [{0}], Releases Scanned [{1}], OperationTime [{2}]", artist.ToString(), releaseScannedCount, sw.ElapsedMilliseconds);
}
catch (Exception ex)
{
Logger.LogError(ex, ex.Serialize());
resultErrors.Add(ex);
}
return new OperationResult<bool>
{
Data = result,
IsSuccess = result,
Errors = resultErrors,
OperationTime = sw.ElapsedMilliseconds
};
}
public async Task<OperationResult<Image>> SetReleaseImageByUrl(ApplicationUser user, Guid id, string imageUrl)
{
return await SaveImageBytes(user, id, WebHelper.BytesForImageUrl(imageUrl));
@ -773,11 +688,11 @@ namespace Roadie.Api.Services
// Ensure is jpeg first
artistSecondaryImage = ImageHelper.ConvertToJpegFormat(artistSecondaryImage);
var artistImageFilename = Path.Combine(newArtistFolder,string.Format(ImageHelper.ArtistSecondaryImageFilename, looper.ToString("00")));
var artistImageFilename = Path.Combine(newArtistFolder, string.Format(ImageHelper.ArtistSecondaryImageFilename, looper.ToString("00")));
while (File.Exists(artistImageFilename))
{
looper++;
artistImageFilename = Path.Combine(newArtistFolder,string.Format(ImageHelper.ArtistSecondaryImageFilename, looper.ToString("00")));
artistImageFilename = Path.Combine(newArtistFolder, string.Format(ImageHelper.ArtistSecondaryImageFilename, looper.ToString("00")));
}
File.WriteAllBytes(artistImageFilename, artistSecondaryImage);
@ -1317,6 +1232,200 @@ namespace Roadie.Api.Services
};
}
private OperationResult<data.Artist> GetByExternalIds(string musicBrainzId = null, string iTunesId = null, string amgId = null, string spotifyId = null)
{
var sw = new Stopwatch();
sw.Start();
var artist = (from a in DbContext.Artists
where a.MusicBrainzId != null && musicBrainzId != null && a.MusicBrainzId == musicBrainzId ||
a.ITunesId != null || iTunesId != null && a.ITunesId == iTunesId || a.AmgId != null ||
amgId != null && a.AmgId == amgId || a.SpotifyId != null ||
spotifyId != null && a.SpotifyId == spotifyId
select a).FirstOrDefault();
sw.Stop();
if (artist == null || !artist.IsValid)
Logger.LogTrace(
"ArtistFactory: Artist Not Found By External Ids: MusicbrainzId [{0}], iTunesIs [{1}], AmgId [{2}], SpotifyId [{3}]",
musicBrainzId, iTunesId, amgId, spotifyId);
return new OperationResult<data.Artist>
{
IsSuccess = artist != null,
OperationTime = sw.ElapsedMilliseconds,
Data = artist
};
}
private async Task<OperationResult<data.Artist>> MergeArtists(ApplicationUser user, data.Artist artistToMerge, data.Artist artistToMergeInto)
{
SimpleContract.Requires<ArgumentNullException>(artistToMerge != null, "Invalid Artist");
SimpleContract.Requires<ArgumentNullException>(artistToMergeInto != null, "Invalid Artist");
var result = false;
var now = DateTime.UtcNow;
var sw = new Stopwatch();
sw.Start();
var artistToMergeFolder = artistToMerge.ArtistFileFolder(Configuration);
var artistToMergeIntoFolder = artistToMergeInto.ArtistFileFolder(Configuration);
artistToMergeInto.RealName = artistToMergeInto.RealName ?? artistToMerge.RealName;
artistToMergeInto.MusicBrainzId = artistToMergeInto.MusicBrainzId ?? artistToMerge.MusicBrainzId;
artistToMergeInto.ITunesId = artistToMergeInto.ITunesId ?? artistToMerge.ITunesId;
artistToMergeInto.AmgId = artistToMergeInto.AmgId ?? artistToMerge.AmgId;
artistToMergeInto.SpotifyId = artistToMergeInto.SpotifyId ?? artistToMerge.SpotifyId;
artistToMergeInto.Thumbnail = artistToMergeInto.Thumbnail ?? artistToMerge.Thumbnail;
artistToMergeInto.Profile = artistToMergeInto.Profile ?? artistToMerge.Profile;
artistToMergeInto.BirthDate = artistToMergeInto.BirthDate ?? artistToMerge.BirthDate;
artistToMergeInto.BeginDate = artistToMergeInto.BeginDate ?? artistToMerge.BeginDate;
artistToMergeInto.EndDate = artistToMergeInto.EndDate ?? artistToMerge.EndDate;
if (!string.IsNullOrEmpty(artistToMerge.ArtistType) && !artistToMerge.ArtistType.Equals("Other", StringComparison.OrdinalIgnoreCase))
{
artistToMergeInto.ArtistType = artistToMergeInto.ArtistType ?? artistToMerge.ArtistType;
}
artistToMergeInto.BioContext = artistToMergeInto.BioContext ?? artistToMerge.BioContext;
artistToMergeInto.DiscogsId = artistToMergeInto.DiscogsId ?? artistToMerge.DiscogsId;
artistToMergeInto.Tags = artistToMergeInto.Tags.AddToDelimitedList(artistToMerge.Tags.ToListFromDelimited());
var altNames = artistToMerge.AlternateNames.ToListFromDelimited().ToList();
altNames.Add(artistToMerge.Name);
altNames.Add(artistToMerge.SortName);
artistToMergeInto.AlternateNames = artistToMergeInto.AlternateNames.AddToDelimitedList(altNames);
artistToMergeInto.URLs = artistToMergeInto.URLs.AddToDelimitedList(artistToMerge.URLs.ToListFromDelimited());
artistToMergeInto.ISNI = artistToMergeInto.ISNI.AddToDelimitedList(artistToMerge.ISNI.ToListFromDelimited());
artistToMergeInto.LastUpdated = now;
try
{
var artistGenres = DbContext.ArtistGenres.Where(x => x.ArtistId == artistToMerge.Id).ToArray();
if (artistGenres != null)
{
var existingArtistGenres = DbContext.ArtistGenres.Where(x => x.ArtistId == artistToMergeInto.Id).ToArray();
foreach (var artistGenre in artistGenres)
{
var existing = existingArtistGenres.FirstOrDefault(x => x.GenreId == artistGenre.GenreId);
// If not exist then add new for artist to merge into
if (existing == null)
{
DbContext.ArtistGenres.Add(new data.ArtistGenre
{
ArtistId = artistToMergeInto.Id,
GenreId = artistGenre.GenreId
});
}
}
}
var artistImages = DbContext.Images.Where(x => x.ArtistId == artistToMerge.Id).ToArray();
if (artistImages != null)
{
foreach (var artistImage in artistImages)
{
artistImage.ArtistId = artistToMergeInto.Id;
}
}
try
{
// Move any Artist and Artist Secondary images from ArtistToMerge into ArtistToMergeInto folder
if (Directory.Exists(artistToMergeFolder))
{
var artistToMergeImages = ImageHelper.FindImageTypeInDirectory(new DirectoryInfo(artistToMergeFolder), ImageType.Artist);
var artistToMergeSecondaryImages = ImageHelper.FindImageTypeInDirectory(new DirectoryInfo(artistToMergeFolder), ImageType.ArtistSecondary).ToList();
// Primary Artist image
if (artistToMergeImages.Any())
{
// If the ArtistToMergeInto already has a primary image then the ArtistToMerge primary image becomes a secondary image
var artistToMergeIntoPrimaryImage = ImageHelper.FindImageTypeInDirectory(new DirectoryInfo(artistToMergeIntoFolder), ImageType.Artist).FirstOrDefault();
if (artistToMergeIntoPrimaryImage != null)
{
artistToMergeSecondaryImages.Add(artistToMergeImages.First());
}
else
{
var artistImageFilename = Path.Combine(artistToMergeIntoFolder, ImageHelper.ArtistImageFilename);
artistToMergeImages.First().MoveTo(artistImageFilename);
}
}
// Secondary Artist images
if (artistToMergeSecondaryImages.Any())
{
var looper = 0;
foreach (var artistSecondaryImage in artistToMergeSecondaryImages)
{
var artistImageFilename = Path.Combine(artistToMergeIntoFolder, string.Format(ImageHelper.ArtistSecondaryImageFilename, looper.ToString("00")));
while (File.Exists(artistImageFilename))
{
looper++;
artistImageFilename = Path.Combine(artistToMergeIntoFolder, string.Format(ImageHelper.ArtistSecondaryImageFilename, looper.ToString("00")));
}
artistSecondaryImage.MoveTo(artistImageFilename);
}
}
}
}
catch (Exception ex)
{
Logger.LogError(ex, "MergeArtists: Error Moving Artist Primary and Secondary Images");
}
var userArtists = DbContext.UserArtists.Where(x => x.ArtistId == artistToMerge.Id).ToArray();
if (artistImages != null)
{
foreach (var userArtist in userArtists)
{
userArtist.ArtistId = artistToMergeInto.Id;
}
}
var artistTracks = DbContext.Tracks.Where(x => x.ArtistId == artistToMerge.Id).ToArray();
if (artistTracks != null)
{
foreach (var artistTrack in artistTracks)
{
artistTrack.ArtistId = artistToMergeInto.Id;
}
}
var artistReleases = DbContext.Releases.Where(x => x.ArtistId == artistToMerge.Id).ToArray();
if (artistReleases != null)
{
foreach (var artistRelease in artistReleases)
{
// See if there is already a release by the same name for the artist to merge into, if so then merge releases
var artistToMergeHasRelease = DbContext.Releases.FirstOrDefault(x => x.ArtistId == artistToMerge.Id && x.Title == artistRelease.Title);
if (artistToMergeHasRelease != null)
{
await ReleaseService.MergeReleases(user, artistRelease, artistToMergeHasRelease, false);
}
else
{
artistRelease.ArtistId = artistToMerge.Id;
}
}
}
}
catch (Exception ex)
{
Logger.LogWarning(ex.ToString());
}
foreach (var release in DbContext.Releases.Include("Artist").Where(x => x.ArtistId == artistToMerge.Id).ToArray())
{
var originalReleaseFolder = release.ReleaseFileFolder(artistToMergeFolder);
await ReleaseService.UpdateRelease(user, release.Adapt<Release>(), originalReleaseFolder);
}
await DbContext.SaveChangesAsync();
await Delete(user, artistToMerge);
result = true;
sw.Stop();
return new OperationResult<data.Artist>
{
Data = artistToMergeInto,
IsSuccess = result,
OperationTime = sw.ElapsedMilliseconds
};
}
private async Task<OperationResult<Image>> SaveImageBytes(ApplicationUser user, Guid id, byte[] imageBytes)
{
var sw = new Stopwatch();
@ -1368,96 +1477,5 @@ namespace Roadie.Api.Services
Errors = errors
};
}
public async Task<OperationResult<bool>> ScanArtistReleasesFolders(ApplicationUser user, Guid artistId, string destinationFolder, bool doJustInfo)
{
SimpleContract.Requires<ArgumentOutOfRangeException>(artistId != Guid.Empty, "Invalid ArtistId");
var result = true;
var resultErrors = new List<Exception>();
var sw = new Stopwatch();
sw.Start();
try
{
var artist = DbContext.Artists
.Include("Releases")
.Include("Releases.Labels")
.FirstOrDefault(x => x.RoadieId == artistId);
if (artist == null)
{
Logger.LogWarning("Unable To Find Artist [{0}]", artistId);
return new OperationResult<bool>();
}
var releaseScannedCount = 0;
var artistFolder = artist.ArtistFileFolder(Configuration);
if (!Directory.Exists(artistFolder))
{
Logger.LogDebug($"ScanArtistReleasesFolders: ArtistFolder Not Found [{ artistFolder }] For Artist `{ artist }`");
return new OperationResult<bool>();
}
var scannedArtistFolders = new List<string>();
// Scan known releases for changes
if (artist.Releases != null)
{
foreach (var release in artist.Releases)
try
{
result = result && (await ReleaseService.ScanReleaseFolder(user, Guid.Empty, doJustInfo, release)).Data;
releaseScannedCount++;
scannedArtistFolders.Add(release.ReleaseFileFolder(artistFolder));
}
catch (Exception ex)
{
Logger.LogError(ex, ex.Serialize());
}
}
// Any folder found in Artist folder not already scanned scan
var nonReleaseFolders = from d in Directory.EnumerateDirectories(artistFolder)
where !(from r in scannedArtistFolders select r).Contains(d)
orderby d
select d;
foreach (var folder in nonReleaseFolders)
{
await FileDirectoryProcessorService.Process(user, new DirectoryInfo(folder), doJustInfo);
}
if (!doJustInfo)
{
Services.FileDirectoryProcessorService.DeleteEmptyFolders(new DirectoryInfo(artistFolder), Logger);
}
// Always update artist image if artist image is found on an artist rescan
var imageFiles = ImageHelper.ImageFilesInFolder(artistFolder, SearchOption.AllDirectories);
if (imageFiles != null && imageFiles.Any())
{
var i = new FileInfo(imageFiles.First());
var iName = i.Name.ToLower().Trim();
if (ImageHelper.IsArtistImage(i))
{
// Read image and convert to jpeg
artist.Thumbnail = ImageHelper.ResizeToThumbnail(File.ReadAllBytes(i.FullName), Configuration);
artist.LastUpdated = DateTime.UtcNow;
await DbContext.SaveChangesAsync();
CacheManager.ClearRegion(artist.CacheRegion);
Logger.LogInformation("Update Thumbnail using Artist File [{0}]", iName);
}
}
sw.Stop();
CacheManager.ClearRegion(artist.CacheRegion);
Logger.LogInformation("Scanned Artist [{0}], Releases Scanned [{1}], OperationTime [{2}]", artist.ToString(), releaseScannedCount, sw.ElapsedMilliseconds);
}
catch (Exception ex)
{
Logger.LogError(ex, ex.Serialize());
resultErrors.Add(ex);
}
return new OperationResult<bool>
{
Data = result,
IsSuccess = result,
Errors = resultErrors,
OperationTime = sw.ElapsedMilliseconds
};
}
}
}

View file

@ -1,7 +1,12 @@
using Microsoft.Extensions.Logging;
using Mapster;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Roadie.Library;
using Roadie.Library.Caching;
using Roadie.Library.Configuration;
using Roadie.Library.Encoding;
using Roadie.Library.Identity;
using Roadie.Library.Imaging;
using Roadie.Library.Models;
using Roadie.Library.Models.Pagination;
using Roadie.Library.Models.Users;
@ -9,6 +14,7 @@ using Roadie.Library.Utility;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
@ -28,8 +34,155 @@ namespace Roadie.Api.Services
{
}
public Task<Library.Models.Pagination.PagedResult<GenreList>> List(User roadieUser, PagedRequest request,
bool? doRandomize = false)
public async Task<OperationResult<Genre>> ById(User roadieUser, Guid id, IEnumerable<string> includes = null)
{
var sw = Stopwatch.StartNew();
sw.Start();
var cacheKey = string.Format("urn:genre_by_id_operation:{0}:{1}", id, includes == null ? "0" : string.Join("|", includes));
var result = await CacheManager.GetAsync(cacheKey, async () =>
{
return await GenreByIdAction(id, includes);
}, data.Genre.CacheRegionUrn(id));
sw.Stop();
return new OperationResult<Genre>(result.Messages)
{
Data = result?.Data,
IsNotFoundResult = result?.IsNotFoundResult ?? false,
Errors = result?.Errors,
IsSuccess = result?.IsSuccess ?? false,
OperationTime = sw.ElapsedMilliseconds
};
}
public async Task<OperationResult<bool>> Delete(ApplicationUser user, Guid id)
{
var sw = new Stopwatch();
sw.Start();
var genre = DbContext.Genres.FirstOrDefault(x => x.RoadieId == id);
if (genre == null) return new OperationResult<bool>(true, string.Format("Genre Not Found [{0}]", id));
DbContext.Genres.Remove(genre);
await DbContext.SaveChangesAsync();
var genreImageFilename = genre.PathToImage(Configuration);
if (File.Exists(genreImageFilename))
{
File.Delete(genreImageFilename);
}
Logger.LogInformation("User `{0}` deleted Genre `{1}]`", user, genre);
CacheManager.ClearRegion(genre.CacheRegion);
sw.Stop();
return new OperationResult<bool>
{
IsSuccess = true,
Data = true,
OperationTime = sw.ElapsedMilliseconds
};
}
private Task<OperationResult<Genre>> GenreByIdAction(Guid id, IEnumerable<string> includes = null)
{
var sw = Stopwatch.StartNew();
sw.Start();
var genre = DbContext.Genres.FirstOrDefault(x => x.RoadieId == id);
if (genre == null)
{
return Task.FromResult(new OperationResult<Genre>(true, string.Format("Genre Not Found [{0}]", id)));
}
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);
if (includes != null && includes.Any())
{
if (includes.Contains("stats"))
{
var releaseCount = (from rg in DbContext.ReleaseGenres
where rg.GenreId == genre.Id
select rg.Id).Count();
var artistCount = (from rg in DbContext.ArtistGenres
where rg.GenreId == genre.Id
select rg.Id).Count();
result.Statistics = new Library.Models.Statistics.ReleaseGroupingStatistics
{
ArtistCount = artistCount,
ReleaseCount = releaseCount
};
}
}
sw.Stop();
return Task.FromResult(new OperationResult<Genre>
{
Data = result,
IsSuccess = result != null,
OperationTime = sw.ElapsedMilliseconds
});
}
public async Task<OperationResult<Image>> UploadGenreImage(User user, Guid id, IFormFile file)
{
var bytes = new byte[0];
using (var ms = new MemoryStream())
{
file.CopyTo(ms);
bytes = ms.ToArray();
}
return await SaveImageBytes(user, id, bytes);
}
public async Task<OperationResult<Image>> SetGenreImageByUrl(User user, Guid id, string imageUrl)
{
return await SaveImageBytes(user, id, WebHelper.BytesForImageUrl(imageUrl));
}
private async Task<OperationResult<Image>> SaveImageBytes(User user, Guid id, byte[] imageBytes)
{
var sw = new Stopwatch();
sw.Start();
var errors = new List<Exception>();
var genre = DbContext.Genres.FirstOrDefault(x => x.RoadieId == id);
if (genre == null) return new OperationResult<Image>(true, string.Format("Genre Not Found [{0}]", id));
try
{
var now = DateTime.UtcNow;
genre.Thumbnail = imageBytes;
if (genre.Thumbnail != null)
{
// Save unaltered label image
File.WriteAllBytes(genre.PathToImage(Configuration), ImageHelper.ConvertToJpegFormat(imageBytes));
genre.Thumbnail = ImageHelper.ResizeToThumbnail(genre.Thumbnail, Configuration);
}
genre.LastUpdated = now;
await DbContext.SaveChangesAsync();
CacheManager.ClearRegion(genre.CacheRegion);
Logger.LogInformation($"UploadGenreImage `{genre}` By User `{user}`");
}
catch (Exception ex)
{
Logger.LogError(ex);
errors.Add(ex);
}
sw.Stop();
return new OperationResult<Image>
{
IsSuccess = !errors.Any(),
Data = MakeThumbnailImage(id, "genre", Configuration.MediumImageSize.Width, Configuration.MediumImageSize.Height, true),
OperationTime = sw.ElapsedMilliseconds,
Errors = errors
};
}
public Task<Library.Models.Pagination.PagedResult<GenreList>> List(User roadieUser, PagedRequest request, bool? doRandomize = false)
{
var sw = new Stopwatch();
sw.Start();
@ -60,7 +213,8 @@ namespace Roadie.Api.Services
ReleaseCount = releaseCount,
ArtistCount = artistCount,
CreatedDate = g.CreatedDate,
LastUpdated = g.LastUpdated
LastUpdated = g.LastUpdated,
Thumbnail = MakeGenreThumbnailImage(g.RoadieId)
};
GenreList[] rows;

View file

@ -19,6 +19,8 @@ namespace Roadie.Api.Services
Task<OperationResult<bool>> DeleteLabel(ApplicationUser user, Guid labelId);
Task<OperationResult<bool>> DeleteGenre(ApplicationUser user, Guid genreId);
Task<OperationResult<bool>> DeleteRelease(ApplicationUser user, Guid releaseId, bool? doDeleteFiles);
Task<OperationResult<bool>> DeleteReleaseSecondaryImage(ApplicationUser user, Guid releaseId, int index);

View file

@ -1,12 +1,22 @@
using Roadie.Library.Models;
using Microsoft.AspNetCore.Http;
using Roadie.Library;
using Roadie.Library.Identity;
using Roadie.Library.Models;
using Roadie.Library.Models.Pagination;
using Roadie.Library.Models.Users;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Roadie.Api.Services
{
public interface IGenreService
{
Task<OperationResult<Genre>> ById(User roadieUser, Guid id, IEnumerable<string> includes = null);
Task<OperationResult<bool>> Delete(ApplicationUser user, Guid id);
Task<PagedResult<GenreList>> List(User roadieUser, PagedRequest request, bool? doRandomize = false);
Task<OperationResult<Image>> SetGenreImageByUrl(User user, Guid id, string imageUrl);
Task<OperationResult<Image>> UploadGenreImage(User user, Guid id, IFormFile file);
}
}

View file

@ -28,6 +28,8 @@ namespace Roadie.Api.Services
Task<FileOperationResult<Image>> LabelImage(Guid id, int? width, int? height, EntityTagHeaderValue etag = null);
Task<FileOperationResult<Image>> GenreImage(Guid id, int? width, int? height, EntityTagHeaderValue etag = null);
Task<FileOperationResult<Image>> PlaylistImage(Guid id, int? width, int? height, EntityTagHeaderValue etag = null);
Task<FileOperationResult<Image>> ReleaseImage(Guid id, int? width, int? height, EntityTagHeaderValue etag = null);

View file

@ -9,7 +9,7 @@ namespace Roadie.Api.Services
{
public interface IUserService
{
Task<OperationResult<User>> ById(User user, Guid id, IEnumerable<string> includes);
Task<OperationResult<User>> ById(User user, Guid id, IEnumerable<string> includes, bool isAccountSettingsEdit = false);
Task<PagedResult<UserList>> List(PagedRequest request);

View file

@ -128,6 +128,17 @@ namespace Roadie.Api.Services
etag);
}
public async Task<FileOperationResult<Image>> GenreImage(Guid id, int? width, int? height, EntityTagHeaderValue etag = null)
{
return await GetImageFileOperation("GenreThumbnail",
data.Label.CacheRegionUrn(id),
id,
width,
height,
async () => { return await GenreImageAction(id, etag); },
etag);
}
public async Task<FileOperationResult<Image>> PlaylistImage(Guid id, int? width, int? height, EntityTagHeaderValue etag = null)
{
return await GetImageFileOperation("PlaylistThumbnail",
@ -480,6 +491,47 @@ namespace Roadie.Api.Services
return Task.FromResult(new FileOperationResult<Image>(OperationMessages.ErrorOccured));
}
private Task<FileOperationResult<Image>> GenreImageAction(Guid id, EntityTagHeaderValue etag = null)
{
try
{
var genre = GetGenre(id);
if (genre == null)
{
return Task.FromResult(new FileOperationResult<Image>(true, string.Format("Genre Not Found [{0}]", id)));
}
var image = new data.Image
{
Bytes = genre.Thumbnail,
CreatedDate = genre.CreatedDate,
LastUpdated = genre.LastUpdated
};
var genreImageFilename = genre.PathToImage(Configuration);
try
{
if (File.Exists(genreImageFilename))
{
image.Bytes = File.ReadAllBytes(genreImageFilename);
}
}
catch (Exception ex)
{
Logger.LogError(ex, $"Error Reading Image File [{genreImageFilename}]");
}
if (genre.Thumbnail == null || !genre.Thumbnail.Any())
{
image = DefaultNotFoundImages.Genre;
}
return Task.FromResult(GenerateFileOperationResult(id, image, etag));
}
catch (Exception ex)
{
Logger.LogError($"Error fetching Label Thumbnail [{id}]", ex);
}
return Task.FromResult(new FileOperationResult<Image>(OperationMessages.ErrorOccured));
}
private Task<FileOperationResult<Image>> PlaylistImageAction(Guid id, EntityTagHeaderValue etag = null)
{
try

View file

@ -77,7 +77,7 @@ namespace Roadie.Api.Services
var cacheKey = string.Format("urn:label_by_id_operation:{0}:{1}", id,
includes == null ? "0" : string.Join("|", includes));
var result = await CacheManager.GetAsync(cacheKey,
async () => { return await LabelByIdAction(id, includes); }, data.Artist.CacheRegionUrn(id));
async () => { return await LabelByIdAction(id, includes); }, data.Label.CacheRegionUrn(id));
sw.Stop();
if (result?.Data != null && roadieUser != null)
{
@ -157,9 +157,11 @@ namespace Roadie.Api.Services
var rowCount = result.Count();
if (doRandomize ?? false)
{
var randomLimit = roadieUser?.RandomReleaseLimit ?? 100;
var randomLimit = roadieUser?.RandomReleaseLimit ?? request.Limit;
request.Limit = request.LimitValue > randomLimit ? randomLimit : request.LimitValue;
rows = result.OrderBy(x => x.RandomSortId).Take(request.LimitValue).ToArray();
rows = result.OrderBy(x => x.RandomSortId)
.Take(request.LimitValue)
.ToArray();
}
else
{
@ -341,8 +343,7 @@ namespace Roadie.Api.Services
result.Tags = label.Tags;
result.URLs = label.URLs;
result.Thumbnail = MakeLabelThumbnailImage(label.RoadieId);
result.MediumThumbnail = MakeThumbnailImage(id, "label", Configuration.MediumImageSize.Width,
Configuration.MediumImageSize.Height);
result.MediumThumbnail = MakeThumbnailImage(id, "label", Configuration.MediumImageSize.Width,Configuration.MediumImageSize.Height);
if (includes != null && includes.Any())
{
if (includes.Contains("stats"))

View file

@ -113,8 +113,10 @@ namespace Roadie.Api.Services
sw.Start();
var cacheKey = string.Format("urn:playlist_by_id_operation:{0}:{1}", id,
includes == null ? "0" : string.Join("|", includes));
var result = await CacheManager.GetAsync(cacheKey,
async () => { return await PlaylistByIdAction(id, includes); }, data.Artist.CacheRegionUrn(id));
var result = await CacheManager.GetAsync(cacheKey, async () =>
{
return await PlaylistByIdAction(id, includes);
}, data.Artist.CacheRegionUrn(id));
sw.Stop();
if (result?.Data != null && roadieUser != null)
{
@ -122,17 +124,17 @@ 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);
}
}
result.Data.UserCanEdit = result.Data.Maintainer.Id == roadieUser.UserId || roadieUser.IsAdmin;
var userBookmarkResult =
await BookmarkService.List(roadieUser, new PagedRequest(), false, BookmarkType.Playlist);
var userBookmarkResult = await BookmarkService.List(roadieUser, new PagedRequest(), false, BookmarkType.Playlist);
if (userBookmarkResult.IsSuccess)
result.Data.UserBookmarked =
userBookmarkResult?.Rows?.FirstOrDefault(x => x.Bookmark.Text == result.Data.Id.ToString()) !=
null;
{
result.Data.UserBookmarked = userBookmarkResult?.Rows?.FirstOrDefault(x => x.Bookmark.Text == result.Data.Id.ToString()) != null;
}
if (result.Data.Comments.Any())
{
var commentIds = result.Data.Comments.Select(x => x.DatabaseId).ToArray();
@ -142,8 +144,7 @@ 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;
}
@ -402,15 +403,14 @@ namespace Roadie.Api.Services
var playlist = GetPlaylist(id);
if (playlist == null)
return Task.FromResult(new OperationResult<Playlist>(true,
string.Format("Playlist Not Found [{0}]", id)));
{
return Task.FromResult(new OperationResult<Playlist>(true, string.Format("Playlist Not Found [{0}]", id)));
}
var result = playlist.Adapt<Playlist>();
result.AlternateNames = playlist.AlternateNames;
result.Tags = playlist.Tags;
result.URLs = playlist.URLs;
var maintainer = DbContext.Users.Include(x => x.UserRoles).Include("UserRoles.Role")
.FirstOrDefault(x => x.Id == playlist.UserId);
var maintainer = DbContext.Users.Include(x => x.UserRoles).Include("UserRoles.Role").FirstOrDefault(x => x.Id == playlist.UserId);
result.Maintainer = UserList.FromDataUser(maintainer, MakeUserThumbnailImage(maintainer.RoadieId));
result.Thumbnail = MakePlaylistThumbnailImage(playlist.RoadieId);
result.MediumThumbnail = MakeThumbnailImage(id, "playlist", Configuration.MediumImageSize.Width,
@ -424,6 +424,7 @@ namespace Roadie.Api.Services
select new { t, pltr }).ToArray();
if (includes.Contains("stats"))
{
result.Statistics = new ReleaseGroupingStatistics
{
ReleaseCount = result.ReleaseCount,
@ -431,7 +432,9 @@ namespace Roadie.Api.Services
TrackSize = result.DurationTime,
FileSize = playlistTracks.Sum(x => (long?)x.t.FileSize).ToFileSize()
};
}
if (includes.Contains("tracks"))
{
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
@ -453,10 +456,13 @@ namespace Roadie.Api.Services
MakeArtistThumbnailImage(releaseArtist.RoadieId),
MakeArtistThumbnailImage(trackArtist == null ? null : (Guid?)trackArtist.RoadieId))
}).ToArray();
}
if (includes.Contains("comments"))
{
var playlistComments = DbContext.Comments.Include(x => x.User)
.Where(x => x.PlaylistId == playlist.Id).OrderByDescending(x => x.CreatedDate).ToArray();
.Where(x => x.PlaylistId == playlist.Id)
.OrderByDescending(x => x.CreatedDate)
.ToArray();
if (playlistComments.Any())
{
var comments = new List<Comment>();
@ -468,15 +474,11 @@ namespace Roadie.Api.Services
{
var comment = playlistComment.Adapt<Comment>();
comment.DatabaseId = playlistComment.Id;
comment.User = UserList.FromDataUser(playlistComment.User,
MakeUserThumbnailImage(playlistComment.User.RoadieId));
comment.DislikedCount = userCommentReactions.Count(x =>
x.CommentId == playlistComment.Id && x.ReactionValue == CommentReaction.Dislike);
comment.LikedCount = userCommentReactions.Count(x =>
x.CommentId == playlistComment.Id && x.ReactionValue == CommentReaction.Like);
comment.User = UserList.FromDataUser(playlistComment.User, MakeUserThumbnailImage(playlistComment.User.RoadieId));
comment.DislikedCount = userCommentReactions.Count(x => x.CommentId == playlistComment.Id && x.ReactionValue == CommentReaction.Dislike);
comment.LikedCount = userCommentReactions.Count(x => x.CommentId == playlistComment.Id && x.ReactionValue == CommentReaction.Like);
comments.Add(comment);
}
result.Comments = comments;
}
}

View file

@ -211,8 +211,16 @@ namespace Roadie.Api.Services
select a.Id;
IQueryable<int> genreReleaseIds = null;
var isFilteredToGenre = false;
if (!string.IsNullOrEmpty(request.FilterByGenre) || !string.IsNullOrEmpty(request.Filter) &&
request.Filter.StartsWith(":genre", StringComparison.OrdinalIgnoreCase))
if(request.FilterToGenreId.HasValue)
{
genreReleaseIds = (from rg in DbContext.ReleaseGenres
join g in DbContext.Genres on rg.GenreId equals g.Id
where g.RoadieId == request.FilterToGenreId
select rg.ReleaseId)
.Distinct();
isFilteredToGenre = true;
}
else if (!string.IsNullOrEmpty(request.FilterByGenre) || !string.IsNullOrEmpty(request.Filter) && request.Filter.StartsWith(":genre", StringComparison.OrdinalIgnoreCase))
{
var genreFilter = request.FilterByGenre ??
(request.Filter ?? string.Empty).Replace(":genre ", "",
@ -221,7 +229,7 @@ namespace Roadie.Api.Services
join g in DbContext.Genres on rg.GenreId equals g.Id
where g.Name.Contains(genreFilter)
select rg.ReleaseId)
.Distinct();
.Distinct();
request.Filter = null;
isFilteredToGenre = true;
}
@ -314,9 +322,11 @@ namespace Roadie.Api.Services
if (doRandomize ?? false)
{
var randomLimit = roadieUser?.RandomReleaseLimit ?? 100;
var randomLimit = roadieUser?.RandomReleaseLimit ?? request.Limit;
request.Limit = request.LimitValue > randomLimit ? randomLimit : request.LimitValue;
rows = result.OrderBy(x => x.RandomSortId).Skip(request.SkipValue).Take(request.LimitValue).ToArray();
rows = result.OrderBy(x => x.RandomSortId)
.Take(request.LimitValue)
.ToArray();
}
else
{

View file

@ -125,6 +125,15 @@ namespace Roadie.Api.Services
}, data.Label.CacheRegionUrn(id));
}
protected data.Genre GetGenre(Guid id)
{
return CacheManager.Get(data.Genre.CacheUrn(id), () =>
{
return DbContext.Genres
.FirstOrDefault(x => x.RoadieId == id);
}, data.Genre.CacheRegionUrn(id));
}
protected data.Playlist GetPlaylist(Guid id)
{
return CacheManager.Get(data.Playlist.CacheUrn(id), () =>
@ -245,6 +254,11 @@ namespace Roadie.Api.Services
return MakeThumbnailImage(id, "label");
}
protected Image MakeGenreThumbnailImage(Guid id)
{
return MakeThumbnailImage(id, "genre");
}
protected string MakeLastFmUrl(string artistName, string releaseTitle)
{
return "http://www.last.fm/music/" + HttpEncoder.UrlEncode($"{artistName}/{releaseTitle}");

View file

@ -518,8 +518,12 @@ namespace Roadie.Api.Services
if (doRandomize ?? false)
{
var randomLimit = roadieUser?.RandomReleaseLimit ?? request.Limit;
request.Limit = request.LimitValue > randomLimit ? randomLimit : request.LimitValue;
rows = result.OrderBy(x => x.Artist.RandomSortId)
.ThenBy(x => x.RandomSortId)
.Take(request.LimitValue)
.ToArray();
}
else

View file

@ -50,14 +50,20 @@ namespace Roadie.Api.Services
;
}
public async Task<OperationResult<User>> ById(User user, Guid id, IEnumerable<string> includes)
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)
{
return new OperationResult<User>(new Exception("Access Denied"));
}
}
var sw = Stopwatch.StartNew();
sw.Start();
var cacheKey = string.Format("urn:user_by_id_operation:{0}", id);
var cacheKey = string.Format("urn:user_by_id_operation:{0}:{1}", id, isAccountSettingsEdit);
var result = await CacheManager.GetAsync(cacheKey, async () =>
{
tsw.Restart();
@ -67,7 +73,15 @@ namespace Roadie.Api.Services
return rr;
}, ApplicationUser.CacheRegionUrn(id));
sw.Stop();
if (result?.Data != null) result.Data.Avatar = MakeUserThumbnailImage(id);
if (result?.Data != null)
{
result.Data.Avatar = MakeUserThumbnailImage(id);
if(!isAccountSettingsEdit)
{
result.Data.ApiToken = null;
result.Data.ConcurrencyStamp = null;
}
}
timings.Add("operation", sw.ElapsedMilliseconds);
Logger.LogDebug("ById Timings: id [{0}]", id);
return new OperationResult<User>(result.Messages)
@ -101,6 +115,7 @@ namespace Roadie.Api.Services
Value = u.RoadieId.ToString()
},
IsEditor = u.UserRoles.Any(x => x.Role.Name == "Editor"),
IsAdmin = u.UserRoles.Any(x => x.Role.Name == "Admin"),
IsPrivate = u.IsPrivate,
Thumbnail = MakeUserThumbnailImage(u.RoadieId),
CreatedDate = u.CreatedDate,
@ -119,15 +134,18 @@ namespace Roadie.Api.Services
rows = result.OrderBy(sortBy).Skip(request.SkipValue).Take(request.LimitValue).ToArray();
if (rows.Any())
{
foreach (var row in rows)
{
var userArtists = DbContext.UserArtists.Include(x => x.Artist)
.Where(x => x.UserId == row.DatabaseId).ToArray();
.Where(x => x.UserId == row.DatabaseId)
.ToArray();
var userReleases = DbContext.UserReleases.Include(x => x.Release)
.Where(x => x.UserId == row.DatabaseId).ToArray();
var userTracks = DbContext.UserTracks.Include(x => x.Track).Where(x => x.UserId == row.DatabaseId)
.ToArray();
.Where(x => x.UserId == row.DatabaseId)
.ToArray();
var userTracks = DbContext.UserTracks.Include(x => x.Track)
.Where(x => x.UserId == row.DatabaseId)
.ToArray();
row.Statistics = new UserStatistics
{
RatedArtists = userArtists.Where(x => x.Rating > 0).Count(),
@ -142,6 +160,7 @@ namespace Roadie.Api.Services
DislikedTracks = userTracks.Where(x => x.IsDisliked ?? false).Count()
};
}
}
sw.Stop();
return Task.FromResult(new Library.Models.Pagination.PagedResult<UserList>
@ -347,41 +366,48 @@ namespace Roadie.Api.Services
{
var user = DbContext.Users.FirstOrDefault(x => x.RoadieId == userBeingUpdatedModel.UserId);
if (user == null)
return new OperationResult<bool>(true,
string.Format("User Not Found [{0}]", userBeingUpdatedModel.UserId));
{
return new OperationResult<bool>(true, string.Format("User Not Found [{0}]", userBeingUpdatedModel.UserId));
}
if (user.Id != userPerformingUpdate.Id && !userPerformingUpdate.IsAdmin)
{
return new OperationResult<bool>
{
Errors = new List<Exception> { new Exception("Access Denied") }
};
}
// Check concurrency stamp
if (user.ConcurrencyStamp != userBeingUpdatedModel.ConcurrencyStamp)
{
return new OperationResult<bool>
{
Errors = new List<Exception> { new Exception("User data is stale.") }
};
}
// Check that username (if changed) doesn't already exist
if (user.UserName != userBeingUpdatedModel.UserName)
{
var userByUsername = DbContext.Users.FirstOrDefault(x =>
x.NormalizedUserName == userBeingUpdatedModel.UserName.ToUpper());
var userByUsername = DbContext.Users.FirstOrDefault(x => x.NormalizedUserName == userBeingUpdatedModel.UserName.ToUpper());
if (userByUsername != null)
{
return new OperationResult<bool>
{
Errors = new List<Exception> { new Exception("Username already in use") }
};
}
}
// Check that email (if changed) doesn't already exist
if (user.Email != userBeingUpdatedModel.Email)
{
var userByEmail =
DbContext.Users.FirstOrDefault(x => x.NormalizedEmail == userBeingUpdatedModel.Email.ToUpper());
var userByEmail = DbContext.Users.FirstOrDefault(x => x.NormalizedEmail == userBeingUpdatedModel.Email.ToUpper());
if (userByEmail != null)
{
return new OperationResult<bool>
{
Errors = new List<Exception> { new Exception("Email already in use") }
};
}
}
var oldPathToImage = user.PathToImage(Configuration);
var didChangeName = user.UserName != userBeingUpdatedModel.UserName;
@ -405,6 +431,7 @@ namespace Roadie.Api.Services
user.FtpUsername = userBeingUpdatedModel.FtpUsername;
user.FtpPassword = EncryptionHelper.Encrypt(userBeingUpdatedModel.FtpPassword, user.RoadieId.ToString());
user.ConcurrencyStamp = Guid.NewGuid().ToString();
user.DefaultRowsPerPage = userBeingUpdatedModel.DefaultRowsPerPage;
if (didChangeName)
{
@ -414,7 +441,6 @@ namespace Roadie.Api.Services
}
}
if (!string.IsNullOrEmpty(userBeingUpdatedModel.AvatarData))
{
var imageData = ImageHelper.ImageDataFromUrl(userBeingUpdatedModel.AvatarData);
@ -560,33 +586,43 @@ namespace Roadie.Api.Services
join ut in DbContext.UserTracks on t.Id equals ut.TrackId
where ut.UserId == user.Id
select new { a, ut.PlayedCount })
.GroupBy(a => a.a)
.Select(x => new
{
Artist = x.Key,
Played = x.Sum(t => t.PlayedCount)
})
.OrderByDescending(x => x.Played)
.FirstOrDefault();
.GroupBy(a => a.a)
.Select(x => new
{
Artist = x.Key,
Played = x.Sum(t => t.PlayedCount)
})
.OrderByDescending(x => x.Played)
.FirstOrDefault();
var mostPlayedReleaseId = (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
join ut in DbContext.UserTracks on t.Id equals ut.TrackId
where ut.UserId == user.Id
select new { r, ut.PlayedCount })
.GroupBy(r => r.r)
.Select(x => new
{
Release = x.Key,
Played = x.Sum(t => t.PlayedCount)
})
.OrderByDescending(x => x.Played)
.Select(x => x.Release.RoadieId)
.FirstOrDefault();
.GroupBy(r => r.r)
.Select(x => new
{
Release = x.Key,
Played = x.Sum(t => t.PlayedCount)
})
.OrderByDescending(x => x.Played)
.Select(x => x.Release.RoadieId)
.FirstOrDefault();
var mostPlayedRelease = GetRelease(mostPlayedReleaseId);
var mostPlayedTrackUserTrack = userTracks
.OrderByDescending(x => x.PlayedCount)
.FirstOrDefault();
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
@ -598,6 +634,22 @@ namespace Roadie.Api.Services
model.Statistics = new UserStatistics
{
LastPlayedTrack = lastPlayedTrack == null
? null
: models.TrackList.FromDataTrack(
MakeTrackPlayUrl(user, lastPlayedTrack.Id, lastPlayedTrack.RoadieId),
lastPlayedTrack,
lastPlayedTrack.ReleaseMedia.MediaNumber,
lastPlayedTrack.ReleaseMedia.Release,
lastPlayedTrack.ReleaseMedia.Release.Artist,
lastPlayedTrack.TrackArtist,
HttpContext.BaseUrl,
MakeTrackThumbnailImage(lastPlayedTrack.RoadieId),
MakeReleaseThumbnailImage(lastPlayedTrack.ReleaseMedia.Release.RoadieId),
MakeArtistThumbnailImage(lastPlayedTrack.ReleaseMedia.Release.Artist.RoadieId),
MakeArtistThumbnailImage(lastPlayedTrack.TrackArtist == null
? null
: (Guid?)lastPlayedTrack.TrackArtist.RoadieId)),
MostPlayedArtist = mostPlayedArtist == null
? null
: models.ArtistList.FromDataArtist(mostPlayedArtist.Artist,
@ -634,7 +686,7 @@ namespace Roadie.Api.Services
RatedTracks = userTracks.Where(x => x.Rating > 0).Count(),
PlayedTracks = userTracks.Where(x => x.PlayedCount.HasValue).Select(x => x.PlayedCount).Sum(),
FavoritedTracks = userTracks.Where(x => x.IsFavorite ?? false).Count(),
DislikedTracks = userTracks.Where(x => x.IsDisliked ?? false).Count()
DislikedTracks = userTracks.Where(x => x.IsDisliked ?? false).Count()
};
}
}

View file

@ -102,9 +102,11 @@ namespace Roadie.Api.Controllers
try
{
// Login user
var loginResult =
await SignInManager.PasswordSignInAsync(model.Username, model.Password, false, false);
if (!loginResult.Succeeded) return BadRequest();
var loginResult = await SignInManager.PasswordSignInAsync(model.Username, model.Password, false, false);
if (!loginResult.Succeeded)
{
return BadRequest();
}
var user = await UserManager.FindByNameAsync(model.Username);
var now = DateTime.UtcNow;
user.LastLogin = now;
@ -127,8 +129,7 @@ namespace Roadie.Api.Controllers
}
CacheManager.ClearRegion(EntityControllerBase.ControllerCacheRegionUrn);
var avatarUrl =
$"{RoadieHttpContext.ImageBaseUrl}/user/{user.RoadieId}/{RoadieSettings.ThumbnailImageSize.Width}/{RoadieSettings.ThumbnailImageSize.Height}";
var avatarUrl = $"{RoadieHttpContext.ImageBaseUrl}/user/{user.RoadieId}/{RoadieSettings.ThumbnailImageSize.Width}/{RoadieSettings.ThumbnailImageSize.Height}";
return Ok(new
{
Username = user.UserName,
@ -139,7 +140,8 @@ namespace Roadie.Api.Controllers
avatarUrl,
Token = t,
user.Timeformat,
user.Timezone
user.Timezone,
DefaultRowsPerPage = user.DefaultRowsPerPage ?? RoadieSettings.DefaultRowsPerPage
});
}
catch (Exception ex)

View file

@ -82,6 +82,15 @@ namespace Roadie.Api.Controllers
return Ok(result);
}
[HttpPost("delete/genre/{id}")]
[ProducesResponseType(200)]
public async Task<IActionResult> DeleteGenre(Guid id)
{
var result = await AdminService.DeleteGenre(await UserManager.GetUserAsync(User), id);
if (!result.IsSuccess) return StatusCode((int)HttpStatusCode.InternalServerError);
return Ok(result);
}
[HttpPost("delete/releasesecondaryimage/{id}/{index}")]
[ProducesResponseType(200)]
public async Task<IActionResult> DeleteReleaseSecondaryImage(Guid id, int index)

View file

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
@ -10,6 +11,8 @@ using Roadie.Library.Models.Pagination;
using System;
using System.Net;
using System.Threading.Tasks;
using System.Web;
using models = Roadie.Library.Models;
namespace Roadie.Api.Controllers
{
@ -29,33 +32,16 @@ namespace Roadie.Api.Controllers
GenreService = genreService;
}
//[EnableQuery]
//public IActionResult Get()
//{
// return Ok(this._RoadieDbContext.Labels.ProjectToType<models.Label>());
//}
//[HttpGet("{id}")]
//[ProducesResponseType(200)]
//[ProducesResponseType(404)]
//public IActionResult Get(Guid id)
//{
// var key = id.ToString();
// var result = this._cacheManager.Get<models.Label>(key, () =>
// {
// var d = this._RoadieDbContext.Labels.FirstOrDefault(x => x.RoadieId == id);
// if (d != null)
// {
// return d.Adapt<models.Label>();
// }
// return null;
// }, key);
// if (result == null)
// {
// return NotFound();
// }
// return Ok(result);
//}
[HttpGet("{id}")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
public async Task<IActionResult> Get(Guid id, string inc = null)
{
var result = await GenreService.ById(await CurrentUserModel(), id,(inc ?? models.Genre.DefaultIncludes).ToLower().Split(","));
if (result == null || result.IsNotFoundResult) return NotFound();
if (!result.IsSuccess) return StatusCode((int)HttpStatusCode.InternalServerError);
return Ok(result);
}
[HttpGet]
[ProducesResponseType(200)]
@ -80,5 +66,35 @@ namespace Roadie.Api.Controllers
return StatusCode((int)HttpStatusCode.InternalServerError);
}
[HttpPost("setImageByUrl/{id}/{imageUrl}")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
[Authorize(Policy = "Editor")]
public async Task<IActionResult> SetGenreImageByUrl(Guid id, string imageUrl)
{
var result = await GenreService.SetGenreImageByUrl(await CurrentUserModel(), id, HttpUtility.UrlDecode(imageUrl));
if (result == null || result.IsNotFoundResult)
{
return NotFound();
}
if (!result.IsSuccess)
{
return StatusCode((int)HttpStatusCode.InternalServerError);
}
return Ok(result);
}
[HttpPost("uploadImage/{id}")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
[Authorize(Policy = "Editor")]
public async Task<IActionResult> UploadImage(Guid id, IFormFile file)
{
var result = await GenreService.UploadGenreImage(await CurrentUserModel(), id, file);
if (result == null || result.IsNotFoundResult) return NotFound();
if (!result.IsSuccess) return StatusCode((int)HttpStatusCode.InternalServerError);
return Ok(result);
}
}
}

View file

@ -117,6 +117,21 @@ namespace Roadie.Api.Controllers
result.ETag);
}
[HttpGet("genre/{id}/{width:int?}/{height:int?}/{cacheBuster?}")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
public async Task<IActionResult> GenreImage(Guid id, int? width, int? height)
{
var result = await ImageService.GenreImage(id, width ?? RoadieSettings.ThumbnailImageSize.Width, height ?? RoadieSettings.ThumbnailImageSize.Height);
if (result == null || result.IsNotFoundResult) return NotFound();
if (!result.IsSuccess) return StatusCode((int)HttpStatusCode.InternalServerError);
return File(result.Data.Bytes,
result.ContentType,
$"{result.Data.Caption ?? id.ToString()}.jpg",
result.LastModified,
result.ETag);
}
[HttpGet("playlist/{id}/{width:int?}/{height:int?}/{cacheBuster?}")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
@ -186,6 +201,17 @@ namespace Roadie.Api.Controllers
return Ok(result);
}
[HttpPost("search/genre/{query}/{resultsCount:int?}")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
public async Task<IActionResult> SearchForGenreImage(string query, int? resultsCount)
{
var result = await ImageService.Search(query, resultsCount ?? 10);
if (result == null || result.IsNotFoundResult) return NotFound();
if (!result.IsSuccess) return StatusCode((int)HttpStatusCode.InternalServerError);
return Ok(result);
}
[HttpPost("search/release/{query}/{resultsCount:int?}")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]

View file

@ -40,6 +40,36 @@ namespace Roadie.Api.Controllers
RoadieHttpContext = httpContext;
}
[HttpGet("accountsettings/{id}")]
[ProducesResponseType(200)]
[ProducesResponseType(204)]
[ProducesResponseType(404)]
public async Task<IActionResult> GetAccountSettings(Guid id, string inc = null)
{
var user = await CurrentUserModel();
var result = await CacheManager.GetAsync($"urn:user_edit_model_by_id:{id}", async () =>
{
return await UserService.ById(user, id, (inc ?? Library.Models.Users.User.DefaultIncludes).ToLower().Split(","), true);
}, ControllerCacheRegionUrn);
if (result == null || result.IsNotFoundResult)
{
return NotFound();
}
if (!result.IsSuccess)
{
return StatusCode((int)HttpStatusCode.InternalServerError);
}
result.AdditionalClientData = new Dictionary<string, object>();
if (RoadieSettings.Integrations.LastFmProviderEnabled)
{
var lastFmCallBackUrl = $"{RoadieHttpContext.BaseUrl}/users/integration/grant?userId={user.UserId}&iname=lastfm";
result.AdditionalClientData.Add("lastFMIntegrationUrl", $"http://www.last.fm/api/auth/?api_key={RoadieSettings.Integrations.LastFMApiKey}&cb={WebUtility.UrlEncode(lastFmCallBackUrl)}");
}
return Ok(result);
}
[HttpGet("{id}")]
[ProducesResponseType(200)]
[ProducesResponseType(204)]
@ -52,17 +82,14 @@ namespace Roadie.Api.Controllers
{
return await UserService.ById(user, id, (inc ?? Library.Models.Users.User.DefaultIncludes).ToLower().Split(","));
}, ControllerCacheRegionUrn);
if (result == null || result.IsNotFoundResult) return NotFound();
if (!result.IsSuccess) return StatusCode((int)HttpStatusCode.InternalServerError);
result.AdditionalClientData = new Dictionary<string, object>();
if (RoadieSettings.Integrations.LastFmProviderEnabled)
if (result == null || result.IsNotFoundResult)
{
var lastFmCallBackUrl =
$"{RoadieHttpContext.BaseUrl}/users/integration/grant?userId={user.UserId}&iname=lastfm";
result.AdditionalClientData.Add("lastFMIntegrationUrl",
$"http://www.last.fm/api/auth/?api_key={RoadieSettings.Integrations.LastFMApiKey}&cb={WebUtility.UrlEncode(lastFmCallBackUrl)}");
return NotFound();
}
if (!result.IsSuccess)
{
return StatusCode((int)HttpStatusCode.InternalServerError);
}
return Ok(result);
}
@ -239,7 +266,10 @@ namespace Roadie.Api.Controllers
[ProducesResponseType(200)]
public async Task<IActionResult> UpdateProfile(User model)
{
if (!ModelState.IsValid) return BadRequest(ModelState);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var user = await CurrentUserModel();
var result = await UserService.UpdateProfile(user, model);
if (result == null || result.IsNotFoundResult) return NotFound();
@ -260,7 +290,8 @@ namespace Roadie.Api.Controllers
avatarUrl,
Token = t,
modelUser.Timeformat,
modelUser.Timezone
modelUser.Timezone,
DefaultRowsPerPage = modelUser.DefaultRowsPerPage ?? RoadieSettings.DefaultRowsPerPage
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

View file

@ -17,6 +17,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{1BA7
Upgrade0003.sql = Upgrade0003.sql
Upgrade0004.sql = Upgrade0004.sql
Upgrade0005.sql = Upgrade0005.sql
Upgrade0006.sql = Upgrade0006.sql
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roadie.Api.Services", "Roadie.Api.Services\Roadie.Api.Services.csproj", "{7B37031E-F2AE-4BE2-9F6F-005CA7A6FDF1}"

6
Upgrade0006.sql Normal file
View file

@ -0,0 +1,6 @@
-- v1.0.3.1 -- Genre tables modifications for https://github.com/sphildreth/roadie-vuejs/issues/63
ALTER TABLE `genre` ADD `thumbnail` BLOB NULL;
ALTER TABLE `genre` ADD `alternateNames` MEDIUMTEXT NULL;
ALTER TABLE `genre` ADD `description` varchar(4000) NULL;
ALTER TABLE `genre` ADD `tags` MEDIUMTEXT NULL;
ALTER TABLE `user` ADD `defaultRowsPerPage` SMALLINT NULL;

View file

@ -1,5 +1,5 @@
-- ///
-- Roadie version 1.0.2.3 new database script, if upgrading skip this and run Upgrade*.sql scripts from your version to current.
-- Roadie version 1.0.3.1 new database script, if upgrading skip this and run Upgrade*.sql scripts from your version to current.
-- ///
-- MySQL dump 10.17 Distrib 10.3.16-MariaDB, for Linux (x86_64)
--
@ -338,6 +338,10 @@ CREATE TABLE `genre` (
`lastUpdated` datetime DEFAULT NULL,
`name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
`normalizedName` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`thumbnail` blob DEFAULT NULL,
`alternateNames` mediumtext COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`description` varchar(4000) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`tags` mediumtext COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ix_genre_name` (`name`),
KEY `ix_genre_roadieId` (`roadieId`),
@ -629,7 +633,7 @@ CREATE TABLE `request` (
KEY `ix_request_roadieId` (`roadieId`),
KEY `requestartist_ibfk_1` (`userId`),
CONSTRAINT `requestartist_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `user` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=54 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
) ENGINE=InnoDB AUTO_INCREMENT=148 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@ -655,7 +659,7 @@ CREATE TABLE `scanHistory` (
PRIMARY KEY (`id`),
KEY `ix_scanHistory_roadieId` (`roadieId`),
KEY `rscanHistoryt_ibfk_1` (`userId`)
) ENGINE=InnoDB AUTO_INCREMENT=97 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
) ENGINE=InnoDB AUTO_INCREMENT=107 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@ -723,7 +727,7 @@ CREATE TABLE `track` (
KEY `track_artistId_IDX` (`artistId`) USING BTREE,
KEY `track_releaseMediaId_IDX` (`releaseMediaId`) USING BTREE,
CONSTRAINT `track_ibfk_1` FOREIGN KEY (`releaseMediaId`) REFERENCES `releasemedia` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=615 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
) ENGINE=InnoDB AUTO_INCREMENT=623 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@ -959,7 +963,7 @@ CREATE TABLE `usersInRoles` (
KEY `ix_usersInRoles_userId` (`userId`),
CONSTRAINT `usersInRoles_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `user` (`id`) ON DELETE CASCADE,
CONSTRAINT `usersInRoles_ibfk_2` FOREIGN KEY (`userRoleId`) REFERENCES `userrole` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@ -989,7 +993,7 @@ CREATE TABLE `usertrack` (
KEY `ix_usertrack_roadieId` (`roadieId`),
CONSTRAINT `usertrack_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `user` (`id`) ON DELETE CASCADE,
CONSTRAINT `usertrack_ibfk_2` FOREIGN KEY (`trackId`) REFERENCES `track` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=237 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
) ENGINE=InnoDB AUTO_INCREMENT=238 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@ -1075,4 +1079,4 @@ SET character_set_client = @saved_cs_client;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2019-07-18 17:31:08
-- Dump completed on 2019-08-02 11:14:18