diff --git a/Inspector/Inspector.csproj b/Inspector/Inspector.csproj index 6706d76..e9beb22 100644 --- a/Inspector/Inspector.csproj +++ b/Inspector/Inspector.csproj @@ -22,7 +22,7 @@ - + diff --git a/Roadie.Api.Library.Tests/ArtistLookupEngineTests.cs b/Roadie.Api.Library.Tests/ArtistLookupEngineTests.cs index fe72721..3d58659 100644 --- a/Roadie.Api.Library.Tests/ArtistLookupEngineTests.cs +++ b/Roadie.Api.Library.Tests/ArtistLookupEngineTests.cs @@ -47,7 +47,7 @@ namespace Roadie.Library.Tests IConfiguration configuration = configurationBuilder.Build(); configuration.GetSection("RoadieSettings").Bind(settings); Configuration = settings; - CacheManager = new DictionaryCacheManager(Logger, new CachePolicy(TimeSpan.FromHours(4))); + CacheManager = new DictionaryCacheManager(Logger, new NewtonsoftCacheSerializer(Logger), new CachePolicy(TimeSpan.FromHours(4))); HttpEncoder = new Encoding.DummyHttpEncoder(); } diff --git a/Roadie.Api.Library.Tests/ExtensionTests.cs b/Roadie.Api.Library.Tests/ExtensionTests.cs index 15ac01e..723fe93 100644 --- a/Roadie.Api.Library.Tests/ExtensionTests.cs +++ b/Roadie.Api.Library.Tests/ExtensionTests.cs @@ -290,5 +290,22 @@ namespace Roadie.Library.Tests var d = input.ToSecondsFromMilliseconds(); Assert.Equal(shouldBe, d); } + + [Theory] + [InlineData("Song (ft. Joe)")] + [InlineData("Song (featuring Joe)")] + [InlineData("Song (feat. Joe)")] + public void StringHaveFeatureFragments(string input) + { + Assert.True(input.HasFeaturingFragments()); + } + + [Theory] + [InlineData("Future Feature")] + [InlineData("Feature Song")] + public void StringShouldNotHaveFeatureFragments(string input) + { + Assert.False(input.HasFeaturingFragments()); + } } } diff --git a/Roadie.Api.Library.Tests/ID3TagsHelperTests.cs b/Roadie.Api.Library.Tests/ID3TagsHelperTests.cs index 6ea698c..012a75b 100644 --- a/Roadie.Api.Library.Tests/ID3TagsHelperTests.cs +++ b/Roadie.Api.Library.Tests/ID3TagsHelperTests.cs @@ -42,7 +42,7 @@ namespace Roadie.Library.Tests configuration.GetSection("RoadieSettings").Bind(settings); settings.ConnectionString = configuration.GetConnectionString("RoadieDatabaseConnection"); Configuration = settings; - CacheManager = new DictionaryCacheManager(Logger, new CachePolicy(TimeSpan.FromHours(4))); + CacheManager = new DictionaryCacheManager(Logger, new NewtonsoftCacheSerializer(Logger), new CachePolicy(TimeSpan.FromHours(4))); var tagHelperLooper = new EventMessageLogger(); tagHelperLooper.Messages += MessageLoggerMessages; TagsHelper = new ID3TagsHelper(Configuration, CacheManager, tagHelperLooper); diff --git a/Roadie.Api.Library.Tests/InspectorTests.cs b/Roadie.Api.Library.Tests/InspectorTests.cs index ecf91f6..a3308dd 100644 --- a/Roadie.Api.Library.Tests/InspectorTests.cs +++ b/Roadie.Api.Library.Tests/InspectorTests.cs @@ -45,7 +45,7 @@ namespace Roadie.Library.Tests configuration.GetSection("RoadieSettings").Bind(settings); settings.ConnectionString = configuration.GetConnectionString("RoadieDatabaseConnection"); Configuration = settings; - CacheManager = new DictionaryCacheManager(Logger, new CachePolicy(TimeSpan.FromHours(4))); + CacheManager = new DictionaryCacheManager(Logger, new NewtonsoftCacheSerializer(Logger), new CachePolicy(TimeSpan.FromHours(4))); var tagHelperLooper = new EventMessageLogger(); tagHelperLooper.Messages += MessageLoggerMessages; TagsHelper = new ID3TagsHelper(Configuration, CacheManager, tagHelperLooper); diff --git a/Roadie.Api.Library.Tests/Roadie.Library.Tests.csproj b/Roadie.Api.Library.Tests/Roadie.Library.Tests.csproj index f891d84..1a5df5e 100644 --- a/Roadie.Api.Library.Tests/Roadie.Library.Tests.csproj +++ b/Roadie.Api.Library.Tests/Roadie.Library.Tests.csproj @@ -23,7 +23,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Roadie.Api.Library.Tests/SearchEngineTests.cs b/Roadie.Api.Library.Tests/SearchEngineTests.cs index 0a17146..47f18f3 100644 --- a/Roadie.Api.Library.Tests/SearchEngineTests.cs +++ b/Roadie.Api.Library.Tests/SearchEngineTests.cs @@ -35,7 +35,7 @@ namespace Roadie.Library.Tests IConfiguration configuration = configurationBuilder.Build(); configuration.GetSection("RoadieSettings").Bind(settings); Configuration = settings; - CacheManager = new DictionaryCacheManager(Logger, new CachePolicy(TimeSpan.FromHours(4))); + CacheManager = new DictionaryCacheManager(Logger, new NewtonsoftCacheSerializer(Logger), new CachePolicy(TimeSpan.FromHours(4))); HttpEncoder = new Encoding.DummyHttpEncoder(); } diff --git a/Roadie.Api.Library/Caching/CacheManagerBase.cs b/Roadie.Api.Library/Caching/CacheManagerBase.cs index 495cb4e..be231c2 100644 --- a/Roadie.Api.Library/Caching/CacheManagerBase.cs +++ b/Roadie.Api.Library/Caching/CacheManagerBase.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using System; using System.Threading.Tasks; @@ -10,19 +9,15 @@ namespace Roadie.Library.Caching public const string SystemCacheRegionUrn = "urn:system"; protected readonly CachePolicy _defaultPolicy; - protected readonly JsonSerializerSettings _serializerSettings; protected ILogger Logger { get; } + protected ICacheSerializer CacheSerializer { get; } - public CacheManagerBase(ILogger logger, CachePolicy defaultPolicy) + public CacheManagerBase(ILogger logger, ICacheSerializer cacheSerializer, CachePolicy defaultPolicy) { Logger = logger; + CacheSerializer = cacheSerializer; _defaultPolicy = defaultPolicy; - _serializerSettings = new JsonSerializerSettings - { - ContractResolver = new IgnoreJsonAttributesResolver(), - Formatting = Formatting.Indented - }; } public abstract bool Add(string key, TCacheValue value); @@ -55,24 +50,5 @@ namespace Roadie.Library.Caching public abstract bool Remove(string key, string region); - protected TOut Deserialize(string s) - { - if (string.IsNullOrEmpty(s)) return default(TOut); - try - { - return JsonConvert.DeserializeObject(s, _serializerSettings); - } - catch (Exception ex) - { - Logger.LogError(ex); - } - - return default(TOut); - } - - protected string Serialize(object o) - { - return JsonConvert.SerializeObject(o, _serializerSettings); - } } } \ No newline at end of file diff --git a/Roadie.Api.Library/Caching/DictionaryCacheManager.cs b/Roadie.Api.Library/Caching/DictionaryCacheManager.cs index 88a0cbf..3fe99ec 100644 --- a/Roadie.Api.Library/Caching/DictionaryCacheManager.cs +++ b/Roadie.Api.Library/Caching/DictionaryCacheManager.cs @@ -11,8 +11,8 @@ namespace Roadie.Library.Caching private Dictionary Cache { get; } - public DictionaryCacheManager(ILogger logger, CachePolicy defaultPolicy) - : base(logger, defaultPolicy) + public DictionaryCacheManager(ILogger logger, ICacheSerializer cacheSerializer, CachePolicy defaultPolicy) + : base(logger, cacheSerializer, defaultPolicy) { Cache = new Dictionary(); } diff --git a/Roadie.Api.Library/Caching/ICacheSerializer.cs b/Roadie.Api.Library/Caching/ICacheSerializer.cs new file mode 100644 index 0000000..26ad043 --- /dev/null +++ b/Roadie.Api.Library/Caching/ICacheSerializer.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Roadie.Library.Caching +{ + public interface ICacheSerializer + { + string Serialize(object o); + TOut Deserialize(string s); + } +} diff --git a/Roadie.Api.Library/Caching/MemoryCacheManager.cs b/Roadie.Api.Library/Caching/MemoryCacheManager.cs index 78570ce..34c218d 100644 --- a/Roadie.Api.Library/Caching/MemoryCacheManager.cs +++ b/Roadie.Api.Library/Caching/MemoryCacheManager.cs @@ -9,8 +9,8 @@ namespace Roadie.Library.Caching { private MemoryCache _cache; - public MemoryCacheManager(ILogger logger, CachePolicy defaultPolicy) - : base(logger, defaultPolicy) + public MemoryCacheManager(ILogger logger, ICacheSerializer cacheSerializer, CachePolicy defaultPolicy) + : base(logger, cacheSerializer, defaultPolicy) { _cache = new MemoryCache(new MemoryCacheOptions()); } diff --git a/Roadie.Api.Library/Caching/NewtonsoftCacheSerializer.cs b/Roadie.Api.Library/Caching/NewtonsoftCacheSerializer.cs new file mode 100644 index 0000000..9831cb7 --- /dev/null +++ b/Roadie.Api.Library/Caching/NewtonsoftCacheSerializer.cs @@ -0,0 +1,43 @@ +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using System; + +namespace Roadie.Library.Caching +{ + public class NewtonsoftCacheSerializer : ICacheSerializer + { + protected ILogger Logger { get; } + + protected readonly JsonSerializerSettings _serializerSettings; + + public NewtonsoftCacheSerializer(ILogger logger) + { + Logger = logger; + _serializerSettings = new JsonSerializerSettings + { + ContractResolver = new IgnoreJsonAttributesResolver(), + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + Formatting = Formatting.Indented + }; + } + + public TOut Deserialize(string s) + { + if (string.IsNullOrEmpty(s)) + { + return default(TOut); + } + try + { + return JsonConvert.DeserializeObject(s, _serializerSettings); + } + catch (Exception ex) + { + Logger.LogError(ex); + } + return default(TOut); + } + + public string Serialize(object o) => JsonConvert.SerializeObject(o, _serializerSettings); + } +} \ No newline at end of file diff --git a/Roadie.Api.Library/Caching/RedisCacheManager.cs b/Roadie.Api.Library/Caching/RedisCacheManager.cs index ec4b43f..da31fc5 100644 --- a/Roadie.Api.Library/Caching/RedisCacheManager.cs +++ b/Roadie.Api.Library/Caching/RedisCacheManager.cs @@ -26,8 +26,8 @@ namespace Roadie.Library.Caching private IDatabase Redis => _redis ?? (_redis = Connection.GetDatabase()); - public RedisCacheManager(ILogger logger, CachePolicy defaultPolicy) - : base(logger, defaultPolicy) + public RedisCacheManager(ILogger logger, ICacheSerializer cacheSerializer, CachePolicy defaultPolicy) + : base(logger, cacheSerializer, defaultPolicy) { } @@ -49,7 +49,7 @@ namespace Roadie.Library.Caching public override bool Add(string key, TCacheValue value, string region, CachePolicy policy) { if (_doTraceLogging) Logger.LogTrace("Added [{0}], Region [{1}]", key, region); - return Redis.StringSet(key, Serialize(value)); + return Redis.StringSet(key, CacheSerializer.Serialize(value)); } public override void Clear() @@ -135,7 +135,7 @@ namespace Roadie.Library.Caching private TOut Get(string key, string region, CachePolicy policy) { - var result = Deserialize(Redis.StringGet(key)); + var result = CacheSerializer.Deserialize(Redis.StringGet(key)); if (result == null) { if (_doTraceLogging) Logger.LogTrace("Get Cache Miss Key [{0}], Region [{1}]", key, region); diff --git a/Roadie.Api.Library/Caching/Utf8JsonCacheSerializer.cs b/Roadie.Api.Library/Caching/Utf8JsonCacheSerializer.cs new file mode 100644 index 0000000..1083bfe --- /dev/null +++ b/Roadie.Api.Library/Caching/Utf8JsonCacheSerializer.cs @@ -0,0 +1,52 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Text; +using Utf8Json; + +namespace Roadie.Library.Caching +{ + public class Utf8JsonCacheSerializer : ICacheSerializer + { + protected ILogger Logger { get; } + + public Utf8JsonCacheSerializer(ILogger logger) + { + Logger = logger; + } + + public string Serialize(object o) + { + if(o == null) + { + return null; + } + try + { + return System.Text.Encoding.UTF8.GetString(JsonSerializer.Serialize(o)); + } + catch (Exception ex) + { + Logger.LogError(ex); + } + return null; + } + + public TOut Deserialize(string s) + { + if (string.IsNullOrEmpty(s)) + { + return default(TOut); + } + try + { + return JsonSerializer.Deserialize(s); + } + catch (Exception ex) + { + Logger.LogError(ex); + } + return default(TOut); + } + } +} diff --git a/Roadie.Api.Library/Configuration/Processing.cs b/Roadie.Api.Library/Configuration/Processing.cs index ad12ff4..e704253 100644 --- a/Roadie.Api.Library/Configuration/Processing.cs +++ b/Roadie.Api.Library/Configuration/Processing.cs @@ -46,6 +46,8 @@ namespace Roadie.Library.Configuration public string UnknownFolder { get; set; } + public bool DoDetectFeatureFragments { get; set; } + public Processing() { DoAudioCleanup = true; @@ -55,6 +57,7 @@ namespace Roadie.Library.Configuration DoParseFromLastFM = true; DoParseFromMusicBrainz = true; DoSaveEditsToTags = true; + DoDetectFeatureFragments = true; MaximumArtistImagesToAdd = 12; MaximumReleaseImagesToAdd = 12; diff --git a/Roadie.Api.Library/Data/CollectionPartial.cs b/Roadie.Api.Library/Data/CollectionPartial.cs index 84e2ab0..6eef9dd 100644 --- a/Roadie.Api.Library/Data/CollectionPartial.cs +++ b/Roadie.Api.Library/Data/CollectionPartial.cs @@ -1,7 +1,5 @@ using CsvHelper; -using Newtonsoft.Json; using Roadie.Library.Configuration; -using Roadie.Library.Enums; using Roadie.Library.Extensions; using Roadie.Library.Utility; using System; @@ -14,6 +12,10 @@ namespace Roadie.Library.Data { public partial class Collection { + public const string ArtistPosition = "artist"; + public const string PositionPosition = "position"; + public const string ReleasePosition = "release"; + /// /// If the given value in either Artist or Release starts with this then the next value is the database Id, example "1,~4,~19" /// @@ -35,7 +37,10 @@ namespace Roadie.Library.Data foreach (var pos in ListInCSVFormat.Split(',')) { looper++; - if (pos.ToLower().Equals("artist")) _artistColumn = looper; + if (String.Equals(pos, ArtistPosition, StringComparison.OrdinalIgnoreCase)) + { + _artistColumn = looper; + } } } @@ -70,7 +75,10 @@ namespace Roadie.Library.Data foreach (var pos in ListInCSVFormat.Split(',')) { looper++; - if (pos.ToLower().Equals("position")) _positionColumn = looper; + if (String.Equals(pos, PositionPosition, StringComparison.OrdinalIgnoreCase)) + { + _positionColumn = looper; + } } } @@ -88,7 +96,10 @@ namespace Roadie.Library.Data foreach (var pos in ListInCSVFormat.Split(',')) { looper++; - if (pos.ToLower().Equals("release")) _releaseColumn = looper; + if (String.Equals(pos, ReleasePosition, StringComparison.OrdinalIgnoreCase)) + { + _releaseColumn = looper; + } } } @@ -129,10 +140,7 @@ namespace Roadie.Library.Data MissingFieldFound = null, HasHeaderRecord = false }; - configuration.BadDataFound = context => - { - Trace.WriteLine($"PositionArtistReleases: Bad data found on row '{context.RawRow}'", "Warning"); - }; + configuration.BadDataFound = context => Trace.WriteLine($"PositionArtistReleases: Bad data found on row '{context.RawRow}'", "Warning"); using (var csv = new CsvReader(sr, configuration)) { while (csv.Read()) @@ -160,6 +168,4 @@ namespace Roadie.Library.Data return $"Id [{Id}], Name [{Name}], RoadieId [{RoadieId}]"; } } - - } \ No newline at end of file diff --git a/Roadie.Api.Library/Engines/ArtistLookupEngine.cs b/Roadie.Api.Library/Engines/ArtistLookupEngine.cs index 4d9df46..965eb99 100644 --- a/Roadie.Api.Library/Engines/ArtistLookupEngine.cs +++ b/Roadie.Api.Library/Engines/ArtistLookupEngine.cs @@ -1,6 +1,5 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using Roadie.Library.Caching; using Roadie.Library.Configuration; using Roadie.Library.Data; @@ -19,6 +18,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using discogs = Roadie.Library.SearchEngines.MetaData.Discogs; using lastfm = Roadie.Library.MetaData.LastFm; @@ -251,7 +251,7 @@ namespace Roadie.Library.Engines } if (!string.IsNullOrEmpty(releaseRoadieDataFilename) && File.Exists(releaseRoadieDataFilename)) { - artist = JsonConvert.DeserializeObject(File.ReadAllText(releaseRoadieDataFilename)); + artist = JsonSerializer.Deserialize(File.ReadAllText(releaseRoadieDataFilename)); var addResult = await Add(artist).ConfigureAwait(false); if (!addResult.IsSuccess) { diff --git a/Roadie.Api.Library/Engines/ReleaseLookupEngine.cs b/Roadie.Api.Library/Engines/ReleaseLookupEngine.cs index 1d1e1d5..6ac56f1 100644 --- a/Roadie.Api.Library/Engines/ReleaseLookupEngine.cs +++ b/Roadie.Api.Library/Engines/ReleaseLookupEngine.cs @@ -30,7 +30,11 @@ namespace Roadie.Library.Engines { public class ReleaseLookupEngine : LookupEngineBase, IReleaseLookupEngine { + private IArtistLookupEngine ArtistLookupEngine { get; } + private ILabelLookupEngine LabelLookupEngine { get; } + public List _addedReleaseIds = new List(); + public List _addedTrackIds = new List(); public IEnumerable AddedReleaseIds => _addedReleaseIds; @@ -49,10 +53,6 @@ namespace Roadie.Library.Engines public IReleaseSearchEngine WikipediaReleaseSearchEngine { get; } - private IArtistLookupEngine ArtistLookupEngine { get; } - - private ILabelLookupEngine LabelLookupEngine { get; } - public ReleaseLookupEngine(IRoadieSettings configuration, IHttpEncoder httpEncoder, IRoadieDbContext context, ICacheManager cacheManager, ILogger logger, IArtistLookupEngine artistLookupEngine, ILabelLookupEngine labelLookupEngine, musicbrainz.IMusicBrainzProvider musicBrainzProvider, lastfm.ILastFmHelper lastFmHelper, @@ -117,7 +117,11 @@ namespace Roadie.Library.Engines { var genreName = releaseGenreTable.ToAlphanumericName().ToTitleCase(); var normalizedName = genreName.ToUpper(); - if (string.IsNullOrEmpty(genreName)) continue; + if (string.IsNullOrEmpty(genreName)) + { + continue; + } + if (genreName.Length > 100) { var originalName = genreName; @@ -204,76 +208,73 @@ namespace Roadie.Library.Engines } } - if (doAddTracksInDatabase) + if (doAddTracksInDatabase && releaseMedias?.Any(x => x.Status == Statuses.New) == true) { - if (releaseMedias?.Any(x => x.Status == Statuses.New) == true) + foreach (var newReleaseMedia in releaseMedias.Where(x => x.Status == Statuses.New)) { - foreach (var newReleaseMedia in releaseMedias.Where(x => x.Status == Statuses.New)) + var releasemedia = new ReleaseMedia { - var releasemedia = new ReleaseMedia + Status = Statuses.Incomplete, + MediaNumber = newReleaseMedia.MediaNumber, + SubTitle = newReleaseMedia.SubTitle, + TrackCount = newReleaseMedia.TrackCount, + ReleaseId = release.Id + }; + var releasemediatracks = new List(); + foreach (var newTrack in newReleaseMedia.Tracks) + { + int? trackArtistId = null; + string partTitles = null; + if (newTrack.TrackArtist != null) { - Status = Statuses.Incomplete, - MediaNumber = newReleaseMedia.MediaNumber, - SubTitle = newReleaseMedia.SubTitle, - TrackCount = newReleaseMedia.TrackCount, - ReleaseId = release.Id - }; - var releasemediatracks = new List(); - foreach (var newTrack in newReleaseMedia.Tracks) - { - int? trackArtistId = null; - string partTitles = null; - if (newTrack.TrackArtist != null) + if (!release.IsCastRecording) { - if (!release.IsCastRecording) + var trackArtistData = await ArtistLookupEngine.GetByName(new AudioMetaData { Artist = newTrack.TrackArtist.Name }, true).ConfigureAwait(false); + if (trackArtistData.IsSuccess) { - var trackArtistData = await ArtistLookupEngine.GetByName(new AudioMetaData { Artist = newTrack.TrackArtist.Name }, true).ConfigureAwait(false); - if (trackArtistData.IsSuccess) - { - trackArtistId = trackArtistData.Data.Id; - } - } - else if (newTrack.TrackArtists?.Any() == true) - { - partTitles = string.Join("/", newTrack.TrackArtists); - } - else - { - partTitles = newTrack.TrackArtist.Name; + trackArtistId = trackArtistData.Data.Id; } } - - releasemediatracks.Add(new Track + else if (newTrack.TrackArtists?.Any() == true) { - ArtistId = trackArtistId, - PartTitles = partTitles, - Status = Statuses.Incomplete, - TrackNumber = newTrack.TrackNumber, - MusicBrainzId = newTrack.MusicBrainzId, - SpotifyId = newTrack.SpotifyId, - AmgId = newTrack.AmgId, - Title = newTrack.Title, - AlternateNames = newTrack.AlternateNames, - Duration = newTrack.Duration, - Tags = newTrack.Tags, - ISRC = newTrack.ISRC, - LastFMId = newTrack.LastFMId - }); + partTitles = string.Join("/", newTrack.TrackArtists); + } + else + { + partTitles = newTrack.TrackArtist.Name; + } } - releasemedia.Tracks = releasemediatracks; - DbContext.ReleaseMedias.Add(releasemedia); - _addedTrackIds.AddRange(releasemedia.Tracks.Select(x => x.Id)); + releasemediatracks.Add(new Track + { + ArtistId = trackArtistId, + PartTitles = partTitles, + Status = Statuses.Incomplete, + TrackNumber = newTrack.TrackNumber, + MusicBrainzId = newTrack.MusicBrainzId, + SpotifyId = newTrack.SpotifyId, + AmgId = newTrack.AmgId, + Title = newTrack.Title, + AlternateNames = newTrack.AlternateNames, + Duration = newTrack.Duration, + Tags = newTrack.Tags, + ISRC = newTrack.ISRC, + LastFMId = newTrack.LastFMId + }); } - try - { - await DbContext.SaveChangesAsync().ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.LogError(ex); - } + releasemedia.Tracks = releasemediatracks; + DbContext.ReleaseMedias.Add(releasemedia); + _addedTrackIds.AddRange(releasemedia.Tracks.Select(x => x.Id)); + } + + try + { + await DbContext.SaveChangesAsync().ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.LogError(ex); } } sw.Stop(); @@ -314,21 +315,21 @@ namespace Roadie.Library.Engines var specialSearchNameEnd = $"|{specialSearchName}"; return await (from a in DbContext.Releases - where a.ArtistId == artist.Id - where a.Title.ToLower() == searchName || - a.Title.ToLower() == specialSearchName || - a.SortTitle.ToLower() == searchName || - a.SortTitle.ToLower() == searchSortName || - a.SortTitle.ToLower() == specialSearchName || - a.AlternateNames.ToLower().Equals(searchName) || - a.AlternateNames.ToLower().StartsWith(searchNameStart) || - a.AlternateNames.ToLower().Contains(searchNameIn) || - a.AlternateNames.ToLower().EndsWith(searchNameEnd) || - a.AlternateNames.ToLower().Equals(specialSearchName) || - a.AlternateNames.ToLower().StartsWith(specialSearchNameStart) || - a.AlternateNames.ToLower().Contains(specialSearchNameIn) || - a.AlternateNames.ToLower().EndsWith(specialSearchNameEnd) - select a + where a.ArtistId == artist.Id + where a.Title.ToLower() == searchName || + a.Title.ToLower() == specialSearchName || + a.SortTitle.ToLower() == searchName || + a.SortTitle.ToLower() == searchSortName || + a.SortTitle.ToLower() == specialSearchName || + a.AlternateNames.ToLower().Equals(searchName) || + a.AlternateNames.ToLower().StartsWith(searchNameStart) || + a.AlternateNames.ToLower().Contains(searchNameIn) || + a.AlternateNames.ToLower().EndsWith(searchNameEnd) || + a.AlternateNames.ToLower().Equals(specialSearchName) || + a.AlternateNames.ToLower().StartsWith(specialSearchNameStart) || + a.AlternateNames.ToLower().Contains(specialSearchNameIn) || + a.AlternateNames.ToLower().EndsWith(specialSearchNameEnd) + select a ).FirstOrDefaultAsync().ConfigureAwait(false); } catch (Exception ex) @@ -348,7 +349,7 @@ namespace Roadie.Library.Engines var sw = new Stopwatch(); sw.Start(); var cacheRegion = new Release { Artist = artist, Title = metaData.Release }.CacheRegion; - var cacheKey = string.Format("urn:release_by_artist_id_and_name:{0}:{1}", artist.Id, metaData.Release); + var cacheKey = $"urn:release_by_artist_id_and_name:{artist.Id}:{metaData.Release}"; var resultInCache = CacheManager.Get(cacheKey, cacheRegion); if (resultInCache != null) { @@ -403,7 +404,11 @@ namespace Roadie.Library.Engines } } - if (release != null) CacheManager.Add(cacheKey, release); + if (release != null) + { + CacheManager.Add(cacheKey, release); + } + return new OperationResult { IsSuccess = release != null, @@ -437,7 +442,11 @@ namespace Roadie.Library.Engines var resultsExceptions = new List(); var releaseGenres = new List(); // Add any Genre found in the given MetaData - if (metaData.Genres != null) releaseGenres.AddRange(metaData.Genres); + if (metaData.Genres != null) + { + releaseGenres.AddRange(metaData.Genres); + } + var releaseLabels = new List(); var releaseMedias = new List(); var releaseImages = new List(); @@ -464,10 +473,26 @@ namespace Roadie.Library.Engines result.AlternateNames = result.AlternateNames.AddToDelimitedList(i.AlternateNames); } - if (i.Tags != null) result.Tags = result.Tags.AddToDelimitedList(i.Tags); - if (i.Urls != null) result.URLs = result.URLs.AddToDelimitedList(i.Urls); - if (i.ImageUrls != null) releaseImageUrls.AddRange(i.ImageUrls); - if (i.ReleaseGenres != null) releaseGenres.AddRange(i.ReleaseGenres); + if (i.Tags != null) + { + result.Tags = result.Tags.AddToDelimitedList(i.Tags); + } + + if (i.Urls != null) + { + result.URLs = result.URLs.AddToDelimitedList(i.Urls); + } + + if (i.ImageUrls != null) + { + releaseImageUrls.AddRange(i.ImageUrls); + } + + if (i.ReleaseGenres != null) + { + releaseGenres.AddRange(i.ReleaseGenres); + } + if (!string.IsNullOrEmpty(i.ReleaseThumbnailUrl)) { releaseImages.Add(new Imaging.Image() @@ -486,11 +511,22 @@ namespace Roadie.Library.Engines ? SafeParser.ToEnum(i.ReleaseType) : result.ReleaseType }); - if (i.ReleaseLabel != null) releaseLabels.AddRange(i.ReleaseLabel); - if (i.ReleaseMedia != null) releaseMedias.AddRange(i.ReleaseMedia); + if (i.ReleaseLabel != null) + { + releaseLabels.AddRange(i.ReleaseLabel); + } + + if (i.ReleaseMedia != null) + { + releaseMedias.AddRange(i.ReleaseMedia); + } + } + + if (iTunesResult.Errors != null) + { + resultsExceptions.AddRange(iTunesResult.Errors); } - if (iTunesResult.Errors != null) resultsExceptions.AddRange(iTunesResult.Errors); sw2.Stop(); Logger.LogTrace($"PerformMetaDataProvidersReleaseSearch: ITunesArtistSearchEngine Complete [{ sw2.ElapsedMilliseconds }]"); } @@ -508,13 +544,32 @@ namespace Roadie.Library.Engines { var mb = mbResult.Data.First(); if (mb.AlternateNames != null) + { result.AlternateNames = result.AlternateNames.AddToDelimitedList(mb.AlternateNames); - if (mb.Tags != null) result.Tags = result.Tags.AddToDelimitedList(mb.Tags); - if (mb.Urls != null) result.URLs = result.URLs.AddToDelimitedList(mb.Urls); - if (mb.ImageUrls != null) releaseImageUrls.AddRange(mb.ImageUrls); - if (mb.ReleaseGenres != null) releaseGenres.AddRange(mb.ReleaseGenres); + } + + if (mb.Tags != null) + { + result.Tags = result.Tags.AddToDelimitedList(mb.Tags); + } + + if (mb.Urls != null) + { + result.URLs = result.URLs.AddToDelimitedList(mb.Urls); + } + + if (mb.ImageUrls != null) + { + releaseImageUrls.AddRange(mb.ImageUrls); + } + + if (mb.ReleaseGenres != null) + { + releaseGenres.AddRange(mb.ReleaseGenres); + } + if (!string.IsNullOrEmpty(mb.ReleaseTitle) && - !mb.ReleaseTitle.Equals(result.Title, StringComparison.OrdinalIgnoreCase)) + !mb.ReleaseTitle.Equals(result.Title, StringComparison.OrdinalIgnoreCase)) { result.AlternateNames.AddToDelimitedList(new[] { mb.ReleaseTitle }); } @@ -541,11 +596,22 @@ namespace Roadie.Library.Engines ? SafeParser.ToEnum(mb.ReleaseType) : result.ReleaseType }); - if (mb.ReleaseLabel != null) releaseLabels.AddRange(mb.ReleaseLabel); - if (mb.ReleaseMedia != null) releaseMedias.AddRange(mb.ReleaseMedia); + if (mb.ReleaseLabel != null) + { + releaseLabels.AddRange(mb.ReleaseLabel); + } + + if (mb.ReleaseMedia != null) + { + releaseMedias.AddRange(mb.ReleaseMedia); + } + } + + if (mbResult.Errors != null) + { + resultsExceptions.AddRange(mbResult.Errors); } - if (mbResult.Errors != null) resultsExceptions.AddRange(mbResult.Errors); sw2.Stop(); Logger.LogTrace($"PerformMetaDataProvidersReleaseSearch: MusicBrainzReleaseSearchEngine Complete [{ sw2.ElapsedMilliseconds }]"); } @@ -564,13 +630,32 @@ namespace Roadie.Library.Engines { var l = lastFmResult.Data.First(); if (l.AlternateNames != null) + { result.AlternateNames = result.AlternateNames.AddToDelimitedList(l.AlternateNames); - if (l.Tags != null) result.Tags = result.Tags.AddToDelimitedList(l.Tags); - if (l.Urls != null) result.URLs = result.URLs.AddToDelimitedList(l.Urls); - if (l.ImageUrls != null) releaseImageUrls.AddRange(l.ImageUrls); - if (l.ReleaseGenres != null) releaseGenres.AddRange(l.ReleaseGenres); + } + + if (l.Tags != null) + { + result.Tags = result.Tags.AddToDelimitedList(l.Tags); + } + + if (l.Urls != null) + { + result.URLs = result.URLs.AddToDelimitedList(l.Urls); + } + + if (l.ImageUrls != null) + { + releaseImageUrls.AddRange(l.ImageUrls); + } + + if (l.ReleaseGenres != null) + { + releaseGenres.AddRange(l.ReleaseGenres); + } + if (!string.IsNullOrEmpty(l.ReleaseTitle) && - !l.ReleaseTitle.Equals(result.Title, StringComparison.OrdinalIgnoreCase)) + !l.ReleaseTitle.Equals(result.Title, StringComparison.OrdinalIgnoreCase)) { result.AlternateNames.AddToDelimitedList(new[] { l.ReleaseTitle }); } @@ -596,11 +681,22 @@ namespace Roadie.Library.Engines ? SafeParser.ToEnum(l.ReleaseType) : result.ReleaseType }); - if (l.ReleaseLabel != null) releaseLabels.AddRange(l.ReleaseLabel); - if (l.ReleaseMedia != null) releaseMedias.AddRange(l.ReleaseMedia); + if (l.ReleaseLabel != null) + { + releaseLabels.AddRange(l.ReleaseLabel); + } + + if (l.ReleaseMedia != null) + { + releaseMedias.AddRange(l.ReleaseMedia); + } + } + + if (lastFmResult.Errors != null) + { + resultsExceptions.AddRange(lastFmResult.Errors); } - if (lastFmResult.Errors != null) resultsExceptions.AddRange(lastFmResult.Errors); sw2.Stop(); Logger.LogTrace($"PerformMetaDataProvidersReleaseSearch: LastFmReleaseSearchEngine Complete [{ sw2.ElapsedMilliseconds }]"); } @@ -617,12 +713,28 @@ namespace Roadie.Library.Engines if (spotifyResult.IsSuccess) { var s = spotifyResult.Data.First(); - if (s.Tags != null) result.Tags = result.Tags.AddToDelimitedList(s.Tags); - if (s.Urls != null) result.URLs = result.URLs.AddToDelimitedList(s.Urls); - if (s.ImageUrls != null) releaseImageUrls.AddRange(s.ImageUrls); - if (s.ReleaseGenres != null) releaseGenres.AddRange(s.ReleaseGenres); + if (s.Tags != null) + { + result.Tags = result.Tags.AddToDelimitedList(s.Tags); + } + + if (s.Urls != null) + { + result.URLs = result.URLs.AddToDelimitedList(s.Urls); + } + + if (s.ImageUrls != null) + { + releaseImageUrls.AddRange(s.ImageUrls); + } + + if (s.ReleaseGenres != null) + { + releaseGenres.AddRange(s.ReleaseGenres); + } + if (!string.IsNullOrEmpty(s.ReleaseTitle) && - !s.ReleaseTitle.Equals(result.Title, StringComparison.OrdinalIgnoreCase)) + !s.ReleaseTitle.Equals(result.Title, StringComparison.OrdinalIgnoreCase)) { result.AlternateNames.AddToDelimitedList(new[] { s.ReleaseTitle }); } @@ -647,11 +759,22 @@ namespace Roadie.Library.Engines ? SafeParser.ToEnum(s.ReleaseType) : result.ReleaseType }); - if (s.ReleaseLabel != null) releaseLabels.AddRange(s.ReleaseLabel); - if (s.ReleaseMedia != null) releaseMedias.AddRange(s.ReleaseMedia); + if (s.ReleaseLabel != null) + { + releaseLabels.AddRange(s.ReleaseLabel); + } + + if (s.ReleaseMedia != null) + { + releaseMedias.AddRange(s.ReleaseMedia); + } + } + + if (spotifyResult.Errors != null) + { + resultsExceptions.AddRange(spotifyResult.Errors); } - if (spotifyResult.Errors != null) resultsExceptions.AddRange(spotifyResult.Errors); sw2.Stop(); Logger.LogTrace($"PerformMetaDataProvidersReleaseSearch: SpotifyReleaseSearchEngine Complete [{ sw2.ElapsedMilliseconds }]"); } @@ -668,12 +791,23 @@ namespace Roadie.Library.Engines if (discogsResult.IsSuccess) { var d = discogsResult.Data.First(); - if (d.Urls != null) result.URLs = result.URLs.AddToDelimitedList(d.Urls); - if (d.ImageUrls != null) releaseImageUrls.AddRange(d.ImageUrls); + if (d.Urls != null) + { + result.URLs = result.URLs.AddToDelimitedList(d.Urls); + } + + if (d.ImageUrls != null) + { + releaseImageUrls.AddRange(d.ImageUrls); + } + if (d.AlternateNames != null) + { result.AlternateNames = result.AlternateNames.AddToDelimitedList(d.AlternateNames); + } + if (!string.IsNullOrEmpty(d.ReleaseTitle) && - !d.ReleaseTitle.Equals(result.Title, StringComparison.OrdinalIgnoreCase)) + !d.ReleaseTitle.Equals(result.Title, StringComparison.OrdinalIgnoreCase)) { result.AlternateNames.AddToDelimitedList(new[] { d.ReleaseTitle }); } @@ -694,11 +828,22 @@ namespace Roadie.Library.Engines ? SafeParser.ToEnum(d.ReleaseType) : result.ReleaseType }); - if (d.ReleaseLabel != null) releaseLabels.AddRange(d.ReleaseLabel); - if (d.ReleaseMedia != null) releaseMedias.AddRange(d.ReleaseMedia); + if (d.ReleaseLabel != null) + { + releaseLabels.AddRange(d.ReleaseLabel); + } + + if (d.ReleaseMedia != null) + { + releaseMedias.AddRange(d.ReleaseMedia); + } + } + + if (discogsResult.Errors != null) + { + resultsExceptions.AddRange(discogsResult.Errors); } - if (discogsResult.Errors != null) resultsExceptions.AddRange(discogsResult.Errors); sw2.Stop(); Logger.LogTrace($"PerformMetaDataProvidersReleaseSearch: DiscogsReleaseSearchEngine Complete [{ sw2.ElapsedMilliseconds }]"); } @@ -885,7 +1030,10 @@ namespace Roadie.Library.Engines : rmTrack.AlternateNames.AddToDelimitedList(releaseTrack.AlternateNames); rmTrack.ISRC ??= releaseTrack.ISRC; rmTrack.LastFMId ??= releaseTrack.LastFMId; - if (!foundTrack) rmTracks.Add(rmTrack); + if (!foundTrack) + { + rmTracks.Add(rmTrack); + } } rm.Tracks = rmTracks; @@ -916,7 +1064,10 @@ namespace Roadie.Library.Engines { var imageCoverByReleaseName = Array.Find(imageFilesInFolder, x => x == result.Title || x == result.Title.ToFileNameFriendly()); - if (imageCoverByReleaseName != null) coverFileName = imageCoverByReleaseName; + if (imageCoverByReleaseName != null) + { + coverFileName = imageCoverByReleaseName; + } } } else if (cover.Any()) diff --git a/Roadie.Api.Library/Extensions/DateTimeExt.cs b/Roadie.Api.Library/Extensions/DateTimeExt.cs index fb767a7..42d7fc3 100644 --- a/Roadie.Api.Library/Extensions/DateTimeExt.cs +++ b/Roadie.Api.Library/Extensions/DateTimeExt.cs @@ -15,6 +15,5 @@ namespace Roadie.Library.Extensions var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); return Convert.ToInt64((date - epoch).TotalSeconds); } - } } \ No newline at end of file diff --git a/Roadie.Api.Library/Extensions/DecimalExt.cs b/Roadie.Api.Library/Extensions/DecimalExt.cs index 2c1c06a..a90f33c 100644 --- a/Roadie.Api.Library/Extensions/DecimalExt.cs +++ b/Roadie.Api.Library/Extensions/DecimalExt.cs @@ -1,5 +1,4 @@ -using Roadie.Library.Utility; -using System; +using System; namespace Roadie.Library.Extensions { @@ -17,7 +16,11 @@ namespace Roadie.Library.Extensions public static TimeSpan? ToTimeSpan(this decimal? value) { - if (!value.HasValue) return null; + if (!value.HasValue) + { + return null; + } + return TimeSpan.FromSeconds((double)value); } } diff --git a/Roadie.Api.Library/Extensions/DictionaryExt.cs b/Roadie.Api.Library/Extensions/DictionaryExt.cs index d5ee31c..b7c4062 100644 --- a/Roadie.Api.Library/Extensions/DictionaryExt.cs +++ b/Roadie.Api.Library/Extensions/DictionaryExt.cs @@ -1,8 +1,6 @@ -using Roadie.Library.Utility; -using System; +using System; using System.Collections.Generic; using System.Linq; -using System.Text; namespace Roadie.Library.Extensions { @@ -10,7 +8,7 @@ namespace Roadie.Library.Extensions { public static string ToTimings(this IDictionary values) { - if(values == null || !values.Any()) + if (values == null || !values.Any()) { return null; } @@ -24,7 +22,5 @@ namespace Roadie.Library.Extensions } return string.Join(", ", timings); } - - } } diff --git a/Roadie.Api.Library/Extensions/ExceptionExt.cs b/Roadie.Api.Library/Extensions/ExceptionExt.cs index b407bdf..4818ee7 100644 --- a/Roadie.Api.Library/Extensions/ExceptionExt.cs +++ b/Roadie.Api.Library/Extensions/ExceptionExt.cs @@ -1,5 +1,5 @@ -using Newtonsoft.Json; -using System; +using System; +using System.Text.Json; namespace Roadie.Library.Extensions { @@ -7,12 +7,15 @@ namespace Roadie.Library.Extensions { public static string Serialize(this Exception input) { - if (input == null) return null; - var settings = new JsonSerializerSettings + if (input == null) { - NullValueHandling = NullValueHandling.Ignore - }; - return JsonConvert.SerializeObject(input, Formatting.Indented, settings); + return null; + } + return JsonSerializer.Serialize(input, new JsonSerializerOptions + { + IgnoreNullValues = true, + WriteIndented = true + }); } } } \ No newline at end of file diff --git a/Roadie.Api.Library/Extensions/GenericExt.cs b/Roadie.Api.Library/Extensions/GenericExt.cs index 72bb72f..e917608 100644 --- a/Roadie.Api.Library/Extensions/GenericExt.cs +++ b/Roadie.Api.Library/Extensions/GenericExt.cs @@ -18,10 +18,16 @@ namespace Roadie.Library.Extensions /// The copied object. public static T Clone(this T source) { - if (!typeof(T).IsSerializable) throw new ArgumentException("The type must be serializable.", "source"); + if (!typeof(T).IsSerializable) + { + throw new ArgumentException("The type must be serializable.", nameof(source)); + } // Don't serialize a null object, simply return the default for that object - if (ReferenceEquals(source, null)) return default(T); + if (ReferenceEquals(source, null)) + { + return default(T); + } IFormatter formatter = new BinaryFormatter(); using (Stream stream = new MemoryStream()) @@ -37,8 +43,12 @@ namespace Roadie.Library.Extensions var oProperties = OriginalEntity.GetType().GetProperties(); foreach (var CurrentProperty in oProperties.Where(p => p.CanWrite)) + { if (CurrentProperty.GetValue(NewEntity, null) != null) + { CurrentProperty.SetValue(OriginalEntity, CurrentProperty.GetValue(NewEntity, null), null); + } + } return OriginalEntity; } @@ -47,7 +57,11 @@ namespace Roadie.Library.Extensions { var fi = source.GetType().GetField(source.ToString()); var attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); - if (attributes != null && attributes.Length > 0) return attributes[0].Description; + if (attributes != null && attributes.Length > 0) + { + return attributes[0].Description; + } + return source.ToString(); } } diff --git a/Roadie.Api.Library/Extensions/IntEx.cs b/Roadie.Api.Library/Extensions/IntEx.cs index c25dadf..8857e88 100644 --- a/Roadie.Api.Library/Extensions/IntEx.cs +++ b/Roadie.Api.Library/Extensions/IntEx.cs @@ -6,7 +6,11 @@ namespace Roadie.Library.Extensions { public static int? Or(this int? value, int? alternative) { - if (!value.HasValue && !alternative.HasValue) return null; + if (!value.HasValue && !alternative.HasValue) + { + return null; + } + return value.HasValue ? value : alternative; } diff --git a/Roadie.Api.Library/Extensions/ListExt.cs b/Roadie.Api.Library/Extensions/ListExt.cs index c078cf3..92c448c 100644 --- a/Roadie.Api.Library/Extensions/ListExt.cs +++ b/Roadie.Api.Library/Extensions/ListExt.cs @@ -31,7 +31,6 @@ namespace Roadie.Library.Extensions } } - public static string ToDelimitedList(this IList list, char delimiter = '|') { return ((ICollection)list).ToDelimitedList(delimiter); @@ -39,7 +38,11 @@ namespace Roadie.Library.Extensions public static string ToDelimitedList(this IEnumerable list, char delimiter = '|') { - if (list == null || !list.Any()) return null; + if (list == null || !list.Any()) + { + return null; + } + return string.Join(delimiter.ToString(), list); } } diff --git a/Roadie.Api.Library/Extensions/LongExt.cs b/Roadie.Api.Library/Extensions/LongExt.cs index 2b0584f..5522954 100644 --- a/Roadie.Api.Library/Extensions/LongExt.cs +++ b/Roadie.Api.Library/Extensions/LongExt.cs @@ -6,7 +6,11 @@ namespace Roadie.Library.Extensions { public static string ToFileSize(this long? l) { - if (!l.HasValue) return "0"; + if (!l.HasValue) + { + return "0"; + } + return string.Format(new FileSizeFormatProvider(), "{0:fs}", l); } } diff --git a/Roadie.Api.Library/Extensions/ShortExt.cs b/Roadie.Api.Library/Extensions/ShortExt.cs index bebab4f..7a800c6 100644 --- a/Roadie.Api.Library/Extensions/ShortExt.cs +++ b/Roadie.Api.Library/Extensions/ShortExt.cs @@ -4,14 +4,26 @@ { public static short? Or(this short? value, short? alternative) { - if (!value.HasValue && !alternative.HasValue) return null; + if (!value.HasValue && !alternative.HasValue) + { + return null; + } + return value.HasValue ? value : alternative; } public static short? TakeLarger(this short? value, short? alternative) { - if (!value.HasValue && !alternative.HasValue) return null; - if (!value.HasValue && alternative.HasValue) return alternative.Value; + if (!value.HasValue && !alternative.HasValue) + { + return null; + } + + if (!value.HasValue && alternative.HasValue) + { + return alternative.Value; + } + return value.Value > alternative.Value ? value.Value : alternative.Value; } } diff --git a/Roadie.Api.Library/Extensions/StringExt.cs b/Roadie.Api.Library/Extensions/StringExt.cs index d9a038d..75c3bc2 100644 --- a/Roadie.Api.Library/Extensions/StringExt.cs +++ b/Roadie.Api.Library/Extensions/StringExt.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Net; using System.Text; using System.Text.RegularExpressions; -using System.Web; namespace Roadie.Library.Extensions { @@ -88,7 +87,7 @@ namespace Roadie.Library.Extensions var rs = removeStringsRegex ?? settings.Processing.RemoveStringsRegex; if (!string.IsNullOrEmpty(rs)) { - result = Regex.Replace(result, rs, "", RegexOptions.IgnoreCase); + result = Regex.Replace(result, rs, string.Empty, RegexOptions.IgnoreCase); } if (result.Length > 5) { @@ -106,9 +105,25 @@ namespace Roadie.Library.Extensions return Regex.Replace(result, @"\s+", " ").Trim(); } + public static bool HasFeaturingFragments(this string input) + { + if (string.IsNullOrWhiteSpace(input)) + { + return false; + } + + return Regex.IsMatch(input, + @"\((ft.|feat.|featuring|feature)+", + RegexOptions.IgnoreCase); + } + public static bool DoesStartWithNumber(this string input) { - if (string.IsNullOrEmpty(input)) return false; + if (string.IsNullOrEmpty(input)) + { + return false; + } + var firstPart = input.Split(' ').First().SafeReplace("[").SafeReplace("]"); return SafeParser.ToNumber(firstPart) > 0; } @@ -116,32 +131,59 @@ namespace Roadie.Library.Extensions public static string FromHexString(this string hexString) { var bytes = new byte[hexString.Length / 2]; - for (var i = 0; i < bytes.Length; i++) bytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16); + for (var i = 0; i < bytes.Length; i++) + { + bytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16); + } return System.Text.Encoding.UTF8.GetString(bytes); // returns: "Hello world" for "48656C6C6F20776F726C64" } public static bool IsValidFilename(this string input) { - var containsABadCharacter = new Regex("[" + Regex.Escape(new string(Path.GetInvalidPathChars())) + "]"); - if (containsABadCharacter.IsMatch(input)) return false; - ; + var containsABadCharacter = new Regex($"[{Regex.Escape(new string(Path.GetInvalidPathChars()))}]"); + if (containsABadCharacter.IsMatch(input)) + { + return false; + }; return true; } public static bool IsValueInDelimitedList(this string input, string value, char delimiter = '|') { - if (string.IsNullOrEmpty(input)) return false; + if (string.IsNullOrEmpty(input)) + { + return false; + } + var p = input.Split(delimiter); return !p.Any() ? false : p.Any(x => x.Trim().Equals(value, StringComparison.OrdinalIgnoreCase)); } + public static string LastSegmentInUrl(this string input) + { + if (string.IsNullOrEmpty(input)) + { + return null; + } + var uri = new Uri(input); + return uri.Segments.Last(); + } + public static string NormalizeName(this string input) { - if (string.IsNullOrEmpty(input)) return input; + if (string.IsNullOrEmpty(input)) + { + return input; + } + input = input.ToLower(); var removeParts = new List { " ft. ", " ft ", " feat ", " feat. " }; - foreach (var removePart in removeParts) input = input.Replace(removePart, ""); + foreach (var removePart in removeParts) + { + input = input.Replace(removePart, string.Empty); + } + var cultInfo = new CultureInfo("en-US", false).TextInfo; return cultInfo.ToTitleCase(input).Trim(); } @@ -158,7 +200,10 @@ namespace Roadie.Library.Extensions for (var i = 0; i < normalizedString.Length; i++) { var c = normalizedString[i]; - if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark) stringBuilder.Append(c); + if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark) + { + stringBuilder.Append(c); + } } return stringBuilder.ToString(); @@ -166,7 +211,11 @@ namespace Roadie.Library.Extensions public static string RemoveFirst(this string input, string remove = "") { - if (string.IsNullOrEmpty(input)) return input; + if (string.IsNullOrEmpty(input)) + { + return input; + } + var index = input.IndexOf(remove); return index < 0 ? input @@ -175,7 +224,11 @@ namespace Roadie.Library.Extensions public static string RemoveStartsWith(this string input, string remove = "") { - if (string.IsNullOrEmpty(input)) return input; + if (string.IsNullOrEmpty(input)) + { + return input; + } + var index = input.IndexOf(remove); var result = input; while (index == 0) @@ -194,7 +247,11 @@ namespace Roadie.Library.Extensions (sb, c) => { string r; - if (UnicodeAccents.TryGetValue(c, out r)) return sb.Append(r); + if (UnicodeAccents.TryGetValue(c, out r)) + { + return sb.Append(r); + } + return sb.Append(c); }).ToString(); } @@ -211,21 +268,33 @@ namespace Roadie.Library.Extensions public static string SafeReplace(this string input, string replace, string replaceWith = " ") { - if (string.IsNullOrEmpty(input)) return null; + if (string.IsNullOrEmpty(input)) + { + return null; + } + return input.Replace(replace, replaceWith); } public static string ScrubHtml(this string value) { - var step1 = Regex.Replace(value, @"<[^>]+>| ", "").Trim(); + var step1 = Regex.Replace(value, @"<[^>]+>| ", string.Empty).Trim(); var step2 = Regex.Replace(step1, @"\s{2,}", " "); return step2; } public static string StripStartingNumber(this string input) { - if (string.IsNullOrEmpty(input)) return null; - if (input.DoesStartWithNumber()) return string.Join(" ", input.Split(' ').Skip(1)); + if (string.IsNullOrEmpty(input)) + { + return null; + } + + if (input.DoesStartWithNumber()) + { + return string.Join(" ", input.Split(' ').Skip(1)); + } + return input; } @@ -240,23 +309,40 @@ namespace Roadie.Library.Extensions .Replace("%", "per"); input = WebUtility.HtmlDecode(input); input = input.ScrubHtml().ToLower() - .Replace("&", "and"); + .Replace("&", "and"); var arr = input.ToCharArray(); arr = Array.FindAll(arr, c => (c == ',' && !stripCommas) || (char.IsWhiteSpace(c) && !stripSpaces) || char.IsLetterOrDigit(c)); input = new string(arr).RemoveDiacritics().RemoveUnicodeAccents().Translit(); - input = Regex.Replace(input, $"[^A-Za-z0-9{ ( !stripSpaces ? @"\s" : "") }{ (!stripCommas ? "," : "")}]+", ""); + input = Regex.Replace(input, $"[^A-Za-z0-9{ (!stripSpaces ? @"\s" : string.Empty) }{ (!stripCommas ? "," : string.Empty)}]+", string.Empty); return input; } public static string ToContentDispositionFriendly(this string input) { - if (string.IsNullOrEmpty(input)) return null; + if (string.IsNullOrEmpty(input)) + { + return null; + } + return input.Replace(',', ' '); } + public static string ToCSV(this IEnumerable input) + { + if (input == null || !input.Any()) + { + return null; + } + return string.Join(",", input); + } + public static string ToFileNameFriendly(this string input) { - if (string.IsNullOrEmpty(input)) return null; + if (string.IsNullOrEmpty(input)) + { + return null; + } + return Regex.Replace(PathSanitizer.SanitizeFilename(input, ' '), @"\s+", " ").Trim(); } @@ -275,14 +361,21 @@ namespace Roadie.Library.Extensions var sb = new StringBuilder(); var bytes = System.Text.Encoding.UTF8.GetBytes(str); - foreach (var t in bytes) sb.Append(t.ToString("X2")); + foreach (var t in bytes) + { + sb.Append(t.ToString("X2")); + } return sb.ToString(); // returns: "48656C6C6F20776F726C64" for "Hello world" } public static IEnumerable ToListFromDelimited(this string input, char delimiter = '|') { - if (string.IsNullOrEmpty(input)) return new string[0]; + if (string.IsNullOrEmpty(input)) + { + return new string[0]; + } + return input.Split(delimiter); } @@ -300,7 +393,7 @@ namespace Roadie.Library.Extensions { if (r.StartsWith("The ")) { - r = r.Replace("The ", "") + ", The"; + r = $"{(r.Replace("The ", string.Empty))}, The"; } } return r.NameCase(); @@ -309,21 +402,36 @@ namespace Roadie.Library.Extensions public static int? ToTrackDuration(this string input) { - if (string.IsNullOrEmpty(input) || string.IsNullOrEmpty(input.Replace(":", ""))) return null; + if (string.IsNullOrEmpty(input) || string.IsNullOrEmpty(input.Replace(":", string.Empty))) + { + return null; + } + try { var parts = input.Contains(":") ? input.Split(':').ToList() : new List { input }; - while (parts.Count() < 3) parts.Insert(0, "00:"); + while (parts.Count() < 3) + { + parts.Insert(0, "00:"); + } + var tsRaw = string.Empty; foreach (var part in parts) { - if (tsRaw.Length > 0) tsRaw += ":"; + if (tsRaw.Length > 0) + { + tsRaw += ":"; + } + tsRaw += part.PadLeft(2, '0').Substring(0, 2); } var ts = TimeSpan.MinValue; var success = TimeSpan.TryParse(tsRaw, out ts); - if (success) return (int?)ts.TotalMilliseconds; + if (success) + { + return (int?)ts.TotalMilliseconds; + } } catch { @@ -366,27 +474,11 @@ namespace Roadie.Library.Extensions public static string TrimEnd(this string input, string suffixToRemove) { if (input != null && suffixToRemove != null && input.EndsWith(suffixToRemove)) + { return input.Substring(0, input.Length - suffixToRemove.Length); + } + return input; } - - public static string LastSegmentInUrl(this string input) - { - if(string.IsNullOrEmpty(input)) - { - return null; - } - var uri = new Uri(input); - return uri.Segments.Last(); - } - - public static string ToCSV(this IEnumerable input) - { - if(input == null || !input.Any()) - { - return null; - } - return string.Join(",", input); - } } } \ No newline at end of file diff --git a/Roadie.Api.Library/Extensions/TimeSpanExt.cs b/Roadie.Api.Library/Extensions/TimeSpanExt.cs index 59f78f5..bb15b42 100644 --- a/Roadie.Api.Library/Extensions/TimeSpanExt.cs +++ b/Roadie.Api.Library/Extensions/TimeSpanExt.cs @@ -6,7 +6,11 @@ namespace Roadie.Library.Extensions { public static string ToDuration(this TimeSpan input) { - if (input == null || input.TotalMilliseconds == 0) return "--/--/--"; + if (input == null || input.TotalMilliseconds == 0) + { + return "--/--/--"; + } + return input.ToString(@"ddd\.hh\:mm\:ss"); } } diff --git a/Roadie.Api.Library/FilePlugins/Audio.cs b/Roadie.Api.Library/FilePlugins/Audio.cs index 6165033..28d897c 100644 --- a/Roadie.Api.Library/FilePlugins/Audio.cs +++ b/Roadie.Api.Library/FilePlugins/Audio.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using Roadie.Library.Caching; using Roadie.Library.Configuration; using Roadie.Library.Encoding; @@ -14,6 +13,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; namespace Roadie.Library.FilePlugins @@ -98,9 +98,16 @@ namespace Roadie.Library.FilePlugins return result; } - if (CheckMakeFolder(artistFolder)) Logger.LogTrace("Created ArtistFolder [{0}]", artistFolder); - if (CheckMakeFolder(releaseFolder)) Logger.LogTrace("Created ReleaseFolder [{0}]", releaseFolder); + if (CheckMakeFolder(artistFolder)) + { + Logger.LogTrace("Created ArtistFolder [{0}]", artistFolder); + } + if (CheckMakeFolder(releaseFolder)) + { + Logger.LogTrace("Created ReleaseFolder [{0}]", releaseFolder); + } + string imageFilename = null; try { // See if file folder parent folder (likely file is in release folder) has primary artist image if so then move to artist folder @@ -110,8 +117,8 @@ namespace Roadie.Library.FilePlugins if (artistImages.Count > 0) { var artistImage = artistImages[0]; - var artistImageFilename = Path.Combine(artistFolder, ImageHelper.ArtistImageFilename); - if (artistImageFilename != artistImage.FullName) + imageFilename = Path.Combine(artistFolder, ImageHelper.ArtistImageFilename); + if (imageFilename != artistImage.FullName) { // Read image and convert to jpeg var imageBytes = File.ReadAllBytes(artistImage.FullName); @@ -120,7 +127,7 @@ namespace Roadie.Library.FilePlugins // Move artist image to artist folder if (!doJustInfo) { - File.WriteAllBytes(artistImageFilename, imageBytes); + File.WriteAllBytes(imageFilename, imageBytes); artistImage.Delete(); } @@ -179,7 +186,10 @@ namespace Roadie.Library.FilePlugins // Read image and convert to jpeg var imageBytes = File.ReadAllBytes(releaseImage.FullName); imageBytes = ImageHelper.ConvertToJpegFormat(imageBytes); - + if(imageBytes == null) + { + Logger.LogWarning($"Unable to read image [{ releaseImage.FullName }]"); + } // Move cover to release folder if (!doJustInfo) { @@ -199,8 +209,7 @@ namespace Roadie.Library.FilePlugins foreach (var releaseImage in releaseImages) { looper++; - var releaseImageFilename = Path.Combine(releaseFolder, - string.Format(ImageHelper.ReleaseSecondaryImageFilename, looper.ToString("00"))); + var releaseImageFilename = Path.Combine(releaseFolder, string.Format(ImageHelper.ReleaseSecondaryImageFilename, looper.ToString("00"))); if (releaseImageFilename != releaseImage.FullName) { // Read image and convert to jpeg @@ -221,7 +230,7 @@ namespace Roadie.Library.FilePlugins } catch (Exception ex) { - Logger.LogError(ex, "Error with Managing Images For [{0}]", fileInfo.FullName); + Logger.LogError(ex, $"Error with Managing Images For [{fileInfo.FullName}], ImageFilename [{imageFilename }]"); } var doesFileExistsForTrack = File.Exists(destinationName); @@ -291,8 +300,7 @@ namespace Roadie.Library.FilePlugins } sw.Stop(); - Logger.LogTrace("<< Audio: Process Complete. Result `{0}`, ElapsedTime [{1}]", - JsonConvert.SerializeObject(result), sw.ElapsedMilliseconds); + Logger.LogTrace("<< Audio: Process Complete. Result `{0}`, ElapsedTime [{1}]", JsonSerializer.Serialize(result), sw.ElapsedMilliseconds); return result; } diff --git a/Roadie.Api.Library/Imaging/ImageHelper.cs b/Roadie.Api.Library/Imaging/ImageHelper.cs index 4e7347c..3c6dfaa 100644 --- a/Roadie.Api.Library/Imaging/ImageHelper.cs +++ b/Roadie.Api.Library/Imaging/ImageHelper.cs @@ -1,4 +1,5 @@ -using Roadie.Library.Configuration; +using ImageMagick; +using Roadie.Library.Configuration; using Roadie.Library.Enums; using Roadie.Library.Extensions; using Roadie.Library.MetaData.Audio; @@ -20,121 +21,123 @@ namespace Roadie.Library.Imaging public static class ImageHelper { public static string ArtistImageFilename = "artist.jpg"; + public static string ArtistSecondaryImageFilename = "artist {0}.jpg"; // Replace with counter of image + public static string ReleaseCoverFilename = "cover.jpg"; + public static string ReleaseSecondaryImageFilename = "release {0}.jpg"; // Replace with counter of image - public static byte[] ConvertToJpegFormat(byte[] imageBytes) - { - if(imageBytes?.Any() != true) - { - return null; - } - - try - { - using(var outStream = new MemoryStream()) - { - IImageFormat imageFormat = null; - using(var image = SixLabors.ImageSharp.Image.Load(imageBytes, out imageFormat)) - { - image.SaveAsJpeg(outStream); - } - return outStream.ToArray(); - } - } catch(Exception ex) - { - Trace.WriteLine($"Error Converting Image to Jpg [{ ex.Message }]", "Warning"); - } - return null; - } - /// /// Only user avatars are GIF to allow for animation. /// public static byte[] ConvertToGifFormat(byte[] imageBytes) { - if(imageBytes?.Any() != true) + if (imageBytes?.Any() != true) { return null; } try { - using(var outStream = new MemoryStream()) + using (var outStream = new MemoryStream()) { IImageFormat imageFormat = null; - using(var image = SixLabors.ImageSharp.Image.Load(imageBytes, out imageFormat)) + using (var image = SixLabors.ImageSharp.Image.Load(imageBytes, out imageFormat)) { image.SaveAsGif(outStream); } return outStream.ToArray(); } - } catch(Exception ex) + } + catch (Exception ex) { Trace.WriteLine($"Error Converting Image to Gif [{ ex.Message }]", "Warning"); } return null; } + public static byte[] ConvertToJpegFormat(byte[] imageBytes) + { + if (imageBytes?.Any() != true) + { + return null; + } + + try + { + return ConvertToJpegFormatViaSixLabors(imageBytes); + } + catch (Exception ex) + { + Trace.WriteLine($"Error Converting Image to Jpg via SixLabors [{ ex }]", "Warning"); + } + try + { + return ConvertToJpegFormatViaMagick(imageBytes); + } + catch (Exception ex) + { + Trace.WriteLine($"Error Converting Image to Jpg via Magick [{ ex }]", "Warning"); + } + return null; + } + + public static byte[] ConvertToJpegFormatViaSixLabors(byte[] imageBytes) + { + using (var outStream = new MemoryStream()) + { + IImageFormat imageFormat = null; + using (var image = SixLabors.ImageSharp.Image.Load(imageBytes, out imageFormat)) + { + image.SaveAsJpeg(outStream); + } + return outStream.ToArray(); + } + } + + public static byte[] ConvertToJpegFormatViaMagick(byte[] imageBytes) + { + using (var image = new MagickImage(imageBytes)) + { + image.Format = MagickFormat.Jpeg; + return image.ToByteArray(); + } + } + public static IEnumerable FindImagesByName(DirectoryInfo directory, string name, SearchOption folderSearchOptions = SearchOption.AllDirectories) { var result = new List(); - if(directory?.Exists != true || string.IsNullOrEmpty(name)) + if (directory?.Exists != true || string.IsNullOrEmpty(name)) + { return result; + } + var imageFilesInFolder = ImageFilesInFolder(directory.FullName, folderSearchOptions); - if(imageFilesInFolder?.Any() != true) + if (imageFilesInFolder?.Any() != true) + { return result; - if(imageFilesInFolder.Length > 0) + } + + if (imageFilesInFolder.Length > 0) { name = name.ToAlphanumericName(); - foreach(var imageFileInFolder in imageFilesInFolder) + foreach (var imageFileInFolder in imageFilesInFolder) { var image = new FileInfo(imageFileInFolder); var filenameWithoutExtension = Path.GetFileName(imageFileInFolder).ToAlphanumericName(); var imageName = image.Name.ToAlphanumericName(); - if(imageName.Equals(name) || filenameWithoutExtension.Equals(name)) + if (imageName.Equals(name) || filenameWithoutExtension.Equals(name)) + { result.Add(image); + } } } return result; } - public static bool IsImageBetterQuality(string image1, string compareToImage) - { - if(string.IsNullOrEmpty(compareToImage)) - { - return true; - } - try - { - if(string.IsNullOrEmpty(image1) || !File.Exists(image1)) - { - return File.Exists(compareToImage); - } - using(var imageComparing = SixLabors.ImageSharp.Image.Load(image1)) - { - using(var imageToCompare = SixLabors.ImageSharp.Image.Load(compareToImage)) - { - // Generally a larger image is the preferred image - var isBigger = imageToCompare.Height > imageComparing.Height && - imageToCompare.Width > imageComparing.Width; - if(isBigger) - { - return true; - } - } - } - } catch(Exception ex) - { - Trace.WriteLine($"Error IsImageBetterQuality Image Comparing [{ image1 }] to [{ compareToImage }], Error [{ ex }]", - "Warning"); - } - return false; - } - public static IEnumerable FindImageTypeInDirectory(DirectoryInfo directory, ImageType type, SearchOption folderSearchOptions = SearchOption.AllDirectories) @@ -149,10 +152,10 @@ namespace Roadie.Library.Imaging { return result; } - foreach(var imageFile in imageFilesInFolder) + foreach (var imageFile in imageFilesInFolder) { var image = new FileInfo(imageFile); - switch(type) + switch (type) { case ImageType.Artist: if (IsArtistImage(image)) @@ -197,174 +200,21 @@ namespace Roadie.Library.Imaging string[] patterns = null, SearchOption options = SearchOption.TopDirectoryOnly) { - if(!Directory.Exists(path)) + if (!Directory.Exists(path)) { return new string[0]; } - if(patterns == null || patterns.Length == 0) + if (patterns == null || patterns.Length == 0) { return Directory.GetFiles(path, "*", options); } - if(patterns.Length == 1) + if (patterns.Length == 1) { return Directory.GetFiles(path, patterns[0], options); } return patterns.SelectMany(pattern => Directory.GetFiles(path, pattern, options)).Distinct().ToArray(); } - public static byte[] ImageDataFromUrl(string imageUrl) - { - if(!string.IsNullOrEmpty(imageUrl) && !imageUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase)) - { - var dataString = imageUrl.Trim() - .Replace('-', '+') - .Replace("data:image/jpeg;base64,", "") - .Replace("data:image/bmp;base64,", "") - .Replace("data:image/gif;base64,", "") - .Replace("data:image/png;base64,", ""); - return Convert.FromBase64String(dataString); - } - - return null; - } - - public static string[] ImageExtensions() - { return new string[6] { "*.bmp", "*.jpeg", "*.jpe", "*.jpg", "*.png", "*.gif" }; } - - public static string[] ImageFilesInFolder(string folder, SearchOption searchOption) - { return GetFiles(folder, ImageExtensions(), searchOption); } - - public static string[] ImageMimeTypes() - { return new string[4] { "image/bmp", "image/jpeg", "image/png", "image/gif" }; } - - public static ImageSearchResult ImageSearchResultForImageUrl(string imageUrl) - { - if(!WebHelper.IsStringUrl(imageUrl)) - return null; - var result = new ImageSearchResult(); - var imageBytes = WebHelper.BytesForImageUrl(imageUrl); - IImageFormat imageFormat = null; - using(var image = SixLabors.ImageSharp.Image.Load(imageBytes, out imageFormat)) - { - result.Height = image.Height.ToString(); - result.Width = image.Width.ToString(); - result.MediaUrl = imageUrl; - } - - return result; - } - - public static bool IsArtistImage(FileInfo fileinfo) - { - if(fileinfo == null) - return false; - return Regex.IsMatch(fileinfo.Name, - @"(band|artist|group|photo)\.(jpg|jpeg|png|bmp|gif)", - RegexOptions.IgnoreCase); - } - - public static bool IsArtistSecondaryImage(FileInfo fileinfo) - { - if(fileinfo == null) - return false; - return Regex.IsMatch(fileinfo.Name, - @"(artist_logo|logo|photo[-_\s]*[0-9]+|(artist[\s_-]+[0-9]+)|(band[\s_-]+[0-9]+))\.(jpg|jpeg|png|bmp|gif)", - RegexOptions.IgnoreCase); - } - - public static bool IsLabelImage(FileInfo fileinfo) - { - if(fileinfo == null) - return false; - return Regex.IsMatch(fileinfo.Name, - @"(label|recordlabel|record_label)\.(jpg|jpeg|png|bmp|gif)", - RegexOptions.IgnoreCase); - } - - public static bool IsReleaseImage(FileInfo fileinfo, string releaseName = null) - { - if(fileinfo == null) - return false; - return Regex.IsMatch(fileinfo.Name, - @"((f[-_\s]*[0-9]*)|00|art|big[art]*|cover|cvr|folder|release|front[-_\s]*)\.(jpg|jpeg|png|bmp|gif)", - RegexOptions.IgnoreCase); - } - - public static bool IsReleaseSecondaryImage(FileInfo fileinfo) - { - if(fileinfo == null) - return false; - return Regex.IsMatch(fileinfo.Name, - @"((img[\s-_]*[0-9]*[\s-_]*[0-9]*)|(book[let]*[#-_\s(]*[0-9]*-*[0-9]*(\))*)|(encartes[-_\s]*[(]*[0-9]*[)]*)|sc[an]*(.)?[0-9]*|matrix(.)?[0-9]*|(cover[\s_-]*[0-9]+)|back|traycard|jewel case|disc|(.*)[in]*side(.*)|in([side|lay|let|site])*[0-9]*|digipack.?\[?\(?([0-9]*)\]?\)?|cd.?\[?\(?([0-9]*)\]?\)?|(release[\s_-]+[0-9]+))\.(jpg|jpeg|png|bmp|gif)", - RegexOptions.IgnoreCase); - } - - public static byte[] ResizeImage(byte[] imageBytes, int width, int height) => ImageHelper.ResizeImage(imageBytes, - width, - height, - false) - .Item2; - - /// - /// Resize a given image to given dimensions if needed - /// - /// Image bytes to resize - /// Resize to width - /// Resize to height - /// Force resize - /// Tuple with bool for did resize and byte array of image - public static Tuple ResizeImage(byte[] imageBytes, - int width, - int height, - bool? forceResize = false) - { - if(imageBytes == null) - { - return null; - } - try - { - using(var outStream = new MemoryStream()) - { - var resized = false; - IImageFormat imageFormat = null; - using(var image = SixLabors.ImageSharp.Image.Load(imageBytes, out imageFormat)) - { - var doForce = forceResize ?? false; - if(doForce || image.Width > width || image.Height > height) - { - int newWidth, newHeight; - if(doForce) - { - newWidth = width; - newHeight = height; - } else - { - float aspect = image.Width / (float)image.Height; - if(aspect < 1) - { - newWidth = (int)(width * aspect); - newHeight = (int)(newWidth / aspect); - } else - { - newHeight = (int)(height / aspect); - newWidth = (int)(newHeight * aspect); - } - } - image.Mutate(ctx => ctx.Resize(newWidth, newHeight)); - resized = true; - } - image.Save(outStream, imageFormat); - } - return new Tuple(resized, outStream.ToArray()); - } - } catch(Exception ex) - { - Trace.WriteLine($"Error Resizing Image [{ex}]", "Warning"); - } - return null; - } - /// /// Get image data from all sources for either fileanme or MetaData /// @@ -380,6 +230,29 @@ namespace Roadie.Library.Imaging return ImageForFilename(configuration, filename); } + public static byte[] ImageDataFromUrl(string imageUrl) + { + if (!string.IsNullOrEmpty(imageUrl) && !imageUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase)) + { + var dataString = imageUrl.Trim() + .Replace('-', '+') + .Replace("data:image/jpeg;base64,", string.Empty) + .Replace("data:image/bmp;base64,", string.Empty) + .Replace("data:image/gif;base64,", string.Empty) + .Replace("data:image/png;base64,", string.Empty) + .Replace("data:image/webp;base64,", string.Empty); + return Convert.FromBase64String(dataString); + } + + return null; + } + + public static string[] ImageExtensions() + { return new string[7] { "*.bmp", "*.jpeg", "*.jpe", "*.jpg", "*.png", "*.gif", "*.webp" }; } + + public static string[] ImageFilesInFolder(string folder, SearchOption searchOption) + { return GetFiles(folder, ImageExtensions(), searchOption); } + /// /// Does image exist with the same filename /// @@ -389,7 +262,7 @@ namespace Roadie.Library.Imaging { AudioMetaDataImage imageMetaData = null; - if(string.IsNullOrEmpty(filename)) + if (string.IsNullOrEmpty(filename)) { return imageMetaData; } @@ -397,9 +270,9 @@ namespace Roadie.Library.Imaging { var fileInfo = new FileInfo(filename); var ReleaseCover = Path.ChangeExtension(filename, "jpg"); - if(File.Exists(ReleaseCover)) + if (File.Exists(ReleaseCover)) { - using(var processor = new ImageProcessor(configuration)) + using (var processor = new ImageProcessor(configuration)) { imageMetaData = new AudioMetaDataImage { @@ -408,23 +281,31 @@ namespace Roadie.Library.Imaging MimeType = Library.Processors.FileProcessor.DetermineFileType(fileInfo) }; } - } else + } + else { // Is there a picture in filename folder (for the Release) var pictures = fileInfo.Directory.GetFiles("*.jpg"); var tagImages = new List(); - if(pictures?.Any() == true) + if (pictures?.Any() == true) { // See if there is a "cover" or "front" jpg file if so use it FileInfo picture = Array.Find(pictures, x => x.Name.Equals("cover", StringComparison.OrdinalIgnoreCase)); - if(picture == null) + if (picture == null) + { picture = Array.Find(pictures, - x => x.Name.Equals("front", StringComparison.OrdinalIgnoreCase)); - if(picture == null) + x => x.Name.Equals("front", StringComparison.OrdinalIgnoreCase)); + } + + if (picture == null) + { picture = pictures[0]; - if(picture != null) - using(var processor = new ImageProcessor(configuration)) + } + + if (picture != null) + { + using (var processor = new ImageProcessor(configuration)) { imageMetaData = new AudioMetaDataImage { @@ -433,11 +314,14 @@ namespace Roadie.Library.Imaging MimeType = Library.Processors.FileProcessor.DetermineFileType(picture) }; } + } } } - } catch(FileNotFoundException) + } + catch (FileNotFoundException) { - } catch(Exception ex) + } + catch (Exception ex) { Trace.WriteLine(ex, ex.Serialize()); } @@ -445,58 +329,132 @@ namespace Roadie.Library.Imaging return imageMetaData; } - public static models.Image MakeThumbnailImage(IRoadieSettings configuration, - IHttpContext httpContext, - Guid id, - string type, - int? width = null, - int? height = null, - bool includeCachebuster = false) + public static string[] ImageMimeTypes() + { return new string[4] { "image/bmp", "image/jpeg", "image/png", "image/gif" }; } + + public static ImageSearchResult ImageSearchResultForImageUrl(string imageUrl) { - return MakeImage(configuration, - httpContext, - id, - type, - width ?? configuration.ThumbnailImageSize.Width, - height ?? configuration.ThumbnailImageSize.Height, - null, - includeCachebuster); + if (!WebHelper.IsStringUrl(imageUrl)) + { + return null; + } + + var result = new ImageSearchResult(); + var imageBytes = WebHelper.BytesForImageUrl(imageUrl); + IImageFormat imageFormat = null; + using (var image = SixLabors.ImageSharp.Image.Load(imageBytes, out imageFormat)) + { + result.Height = image.Height.ToString(); + result.Width = image.Width.ToString(); + result.MediaUrl = imageUrl; + } + + return result; } - public static models.Image MakeNewImage(IHttpContext httpContext, string type) - { return new models.Image($"{httpContext.ImageBaseUrl}/{type}.jpg", null, null); } + public static bool IsArtistImage(FileInfo fileinfo) + { + if (fileinfo == null) + { + return false; + } - public static models.Image MakePlaylistThumbnailImage(IRoadieSettings configuration, - IHttpContext httpContext, - Guid id) - { return MakeThumbnailImage(configuration, httpContext, id, "playlist"); } + return Regex.IsMatch(fileinfo.Name, + @"(band|artist|group|photo)\.(jpg|jpeg|png|bmp|gif)", + RegexOptions.IgnoreCase); + } - public static models.Image MakeReleaseThumbnailImage(IRoadieSettings configuration, - IHttpContext httpContext, - Guid id) - { return MakeThumbnailImage(configuration, httpContext, id, "release"); } + public static bool IsArtistSecondaryImage(FileInfo fileinfo) + { + if (fileinfo == null) + { + return false; + } - public static models.Image MakeTrackThumbnailImage(IRoadieSettings configuration, - IHttpContext httpContext, - Guid id) - { return MakeThumbnailImage(configuration, httpContext, id, "track"); } + return Regex.IsMatch(fileinfo.Name, + @"(artist_logo|logo|photo[-_\s]*[0-9]+|(artist[\s_-]+[0-9]+)|(band[\s_-]+[0-9]+))\.(jpg|jpeg|png|bmp|gif)", + RegexOptions.IgnoreCase); + } - public static models.Image MakeUserThumbnailImage(IRoadieSettings configuration, - IHttpContext httpContext, - Guid id) - { return MakeThumbnailImage(configuration, httpContext, id, "user"); } + public static bool IsImageBetterQuality(string image1, string compareToImage) + { + if (string.IsNullOrEmpty(compareToImage)) + { + return true; + } + try + { + if (string.IsNullOrEmpty(image1) || !File.Exists(image1)) + { + return File.Exists(compareToImage); + } + using (var imageComparing = SixLabors.ImageSharp.Image.Load(image1)) + { + using (var imageToCompare = SixLabors.ImageSharp.Image.Load(compareToImage)) + { + // Generally a larger image is the preferred image + var isBigger = imageToCompare.Height > imageComparing.Height && + imageToCompare.Width > imageComparing.Width; + if (isBigger) + { + return true; + } + } + } + } + catch (Exception ex) + { + Trace.WriteLine($"Error IsImageBetterQuality Image Comparing [{ image1 }] to [{ compareToImage }], Error [{ ex }]", + "Warning"); + } + return false; + } - public static models.Image MakeLabelThumbnailImage(IRoadieSettings configuration, - IHttpContext httpContext, - Guid id) - { return MakeThumbnailImage(configuration, httpContext, id, "label"); } + public static bool IsLabelImage(FileInfo fileinfo) + { + if (fileinfo == null) + { + return false; + } + + return Regex.IsMatch(fileinfo.Name, + @"(label|recordlabel|record_label)\.(jpg|jpeg|png|bmp|gif)", + RegexOptions.IgnoreCase); + } + + public static bool IsReleaseImage(FileInfo fileinfo, string releaseName = null) + { + if (fileinfo == null) + { + return false; + } + + return Regex.IsMatch(fileinfo.Name, + @"((f[-_\s]*[0-9]*)|00|art|big[art]*|cover|cvr|folder|release|front[-_\s]*)\.(jpg|jpeg|png|bmp|gif)", + RegexOptions.IgnoreCase); + } + + public static bool IsReleaseSecondaryImage(FileInfo fileinfo) + { + if (fileinfo == null) + { + return false; + } + + return Regex.IsMatch(fileinfo.Name, + @"((img[\s-_]*[0-9]*[\s-_]*[0-9]*)|(book[let]*[#-_\s(]*[0-9]*-*[0-9]*(\))*)|(encartes[-_\s]*[(]*[0-9]*[)]*)|sc[an]*(.)?[0-9]*|matrix(.)?[0-9]*|(cover[\s_-]*[0-9]+)|back|traycard|jewel case|disc|(.*)[in]*side(.*)|in([side|lay|let|site])*[0-9]*|digipack.?\[?\(?([0-9]*)\]?\)?|cd.?\[?\(?([0-9]*)\]?\)?|(release[\s_-]+[0-9]+))\.(jpg|jpeg|png|bmp|gif)", + RegexOptions.IgnoreCase); + } public static models.Image MakeArtistThumbnailImage(IRoadieSettings configuration, IHttpContext httpContext, Guid? id) { - if(!id.HasValue) + if (!id.HasValue) + { return null; + } + return MakeThumbnailImage(configuration, httpContext, id.Value, "artist"); } @@ -522,7 +480,7 @@ namespace Roadie.Library.Imaging int imageId, string caption = null) { - if(type == ImageType.ArtistSecondary) + if (type == ImageType.ArtistSecondary) { return new models.Image($"{httpContext.ImageBaseUrl}/artist-secondary/{id}/{imageId}", caption, @@ -538,8 +496,12 @@ namespace Roadie.Library.Imaging Guid id) { return MakeThumbnailImage(configuration, httpContext, id, "genre"); } - public static models.Image MakeUnknownImage(IHttpContext httpContext, string unknownType = "unknown") - { return new models.Image($"{httpContext.ImageBaseUrl}/{ unknownType }.jpg"); } + public static models.Image MakeImage(IRoadieSettings configuration, + IHttpContext httpContext, + Guid id, + string type, + IImageSize imageSize) + { return MakeImage(configuration, httpContext, id, type, imageSize.Width, imageSize.Height); } public static models.Image MakeImage(IRoadieSettings configuration, IHttpContext httpContext, @@ -554,13 +516,6 @@ namespace Roadie.Library.Imaging $"{httpContext.ImageBaseUrl}/{id}/{configuration.SmallImageSize.Width}/{configuration.SmallImageSize.Height}"); } - public static models.Image MakeImage(IRoadieSettings configuration, - IHttpContext httpContext, - Guid id, - string type, - IImageSize imageSize) - { return MakeImage(configuration, httpContext, id, type, imageSize.Width, imageSize.Height); } - public static models.Image MakeImage(IRoadieSettings configuration, IHttpContext httpContext, Guid id, @@ -570,14 +525,135 @@ namespace Roadie.Library.Imaging string caption = null, bool includeCachebuster = false) { - if(width.HasValue && + if (width.HasValue && height.HasValue && (width.Value != configuration.ThumbnailImageSize.Width || height.Value != configuration.ThumbnailImageSize.Height)) + { return new models.Image($"{httpContext.ImageBaseUrl}/{type}/{id}/{width}/{height}/{(includeCachebuster ? DateTime.UtcNow.Ticks.ToString() : string.Empty)}", - caption, - $"{httpContext.ImageBaseUrl}/{type}/{id}/{configuration.ThumbnailImageSize.Width}/{configuration.ThumbnailImageSize.Height}"); + caption, + $"{httpContext.ImageBaseUrl}/{type}/{id}/{configuration.ThumbnailImageSize.Width}/{configuration.ThumbnailImageSize.Height}"); + } + return new models.Image($"{httpContext.ImageBaseUrl}/{type}/{id}", caption, null); } + + public static models.Image MakeLabelThumbnailImage(IRoadieSettings configuration, + IHttpContext httpContext, + Guid id) + { return MakeThumbnailImage(configuration, httpContext, id, "label"); } + + public static models.Image MakeNewImage(IHttpContext httpContext, string type) + { return new models.Image($"{httpContext.ImageBaseUrl}/{type}.jpg", null, null); } + + public static models.Image MakePlaylistThumbnailImage(IRoadieSettings configuration, + IHttpContext httpContext, + Guid id) + { return MakeThumbnailImage(configuration, httpContext, id, "playlist"); } + + public static models.Image MakeReleaseThumbnailImage(IRoadieSettings configuration, + IHttpContext httpContext, + Guid id) + { return MakeThumbnailImage(configuration, httpContext, id, "release"); } + + public static models.Image MakeThumbnailImage(IRoadieSettings configuration, + IHttpContext httpContext, + Guid id, + string type, + int? width = null, + int? height = null, + bool includeCachebuster = false) + { + return MakeImage(configuration, + httpContext, + id, + type, + width ?? configuration.ThumbnailImageSize.Width, + height ?? configuration.ThumbnailImageSize.Height, + null, + includeCachebuster); + } + + public static models.Image MakeTrackThumbnailImage(IRoadieSettings configuration, + IHttpContext httpContext, + Guid id) + { return MakeThumbnailImage(configuration, httpContext, id, "track"); } + + public static models.Image MakeUnknownImage(IHttpContext httpContext, string unknownType = "unknown") + { return new models.Image($"{httpContext.ImageBaseUrl}/{ unknownType }.jpg"); } + + public static models.Image MakeUserThumbnailImage(IRoadieSettings configuration, + IHttpContext httpContext, + Guid id) + { return MakeThumbnailImage(configuration, httpContext, id, "user"); } + + public static byte[] ResizeImage(byte[] imageBytes, int width, int height) => ImageHelper.ResizeImage(imageBytes, + width, + height, + false) + .Item2; + + /// + /// Resize a given image to given dimensions if needed + /// + /// Image bytes to resize + /// Resize to width + /// Resize to height + /// Force resize + /// Tuple with bool for did resize and byte array of image + public static Tuple ResizeImage(byte[] imageBytes, + int width, + int height, + bool? forceResize = false) + { + if (imageBytes == null) + { + return null; + } + try + { + using (var outStream = new MemoryStream()) + { + var resized = false; + IImageFormat imageFormat = null; + using (var image = SixLabors.ImageSharp.Image.Load(imageBytes, out imageFormat)) + { + var doForce = forceResize ?? false; + if (doForce || image.Width > width || image.Height > height) + { + int newWidth, newHeight; + if (doForce) + { + newWidth = width; + newHeight = height; + } + else + { + float aspect = image.Width / (float)image.Height; + if (aspect < 1) + { + newWidth = (int)(width * aspect); + newHeight = (int)(newWidth / aspect); + } + else + { + newHeight = (int)(height / aspect); + newWidth = (int)(newHeight * aspect); + } + } + image.Mutate(ctx => ctx.Resize(newWidth, newHeight)); + resized = true; + } + image.Save(outStream, imageFormat); + } + return new Tuple(resized, outStream.ToArray()); + } + } + catch (Exception ex) + { + Trace.WriteLine($"Error Resizing Image [{ex}]", "Warning"); + } + return null; + } } } \ No newline at end of file diff --git a/Roadie.Api.Library/Inspect/Inspector.cs b/Roadie.Api.Library/Inspect/Inspector.cs index dcce8da..b77dfe0 100644 --- a/Roadie.Api.Library/Inspect/Inspector.cs +++ b/Roadie.Api.Library/Inspect/Inspector.cs @@ -3,7 +3,6 @@ using HashidsNet; using Mapster; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using Roadie.Library.Caching; using Roadie.Library.Configuration; using Roadie.Library.Enums; @@ -21,6 +20,7 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Management.Automation; +using System.Text.Json; namespace Roadie.Library.Inspect { @@ -28,7 +28,13 @@ namespace Roadie.Library.Inspect { private const string Salt = "6856F2EE-5965-4345-884B-2CCA457AAF59"; + private IRoadieSettings Configuration { get; } + private ILogger Logger => MessageLogger as ILogger; + private IEventMessageLogger MessageLogger { get; } + private ID3TagsHelper TagsHelper { get; } + private IEnumerable _directoryPlugins; + private IEnumerable _filePlugins; public DictionaryCacheManager CacheManager { get; } @@ -47,6 +53,7 @@ namespace Roadie.Library.Inspect .SelectMany(s => s.GetTypes()) .Where(p => type.IsAssignableFrom(p)); foreach (var t in types) + { if (t.GetInterface("IInspectorDirectoryPlugin") != null && !t.IsAbstract && !t.IsInterface) { var plugin = Activator.CreateInstance(t, Configuration, CacheManager, Logger, TagsHelper) as IInspectorDirectoryPlugin; @@ -59,6 +66,7 @@ namespace Roadie.Library.Inspect Console.WriteLine($"╠╣ Not Loading Disabled Plugin [{plugin.Description}]"); } } + } } catch (Exception ex) { @@ -113,18 +121,8 @@ namespace Roadie.Library.Inspect } } - private IRoadieSettings Configuration { get; } - - private ILogger Logger => MessageLogger as ILogger; - - private IEventMessageLogger MessageLogger { get; } - - private ID3TagsHelper TagsHelper { get; } - public Inspector() { - Console.WriteLine("Roadie Media Inspector"); - MessageLogger = new EventMessageLogger(); MessageLogger.Messages += MessageLogger_Messages; @@ -135,31 +133,141 @@ namespace Roadie.Library.Inspect configuration.GetSection("RoadieSettings").Bind(settings); settings.ConnectionString = configuration.GetConnectionString("RoadieDatabaseConnection"); Configuration = settings; - CacheManager = new DictionaryCacheManager(Logger, new CachePolicy(TimeSpan.FromHours(4))); + CacheManager = new DictionaryCacheManager(Logger, new NewtonsoftCacheSerializer(Logger), new CachePolicy(TimeSpan.FromHours(4))); var tagHelperLooper = new EventMessageLogger(); tagHelperLooper.Messages += MessageLogger_Messages; TagsHelper = new ID3TagsHelper(Configuration, CacheManager, tagHelperLooper); } - public static string ArtistInspectorToken(AudioMetaData metaData) => ToToken(metaData.Artist); - - public static string ReleaseInspectorToken(AudioMetaData metaData) => ToToken(metaData.Artist + metaData.Release); - - public static string ToToken(string input) + private void InspectImage(bool isReadOnly, bool doCopy, string dest, string subdirectory, FileInfo image) { - var hashids = new Hashids(Salt); - var numbers = 0; - var bytes = System.Text.Encoding.ASCII.GetBytes(input); - var looper = bytes.Length / 4; - for (var i = 0; i < looper; i++) + if (!image.Exists) { - numbers += BitConverter.ToInt32(bytes, i * 4); + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"╟ ■ InspectImage: Image Not Found [{image.FullName}]"); + Console.ResetColor(); + return; } - if (numbers < 0) numbers *= -1; - return hashids.Encode(numbers); + + Console.WriteLine($"╟─ Inspecting Image [{image.FullName}]"); + var newImageFolder = new DirectoryInfo(Path.Combine(dest, subdirectory)); + if (!newImageFolder.Exists) + { + newImageFolder.Create(); + } + + var newImagePath = Path.Combine(dest, subdirectory, image.Name); + + if (image.FullName != newImagePath) + { + var looper = 0; + while (File.Exists(newImagePath)) + { + looper++; + newImagePath = Path.Combine(dest, subdirectory, looper.ToString("00"), image.Name); + } + if (isReadOnly) + { + Console.WriteLine($"╟ 🔒 Read Only Mode: Would be [{(doCopy ? "Copied" : "Moved")}] to [{newImagePath}]"); + } + else + { + try + { + if (!doCopy) + { + image.MoveTo(newImagePath); + } + else + { + image.CopyTo(newImagePath, true); + } + Console.ForegroundColor = ConsoleColor.DarkYellow; + Console.WriteLine($"╠═ 🚛 {(doCopy ? "Copied" : "Moved")} Image File to [{newImagePath}]"); + } + catch (Exception ex) + { + Logger.LogError(ex); + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"📛 Error file [{image.FullName}], newImagePath [{newImagePath}], Exception: [{ex}]"); + } + } + } + + Console.ResetColor(); } + private void MessageLogger_Messages(object sender, EventMessage e) + { + Console.WriteLine($"Log Level [{e.Level}] Log Message [{e.Message}] "); + var message = e.Message; + switch (e.Level) + { + case LogLevel.Trace: + Logger.LogTrace(message); + break; + + case LogLevel.Debug: + Logger.LogDebug(message); + break; + + case LogLevel.Information: + Logger.LogInformation(message); + break; + + case LogLevel.Warning: + Logger.LogWarning(message); + break; + + case LogLevel.Critical: + Logger.LogCritical(message); + break; + } + } + + private string RunScript(string scriptFilename, bool doCopy, bool isReadOnly, string directoryToInspect, string dest) + { + if (string.IsNullOrEmpty(scriptFilename)) + { + return null; + } + + try + { + if (!File.Exists(scriptFilename)) + { + Console.WriteLine($"Script Not Found: [{ scriptFilename }]"); + return null; + } + + Console.WriteLine($"Running Script: [{ scriptFilename }]"); + var script = File.ReadAllText(scriptFilename); + using (var ps = PowerShell.Create()) + { + var r = string.Empty; + var results = ps.AddScript(script) + .AddParameter("DoCopy", doCopy) + .AddParameter("IsReadOnly", isReadOnly) + .AddParameter("DirectoryToInspect", directoryToInspect) + .AddParameter("Dest", dest) + .Invoke(); + foreach (var result in results) + { + r += result + Environment.NewLine; + } + return r; + } + } + catch (Exception ex) + { + Console.WriteLine($"📛 Error with Script File [{scriptFilename}], Error [{ex}] "); + } + return null; + } + + public static string ArtistInspectorToken(AudioMetaData metaData) => ToToken(metaData.Artist); + public void Inspect(bool doCopy, bool isReadOnly, string directoryToInspect, string destination, bool dontAppendSubFolder, bool dontDeleteEmptyFolders, bool dontRunPreScripts) { Configuration.Inspector.IsInReadOnlyMode = isReadOnly; @@ -171,13 +279,32 @@ namespace Roadie.Library.Inspect Trace.Listeners.Add(new LoggingTraceListener()); + Console.ForegroundColor = ConsoleColor.Blue; + Console.WriteLine(""); + Console.WriteLine(" ▄▄▄ ▄▄▄· ·▄▄▄▄ ▪ ▄▄▄ . • ▌ ▄ ·. ▄▄▄ .·▄▄▄▄ ▪ ▄▄▄· ▪ ▐ ▄ .▄▄ · ▄▄▄·▄▄▄ . ▄▄· ▄▄▄▄▄ ▄▄▄ "); + Console.WriteLine(" ▀▄ █·▪ ▐█ ▀█ ██▪ ██ ██ ▀▄.▀· ·██ ▐███▪▀▄.▀·██▪ ██ ██ ▐█ ▀█ ██ •█▌▐█▐█ ▀. ▐█ ▄█▀▄.▀·▐█ ▌▪•██ ▪ ▀▄ █·"); + Console.WriteLine(" ▐▀▀▄ ▄█▀▄ ▄█▀▀█ ▐█· ▐█▌▐█·▐▀▀▪▄ ▐█ ▌▐▌▐█·▐▀▀▪▄▐█· ▐█▌▐█·▄█▀▀█ ▐█·▐█▐▐▌▄▀▀▀█▄ ██▀·▐▀▀▪▄██ ▄▄ ▐█.▪ ▄█▀▄ ▐▀▀▄ "); + Console.WriteLine(" ▐█•█▌▐█▌.▐▌▐█ ▪▐▌██. ██ ▐█▌▐█▄▄▌ ██ ██▌▐█▌▐█▄▄▌██. ██ ▐█▌▐█ ▪▐▌ ▐█▌██▐█▌▐█▄▪▐█▐█▪·•▐█▄▄▌▐███▌ ▐█▌·▐█▌.▐▌▐█•█▌"); + Console.WriteLine(" .▀ ▀ ▀█▄▀▪ ▀ ▀ ▀▀▀▀▀• ▀▀▀ ▀▀▀ ▀▀ █▪▀▀▀ ▀▀▀ ▀▀▀▀▀• ▀▀▀ ▀ ▀ ▀▀▀▀▀ █▪ ▀▀▀▀ .▀ ▀▀▀ ·▀▀▀ ▀▀▀ ▀█▄▀▪.▀ ▀"); + Console.WriteLine(""); + Console.ResetColor(); + Console.BackgroundColor = ConsoleColor.White; Console.ForegroundColor = ConsoleColor.Blue; Console.WriteLine($"✨ Inspector Start, UTC [{DateTime.UtcNow.ToString("s")}]"); Console.ResetColor(); + if (!Directory.Exists(directoryToInspect)) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"📛 Folder To Inspect [{ directoryToInspect }] is not found."); + Console.ResetColor(); + return; + } + string scriptResult = null; // Run PreInspect script + dontRunPreScripts = File.Exists(Configuration.Processing.PreInspectScript) && dontRunPreScripts; if (dontRunPreScripts) { Console.BackgroundColor = ConsoleColor.Blue; @@ -198,7 +325,10 @@ namespace Roadie.Library.Inspect } // Create a new destination subfolder for each Inspector run by Current timestamp var dest = Path.Combine(destination, DateTime.UtcNow.ToString("yyyyMMddHHmm")); - if (isReadOnly || dontAppendSubFolder) dest = destination; + if (isReadOnly || dontAppendSubFolder) + { + dest = destination; + } // Get all the directorys in the directory var directoryDirectories = Directory.GetDirectories(directoryToInspect, "*.*", SearchOption.AllDirectories); var directories = new List @@ -239,7 +369,7 @@ namespace Roadie.Library.Inspect var pluginResult = plugin.Process(directoryInfo); if (!pluginResult.IsSuccess) { - Console.WriteLine($"📛 Plugin Failed: Error [{JsonConvert.SerializeObject(pluginResult)}]"); + Console.WriteLine($"📛 Plugin Failed: Error [{JsonSerializer.Serialize(pluginResult)}]"); return; } if (!string.IsNullOrEmpty(pluginResult.Data)) @@ -261,7 +391,11 @@ namespace Roadie.Library.Inspect Console.WriteLine($"╟─ 🎵 Inspecting [{fileInfo.FullName}]"); var tagLib = TagsHelper.MetaDataForFile(fileInfo.FullName, true); Console.ForegroundColor = ConsoleColor.Cyan; - if (!tagLib?.IsSuccess ?? false) Console.ForegroundColor = ConsoleColor.DarkYellow; + if (!tagLib?.IsSuccess ?? false) + { + Console.ForegroundColor = ConsoleColor.DarkYellow; + } + Console.WriteLine($"╟ (Pre ) : {tagLib.Data}"); Console.ResetColor(); tagLib.Data.Filename = fileInfo.FullName; @@ -270,7 +404,7 @@ namespace Roadie.Library.Inspect { Console.ForegroundColor = ConsoleColor.DarkYellow; Console.WriteLine($"╟ ❗ INVALID: Missing: {ID3TagsHelper.DetermineMissingRequiredMetaData(originalMetaData)}"); - Console.WriteLine($"╟ [{JsonConvert.SerializeObject(tagLib, Newtonsoft.Json.Formatting.Indented)}]"); + Console.WriteLine($"╟ [{JsonSerializer.Serialize(tagLib, new JsonSerializerOptions { WriteIndented = true })}]"); Console.ResetColor(); } @@ -283,7 +417,7 @@ namespace Roadie.Library.Inspect if (!pluginResult.IsSuccess) { Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"📛 Plugin Failed: Error [{JsonConvert.SerializeObject(pluginResult)}]"); + Console.WriteLine($"📛 Plugin Failed: Error [{JsonSerializer.Serialize(pluginResult)}]"); Console.ResetColor(); return; } @@ -421,7 +555,11 @@ namespace Roadie.Library.Inspect { if (fileInfo.FullName != newPath) { - if (File.Exists(newPath)) File.Delete(newPath); + if (File.Exists(newPath)) + { + File.Delete(newPath); + } + fileInfo.MoveTo(newPath); } } @@ -453,12 +591,14 @@ namespace Roadie.Library.Inspect var pluginResult = plugin.Process(directoryInfo); if (!pluginResult.IsSuccess) { - Console.WriteLine($"📛 Plugin Failed: Error [{JsonConvert.SerializeObject(pluginResult)}]"); + Console.WriteLine($"📛 Plugin Failed: Error [{JsonSerializer.Serialize(pluginResult)}]"); return; } if (!string.IsNullOrEmpty(pluginResult.Data)) + { Console.WriteLine($"╠╣ Directory Plugin Message: {pluginResult.Data}"); + } } } @@ -469,7 +609,7 @@ namespace Roadie.Library.Inspect catch (Exception ex) { Logger.LogError(ex); - Console.WriteLine("📛 Exception: " + ex); + Console.WriteLine($"📛 Exception: {ex}"); } if (!dontDeleteEmptyFolders) @@ -496,120 +636,24 @@ namespace Roadie.Library.Inspect } } - private void InspectImage(bool isReadOnly, bool doCopy, string dest, string subdirectory, FileInfo image) + public static string ReleaseInspectorToken(AudioMetaData metaData) => ToToken(metaData.Artist + metaData.Release); + + public static string ToToken(string input) { - if (!image.Exists) + var hashids = new Hashids(Salt); + var numbers = 0; + var bytes = System.Text.Encoding.ASCII.GetBytes(input); + var looper = bytes.Length / 4; + for (var i = 0; i < looper; i++) { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"╟ ■ InspectImage: Image Not Found [{image.FullName}]"); - Console.ResetColor(); - return; + numbers += BitConverter.ToInt32(bytes, i * 4); + } + if (numbers < 0) + { + numbers *= -1; } - Console.WriteLine($"╟─ Inspecting Image [{image.FullName}]"); - var newImageFolder = new DirectoryInfo(Path.Combine(dest, subdirectory)); - if (!newImageFolder.Exists) newImageFolder.Create(); - var newImagePath = Path.Combine(dest, subdirectory, image.Name); - - if (image.FullName != newImagePath) - { - var looper = 0; - while (File.Exists(newImagePath)) - { - looper++; - newImagePath = Path.Combine(dest, subdirectory, looper.ToString("00"), image.Name); - } - if (isReadOnly) - { - Console.WriteLine($"╟ 🔒 Read Only Mode: Would be [{(doCopy ? "Copied" : "Moved")}] to [{newImagePath}]"); - } - else - { - try - { - if (!doCopy) - { - image.MoveTo(newImagePath); - } - else - { - image.CopyTo(newImagePath, true); - } - Console.ForegroundColor = ConsoleColor.DarkYellow; - Console.WriteLine($"╠═ 🚛 {(doCopy ? "Copied" : "Moved")} Image File to [{newImagePath}]"); - } - catch (Exception ex) - { - Logger.LogError(ex); - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"📛 Error file [{image.FullName}], newImagePath [{newImagePath}], Exception: [{ex}]"); - } - } - } - - Console.ResetColor(); - } - - private void MessageLogger_Messages(object sender, EventMessage e) - { - Console.WriteLine($"Log Level [{e.Level}] Log Message [{e.Message}] "); - var message = e.Message; - switch (e.Level) - { - case LogLevel.Trace: - Logger.LogTrace(message); - break; - - case LogLevel.Debug: - Logger.LogDebug(message); - break; - - case LogLevel.Information: - Logger.LogInformation(message); - break; - - case LogLevel.Warning: - Logger.LogWarning(message); - break; - - case LogLevel.Critical: - Logger.LogCritical(message); - break; - } - } - - private string RunScript(string scriptFilename, bool doCopy, bool isReadOnly, string directoryToInspect, string dest) - { - if (string.IsNullOrEmpty(scriptFilename)) - { - return null; - } - - try - { - Console.WriteLine($"Running Script: [{ scriptFilename }]"); - var script = File.ReadAllText(scriptFilename); - using (var ps = PowerShell.Create()) - { - var r = string.Empty; - var results = ps.AddScript(script) - .AddParameter("DoCopy", doCopy) - .AddParameter("IsReadOnly", isReadOnly) - .AddParameter("DirectoryToInspect", directoryToInspect) - .AddParameter("Dest", dest) - .Invoke(); - foreach (var result in results) - { - r += result + Environment.NewLine; - } - return r; - } - } - catch (Exception ex) - { - Console.WriteLine($"📛 Error with Script File [{scriptFilename}], Error [{ex}] "); - } - return null; + return hashids.Encode(numbers); } } diff --git a/Roadie.Api.Library/Inspect/Plugins/Directory/DeleteUnwantedFiles.cs b/Roadie.Api.Library/Inspect/Plugins/Directory/DeleteUnwantedFiles.cs index dfaa631..f06c799 100644 --- a/Roadie.Api.Library/Inspect/Plugins/Directory/DeleteUnwantedFiles.cs +++ b/Roadie.Api.Library/Inspect/Plugins/Directory/DeleteUnwantedFiles.cs @@ -36,7 +36,11 @@ namespace Roadie.Library.Inspect.Plugins.Directory { if (fileExtensionsToDelete.Any(x => x.Equals(file.Extension, StringComparison.OrdinalIgnoreCase))) { - if (!Configuration.Inspector.IsInReadOnlyMode) file.Delete(); + if (!Configuration.Inspector.IsInReadOnlyMode) + { + file.Delete(); + } + deletedFiles.Add(file.Name); Console.WriteLine($" X Deleted File [{file}], Was found in in FileExtensionsToDelete"); } diff --git a/Roadie.Api.Library/Inspect/Plugins/Directory/EnsureArtistConsistent.cs b/Roadie.Api.Library/Inspect/Plugins/Directory/EnsureArtistConsistent.cs index 5ba57e4..493345a 100644 --- a/Roadie.Api.Library/Inspect/Plugins/Directory/EnsureArtistConsistent.cs +++ b/Roadie.Api.Library/Inspect/Plugins/Directory/EnsureArtistConsistent.cs @@ -36,10 +36,13 @@ namespace Roadie.Library.Inspect.Plugins.Directory var firstMetaData = metaDatasForFilesInFolder.OrderBy(x => x.Filename ?? string.Empty) .ThenBy(x => SafeParser.ToNumber(x.TrackNumber)).FirstOrDefault(); if (firstMetaData == null) + { return new OperationResult("Error Getting First MetaData") { Data = $"Unable to read Metadatas for Directory [{directory.FullName}]" }; + } + var artist = firstMetaData.Artist; foreach (var metaData in metaDatasForFilesInFolder.Where(x => x.Artist != artist)) { @@ -47,7 +50,10 @@ namespace Roadie.Library.Inspect.Plugins.Directory Console.WriteLine( $"╟ Setting Artist to [{artist}], was [{metaData.Artist}] on file [{metaData.FileInfo.Name}"); metaData.Artist = artist; - if (!Configuration.Inspector.IsInReadOnlyMode) TagsHelper.WriteTags(metaData, metaData.Filename); + if (!Configuration.Inspector.IsInReadOnlyMode) + { + TagsHelper.WriteTags(metaData, metaData.Filename); + } } data = $"Found [{found}] files, Modified [{modified}] files"; diff --git a/Roadie.Api.Library/Inspect/Plugins/Directory/EnsureReleaseConsistent.cs b/Roadie.Api.Library/Inspect/Plugins/Directory/EnsureReleaseConsistent.cs index 3d5ed16..209c350 100644 --- a/Roadie.Api.Library/Inspect/Plugins/Directory/EnsureReleaseConsistent.cs +++ b/Roadie.Api.Library/Inspect/Plugins/Directory/EnsureReleaseConsistent.cs @@ -39,7 +39,10 @@ namespace Roadie.Library.Inspect.Plugins.Directory Console.WriteLine( $"╟ Setting Release to [{release}], was [{metaData.Release}] on file [{metaData.FileInfo.Name}"); metaData.Release = release; - if (!Configuration.Inspector.IsInReadOnlyMode) TagsHelper.WriteTags(metaData, metaData.Filename); + if (!Configuration.Inspector.IsInReadOnlyMode) + { + TagsHelper.WriteTags(metaData, metaData.Filename); + } } data = $"Found [{found}] files, Modified [{modified}] files"; diff --git a/Roadie.Api.Library/Inspect/Plugins/Directory/EnsureYearConsistent.cs b/Roadie.Api.Library/Inspect/Plugins/Directory/EnsureYearConsistent.cs index 132e2c8..3d5ac2e 100644 --- a/Roadie.Api.Library/Inspect/Plugins/Directory/EnsureYearConsistent.cs +++ b/Roadie.Api.Library/Inspect/Plugins/Directory/EnsureYearConsistent.cs @@ -38,7 +38,10 @@ namespace Roadie.Library.Inspect.Plugins.Directory Console.WriteLine( $"╟ Setting Year to [{year}], was [{metaData.Year}] on file [{metaData.FileInfo.Name}"); metaData.Year = year; - if (!Configuration.Inspector.IsInReadOnlyMode) TagsHelper.WriteTags(metaData, metaData.Filename); + if (!Configuration.Inspector.IsInReadOnlyMode) + { + TagsHelper.WriteTags(metaData, metaData.Filename); + } } data = $"Found [{found}] files, Modified [{modified}] files"; diff --git a/Roadie.Api.Library/Inspect/Plugins/Directory/IInspectorDirectoryPlugin.cs b/Roadie.Api.Library/Inspect/Plugins/Directory/IInspectorDirectoryPlugin.cs index c5c7de0..15697c9 100644 --- a/Roadie.Api.Library/Inspect/Plugins/Directory/IInspectorDirectoryPlugin.cs +++ b/Roadie.Api.Library/Inspect/Plugins/Directory/IInspectorDirectoryPlugin.cs @@ -5,8 +5,11 @@ namespace Roadie.Library.Inspect.Plugins.Directory public interface IInspectorDirectoryPlugin { string Description { get; } + bool IsEnabled { get; } + bool IsPostProcessingPlugin { get; } + int Order { get; } OperationResult Process(DirectoryInfo directory); diff --git a/Roadie.Api.Library/Inspect/Plugins/File/CleanUpArtists.cs b/Roadie.Api.Library/Inspect/Plugins/File/CleanUpArtists.cs index 4c59c0c..9ef5a84 100644 --- a/Roadie.Api.Library/Inspect/Plugins/File/CleanUpArtists.cs +++ b/Roadie.Api.Library/Inspect/Plugins/File/CleanUpArtists.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Logging; +using MetadataExtractor; +using Microsoft.Extensions.Logging; using Roadie.Library.Caching; using Roadie.Library.Configuration; using Roadie.Library.Extensions; @@ -14,16 +15,15 @@ namespace Roadie.Library.Inspect.Plugins.File public override int Order => 5; - public CleanUpArtists(IRoadieSettings configuration, ICacheManager cacheManager, ILogger logger, - IID3TagsHelper tagsHelper) + public CleanUpArtists(IRoadieSettings configuration, ICacheManager cacheManager, ILogger logger, IID3TagsHelper tagsHelper) : base(configuration, cacheManager, logger, tagsHelper) { } public string CleanArtist(string artist, string trackArtist = null) { - artist = artist ?? string.Empty; - trackArtist = trackArtist ?? string.Empty; + artist ??= string.Empty; + trackArtist ??= string.Empty; var splitCharacter = AudioMetaData.ArtistSplitCharacter.ToString(); // Replace seperators with proper split character @@ -39,12 +39,21 @@ namespace Roadie.Library.Inspect.Plugins.File } if (!string.IsNullOrEmpty(result) && !string.IsNullOrEmpty(trackArtist)) { - result = result.Replace(splitCharacter + trackArtist + splitCharacter, "", StringComparison.OrdinalIgnoreCase); - result = result.Replace(trackArtist + splitCharacter, "", StringComparison.OrdinalIgnoreCase); - result = result.Replace(splitCharacter + trackArtist, "", StringComparison.OrdinalIgnoreCase); - result = result.Replace(trackArtist, "", StringComparison.OrdinalIgnoreCase); + result = result.Replace(splitCharacter + trackArtist + splitCharacter, string.Empty, StringComparison.OrdinalIgnoreCase); + result = result.Replace(trackArtist + splitCharacter, string.Empty, StringComparison.OrdinalIgnoreCase); + result = result.Replace(splitCharacter + trackArtist, string.Empty, StringComparison.OrdinalIgnoreCase); + result = result.Replace(trackArtist, string.Empty, StringComparison.OrdinalIgnoreCase); + } + if(Configuration.Processing.DoDetectFeatureFragments) + { + if(!string.IsNullOrWhiteSpace(result)) + { + if(result.HasFeaturingFragments()) + { + throw new RoadieProcessingException($"Artist name [{ result }] has Feature fragments."); + } + } } - return string.IsNullOrEmpty(result) ? null : result; } diff --git a/Roadie.Api.Library/Inspect/Plugins/File/CleanUpComments.cs b/Roadie.Api.Library/Inspect/Plugins/File/CleanUpComments.cs index 6dba96b..70ae4ca 100644 --- a/Roadie.Api.Library/Inspect/Plugins/File/CleanUpComments.cs +++ b/Roadie.Api.Library/Inspect/Plugins/File/CleanUpComments.cs @@ -22,8 +22,13 @@ namespace Roadie.Library.Inspect.Plugins.File { var result = new OperationResult(); if (Configuration.Processing.DoAudioCleanup) + { if (Configuration.Processing.DoClearComments) + { metaData.Comments = null; + } + } + result.Data = metaData; result.IsSuccess = true; return result; diff --git a/Roadie.Api.Library/Inspect/Plugins/File/CleanUpReleaseTitle.cs b/Roadie.Api.Library/Inspect/Plugins/File/CleanUpReleaseTitle.cs index 86a3e7b..88cb03e 100644 --- a/Roadie.Api.Library/Inspect/Plugins/File/CleanUpReleaseTitle.cs +++ b/Roadie.Api.Library/Inspect/Plugins/File/CleanUpReleaseTitle.cs @@ -27,9 +27,21 @@ namespace Roadie.Library.Inspect.Plugins.File var originalRelease = metaData.Release; metaData.Release = metaData.Release ?.CleanString(Configuration, Configuration.Processing.ReleaseRemoveStringsRegex).ToTitleCase(false); - if (string.IsNullOrEmpty(metaData.Release)) metaData.Release = originalRelease; + if (string.IsNullOrEmpty(metaData.Release)) + { + metaData.Release = originalRelease; + } + } + if (Configuration.Processing.DoDetectFeatureFragments) + { + if (!string.IsNullOrWhiteSpace(metaData?.Release)) + { + if (metaData.Release.HasFeaturingFragments()) + { + throw new RoadieProcessingException($"Release title [{ metaData?.Release }] has Feature fragments."); + } + } } - result.Data = metaData; result.IsSuccess = true; return result; diff --git a/Roadie.Api.Library/Inspect/Plugins/File/CleanUpTrackTitle.cs b/Roadie.Api.Library/Inspect/Plugins/File/CleanUpTrackTitle.cs index b1d6c9e..f300b74 100644 --- a/Roadie.Api.Library/Inspect/Plugins/File/CleanUpTrackTitle.cs +++ b/Roadie.Api.Library/Inspect/Plugins/File/CleanUpTrackTitle.cs @@ -27,9 +27,21 @@ namespace Roadie.Library.Inspect.Plugins.File var originalTitle = metaData.Title; metaData.Title = metaData.Title ?.CleanString(Configuration, Configuration.Processing.TrackRemoveStringsRegex).ToTitleCase(false); - if (string.IsNullOrEmpty(metaData.Title)) metaData.Title = originalTitle; + if (string.IsNullOrEmpty(metaData.Title)) + { + metaData.Title = originalTitle; + } + } + if (Configuration.Processing.DoDetectFeatureFragments) + { + if (!string.IsNullOrWhiteSpace(metaData?.Title)) + { + if (metaData.Release.HasFeaturingFragments()) + { + throw new RoadieProcessingException($"Track title [{ metaData?.Title }] has Feature fragments."); + } + } } - result.Data = metaData; result.IsSuccess = true; return result; diff --git a/Roadie.Api.Library/Inspect/Plugins/File/EnsureFileWriteable.cs b/Roadie.Api.Library/Inspect/Plugins/File/EnsureFileWriteable.cs index 39d7647..0347f6c 100644 --- a/Roadie.Api.Library/Inspect/Plugins/File/EnsureFileWriteable.cs +++ b/Roadie.Api.Library/Inspect/Plugins/File/EnsureFileWriteable.cs @@ -20,25 +20,27 @@ namespace Roadie.Library.Inspect.Plugins.File { } + private static FileAttributes RemoveAttribute(FileAttributes attributes, FileAttributes attributesToRemove) + { + return attributes & ~attributesToRemove; + } + public override OperationResult Process(AudioMetaData metaData) { var result = new OperationResult(); if (Configuration.Processing.DoAudioCleanup) + { if (metaData.FileInfo.IsReadOnly) { metaData.FileInfo.Attributes = RemoveAttribute(metaData.FileInfo.Attributes, FileAttributes.ReadOnly); Console.WriteLine($"╟ Removed read only attribute on file file [{metaData.FileInfo.Name}"); } + } result.Data = metaData; result.IsSuccess = true; return result; } - - private static FileAttributes RemoveAttribute(FileAttributes attributes, FileAttributes attributesToRemove) - { - return attributes & ~attributesToRemove; - } } } \ No newline at end of file diff --git a/Roadie.Api.Library/Inspect/Plugins/File/IInspectorFilePlugin.cs b/Roadie.Api.Library/Inspect/Plugins/File/IInspectorFilePlugin.cs index 5c077a5..41c4b1f 100644 --- a/Roadie.Api.Library/Inspect/Plugins/File/IInspectorFilePlugin.cs +++ b/Roadie.Api.Library/Inspect/Plugins/File/IInspectorFilePlugin.cs @@ -5,7 +5,9 @@ namespace Roadie.Library.Inspect.Plugins.File public interface IInspectorFilePlugin { string Description { get; } + bool IsEnabled { get; } + int Order { get; } OperationResult Process(AudioMetaData metaData); diff --git a/Roadie.Api.Library/Inspect/Plugins/PluginBase.cs b/Roadie.Api.Library/Inspect/Plugins/PluginBase.cs index 9609af6..0c9bd05 100644 --- a/Roadie.Api.Library/Inspect/Plugins/PluginBase.cs +++ b/Roadie.Api.Library/Inspect/Plugins/PluginBase.cs @@ -12,22 +12,21 @@ namespace Roadie.Library.Inspect.Plugins { public abstract class PluginBase { - public abstract string Description { get; } - - public abstract int Order { get; } + private Dictionary> CachedAudioDatas { get; } protected ICacheManager CacheManager { get; } protected IRoadieSettings Configuration { get; } - protected IEnumerable ListReplacements { get; } = new List - {" ; ", " ;", "; ", ";", ";", "\\"}; + protected IEnumerable ListReplacements { get; } = new List { " ; ", " ;", "; ", ";", ";", "\\" }; protected ILogger Logger { get; } protected IID3TagsHelper TagsHelper { get; } - private Dictionary> CachedAudioDatas { get; } + public abstract string Description { get; } + + public abstract int Order { get; } public PluginBase(IRoadieSettings configuration, ICacheManager cacheManager, ILogger logger, IID3TagsHelper tagsHelper) diff --git a/Roadie.Api.Library/Models/Artist.cs b/Roadie.Api.Library/Models/Artist.cs index 988648c..01954e6 100644 --- a/Roadie.Api.Library/Models/Artist.cs +++ b/Roadie.Api.Library/Models/Artist.cs @@ -1,5 +1,4 @@ -using Newtonsoft.Json; -using Roadie.Library.Models.Collections; +using Roadie.Library.Models.Collections; using Roadie.Library.Models.Playlists; using Roadie.Library.Models.Releases; using Roadie.Library.Models.Statistics; @@ -8,6 +7,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Roadie.Library.Models { @@ -48,7 +48,7 @@ namespace Roadie.Library.Models [IgnoreDataMember] public string ISNI { get; set; } - [JsonProperty("isniList")] + [JsonPropertyName("isniList")] public IEnumerable ISNIList { get diff --git a/Roadie.Api.Library/Models/BookmarkList.cs b/Roadie.Api.Library/Models/BookmarkList.cs index 2623fb6..9bb851d 100644 --- a/Roadie.Api.Library/Models/BookmarkList.cs +++ b/Roadie.Api.Library/Models/BookmarkList.cs @@ -1,9 +1,9 @@ -using Newtonsoft.Json; -using Roadie.Library.Enums; +using Roadie.Library.Enums; using Roadie.Library.Models.Collections; using Roadie.Library.Models.Playlists; using Roadie.Library.Models.Releases; using System; +using System.Text.Json.Serialization; namespace Roadie.Library.Models { diff --git a/Roadie.Api.Library/Models/Collections/CollectionReleaseList.cs b/Roadie.Api.Library/Models/Collections/CollectionReleaseList.cs index f6080a8..d2b2a65 100644 --- a/Roadie.Api.Library/Models/Collections/CollectionReleaseList.cs +++ b/Roadie.Api.Library/Models/Collections/CollectionReleaseList.cs @@ -1,5 +1,5 @@ -using Newtonsoft.Json; -using System; +using System; +using System.Text.Json.Serialization; namespace Roadie.Library.Models.Collections { @@ -7,7 +7,9 @@ namespace Roadie.Library.Models.Collections internal class CollectionReleaseList : EntityInfoModelBase { private string _listNumber; + public DataToken Artist { get; set; } + public string ArtistThumbnailUrl { get; set; } public string ListNumber @@ -16,10 +18,16 @@ namespace Roadie.Library.Models.Collections set => _listNumber = value; } - [JsonIgnore] public int ListNumberValue { get; set; } + [JsonIgnore] + public int ListNumberValue { get; set; } + public DataToken Release { get; set; } - [JsonIgnore] public DateTime? ReleaseDateDateTime { get; set; } + + [JsonIgnore] + public DateTime? ReleaseDateDateTime { get; set; } + public short? ReleaseRating { get; set; } + public string ReleaseThumbnailUrl { get; set; } public string ReleaseYear => ReleaseDateDateTime.HasValue diff --git a/Roadie.Api.Library/Models/DataToken.cs b/Roadie.Api.Library/Models/DataToken.cs index 8d755db..1e21365 100644 --- a/Roadie.Api.Library/Models/DataToken.cs +++ b/Roadie.Api.Library/Models/DataToken.cs @@ -1,7 +1,7 @@ using Mapster; -using Newtonsoft.Json; using Roadie.Library.Utility; using System; +using System.Text.Json.Serialization; namespace Roadie.Library.Models { diff --git a/Roadie.Api.Library/Models/EntityInfoModelBase.cs b/Roadie.Api.Library/Models/EntityInfoModelBase.cs index 2ad158b..0ee28e3 100644 --- a/Roadie.Api.Library/Models/EntityInfoModelBase.cs +++ b/Roadie.Api.Library/Models/EntityInfoModelBase.cs @@ -1,8 +1,8 @@ using Mapster; -using Newtonsoft.Json; using Roadie.Library.Utility; using System; using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace Roadie.Library.Models { @@ -26,9 +26,6 @@ namespace Roadie.Library.Models public DateTime? LastUpdated { get; set; } - [MaxLength(250)] - public virtual string SortName { get; set; } - /// /// Random int to sort when Random Request /// @@ -36,6 +33,9 @@ namespace Roadie.Library.Models [JsonIgnore] public int RandomSortId { get; set; } + [MaxLength(250)] + public virtual string SortName { get; set; } + public EntityInfoModelBase() { RandomSortId = StaticRandom.Instance.Next(); diff --git a/Roadie.Api.Library/Models/EntityModelBase.cs b/Roadie.Api.Library/Models/EntityModelBase.cs index 0e5abb1..b5b7c1f 100644 --- a/Roadie.Api.Library/Models/EntityModelBase.cs +++ b/Roadie.Api.Library/Models/EntityModelBase.cs @@ -1,5 +1,4 @@ using Mapster; -using Newtonsoft.Json; using Roadie.Library.Enums; using Roadie.Library.Utility; using System; @@ -7,6 +6,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Roadie.Library.Models { diff --git a/Roadie.Api.Library/Models/LabelList.cs b/Roadie.Api.Library/Models/LabelList.cs index 39519ea..9885373 100644 --- a/Roadie.Api.Library/Models/LabelList.cs +++ b/Roadie.Api.Library/Models/LabelList.cs @@ -1,5 +1,4 @@ -using Newtonsoft.Json; -using System; +using System; namespace Roadie.Library.Models { @@ -7,9 +6,13 @@ namespace Roadie.Library.Models public sealed class LabelList : EntityInfoModelBase { public int? ArtistCount { get; set; } + public DataToken Label { get; set; } + public int? ReleaseCount { get; set; } + public Image Thumbnail { get; set; } + public int? TrackCount { get; set; } public static LabelList FromDataLabel(Data.Label label, Image labelThumbnail) diff --git a/Roadie.Api.Library/Models/PlayActivityList.cs b/Roadie.Api.Library/Models/PlayActivityList.cs index dcb22a5..cdaa361 100644 --- a/Roadie.Api.Library/Models/PlayActivityList.cs +++ b/Roadie.Api.Library/Models/PlayActivityList.cs @@ -1,35 +1,52 @@ -using Newtonsoft.Json; -using Roadie.Library.Models.Users; +using Roadie.Library.Models.Users; using System; +using System.Text.Json.Serialization; namespace Roadie.Library.Models { [Serializable] public class PlayActivityList { + public DataToken Artist { get; set; } + public Image ArtistThumbnail { get; set; } public bool IsNowPlaying { get; set; } + public string PlayedDate => PlayedDateDateTime.HasValue ? PlayedDateDateTime.Value.ToString("s") : null; - [JsonIgnore] public DateTime? PlayedDateDateTime { get; set; } + [JsonIgnore] + public DateTime? PlayedDateDateTime { get; set; } + public string PlayedDay => PlayedDateDateTime.HasValue ? PlayedDateDateTime.Value.ToString("MM/dd/yyyy") : null; + public int? Rating { get; set; } + public DataToken Release { get; set; } + public string ReleasePlayUrl { get; set; } + public Image ReleaseThumbnail { get; set; } + public TrackList Track { get; set; } + public DataToken TrackArtist { get; set; } + public string TrackPlayUrl { get; set; } + public DataToken User { get; set; } + public int? UserRating { get; set; } + public Image UserThumbnail { get; set; } + public UserTrack UserTrack { get; set; } public override string ToString() { return $"User [{User}], Artist [{Artist}], Release [{Release}], Track [{Track}]"; } + } } \ No newline at end of file diff --git a/Roadie.Api.Library/Models/Player/PlayResult.cs b/Roadie.Api.Library/Models/Player/PlayResult.cs index 9a0cfe3..df1b2fe 100644 --- a/Roadie.Api.Library/Models/Player/PlayResult.cs +++ b/Roadie.Api.Library/Models/Player/PlayResult.cs @@ -1,8 +1,8 @@ -using Newtonsoft.Json; -using Roadie.Library.Configuration; +using Roadie.Library.Configuration; using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json; namespace Roadie.Library.Models.Player { @@ -11,14 +11,11 @@ namespace Roadie.Library.Models.Player { private string _rawTracks; - public string RawTracks => _rawTracks ?? (_rawTracks = JsonConvert.SerializeObject(Tracks)); + public string RawTracks => _rawTracks ?? (_rawTracks = JsonSerializer.Serialize(Tracks)); public string SiteName { get; set; } - public string TotalTrackTime - { - get { return TimeSpan.FromMilliseconds((double)Tracks.Sum(x => x.Duration)).ToString(@"hh\:mm\:ss"); } - } + public string TotalTrackTime => TimeSpan.FromMilliseconds((double)Tracks.Sum(x => x.Duration)).ToString(@"hh\:mm\:ss"); public string TrackCount => Tracks.Count().ToString("D3"); diff --git a/Roadie.Api.Library/Models/Releases/ReleaseLabelList.cs b/Roadie.Api.Library/Models/Releases/ReleaseLabelList.cs index 1f464f9..b72575b 100644 --- a/Roadie.Api.Library/Models/Releases/ReleaseLabelList.cs +++ b/Roadie.Api.Library/Models/Releases/ReleaseLabelList.cs @@ -1,5 +1,5 @@ -using Newtonsoft.Json; -using System; +using System; +using System.Text.Json.Serialization; namespace Roadie.Library.Models.Releases { @@ -7,10 +7,17 @@ namespace Roadie.Library.Models.Releases public sealed class ReleaseLabelList : EntityInfoModelBase { public string BeginDate => BeginDatedDateTime.HasValue ? BeginDatedDateTime.Value.ToString("s") : null; - [JsonIgnore] public DateTime? BeginDatedDateTime { get; set; } + + [JsonIgnore] + public DateTime? BeginDatedDateTime { get; set; } + public string CatalogNumber { get; set; } + public string EndDate => EndDatedDateTime.HasValue ? EndDatedDateTime.Value.ToString("s") : null; - [JsonIgnore] public DateTime? EndDatedDateTime { get; set; } + + [JsonIgnore] + public DateTime? EndDatedDateTime { get; set; } + public DataToken Label { get; set; } } } \ No newline at end of file diff --git a/Roadie.Api.Library/Models/Releases/ReleaseList.cs b/Roadie.Api.Library/Models/Releases/ReleaseList.cs index d363e6e..e1f7f05 100644 --- a/Roadie.Api.Library/Models/Releases/ReleaseList.cs +++ b/Roadie.Api.Library/Models/Releases/ReleaseList.cs @@ -1,11 +1,11 @@ using Mapster; -using Newtonsoft.Json; using Roadie.Library.Enums; using Roadie.Library.Models.Users; using Roadie.Library.Utility; using System; using System.Collections.Generic; using System.Diagnostics; +using System.Text.Json.Serialization; namespace Roadie.Library.Models.Releases { @@ -14,14 +14,20 @@ namespace Roadie.Library.Models.Releases public sealed class ReleaseList : EntityInfoModelBase { public DataToken Artist { get; set; } + public Image ArtistThumbnail { get; set; } + public decimal? Duration { get; set; } public string DurationTime { get { - if (!Duration.HasValue) return "--:--"; + if (!Duration.HasValue) + { + return "--:--"; + } + return new TimeInfo(Duration.Value).ToFullFormattedString(); } } @@ -41,6 +47,7 @@ namespace Roadie.Library.Models.Releases } public DateTime? LastPlayed { get; set; } + public LibraryStatus? LibraryStatus { get; set; } /// @@ -49,17 +56,26 @@ namespace Roadie.Library.Models.Releases public int? ListNumber { get; set; } public IEnumerable Media { get; set; } + public int? MediaCount { get; set; } + public double? Rank { get; set; } + public short? Rating { get; set; } + public DataToken Release { get; set; } public string ReleaseDate => ReleaseDateDateTime.HasValue ? ReleaseDateDateTime.Value.ToUniversalTime().ToString("yyyy-MM-dd") : null; - [JsonIgnore] public DateTime? ReleaseDateDateTime { get; set; } - [JsonIgnore] [AdaptIgnore] public string ReleaseName => Release?.Text; + [JsonIgnore] + public DateTime? ReleaseDateDateTime { get; set; } + + [JsonIgnore] + [AdaptIgnore] + public string ReleaseName => Release?.Text; + public string ReleasePlayUrl { get; set; } public string ReleaseYear => ReleaseDateDateTime.HasValue @@ -67,10 +83,15 @@ namespace Roadie.Library.Models.Releases : null; public Statuses? Status { get; set; } + public string StatusVerbose => (Status ?? Statuses.Missing).ToString(); + public Image Thumbnail { get; set; } + public int? TrackCount { get; set; } + public int? TrackPlayedCount { get; set; } + public UserRelease UserRating { get; set; } public static ReleaseList FromDataRelease(Data.Release release, Data.Artist artist, string baseUrl, diff --git a/Roadie.Api.Library/Models/ThirdPartyApi/Subsonic/Request.cs b/Roadie.Api.Library/Models/ThirdPartyApi/Subsonic/Request.cs index fbec7c9..7559e6b 100644 --- a/Roadie.Api.Library/Models/ThirdPartyApi/Subsonic/Request.cs +++ b/Roadie.Api.Library/Models/ThirdPartyApi/Subsonic/Request.cs @@ -1,8 +1,8 @@ -using Newtonsoft.Json; -using Roadie.Library.Extensions; +using Roadie.Library.Extensions; using Roadie.Library.Models.Pagination; using Roadie.Library.Utility; using System; +using System.Text.Json.Serialization; namespace Roadie.Library.Models.ThirdPartyApi.Subsonic { @@ -20,8 +20,16 @@ namespace Roadie.Library.Models.ThirdPartyApi.Subsonic { get { - if (string.IsNullOrEmpty(id)) return null; - if (id.StartsWith(ArtistIdIdentifier)) return SafeParser.ToGuid(id); + if (string.IsNullOrEmpty(id)) + { + return null; + } + + if (id.StartsWith(ArtistIdIdentifier)) + { + return SafeParser.ToGuid(id); + } + return null; } } @@ -40,8 +48,16 @@ namespace Roadie.Library.Models.ThirdPartyApi.Subsonic { get { - if (string.IsNullOrEmpty(id)) return null; - if (id.StartsWith(CollectionIdentifier)) return SafeParser.ToGuid(id); + if (string.IsNullOrEmpty(id)) + { + return null; + } + + if (id.StartsWith(CollectionIdentifier)) + { + return SafeParser.ToGuid(id); + } + return null; } } @@ -73,8 +89,12 @@ namespace Roadie.Library.Models.ThirdPartyApi.Subsonic // Default should be false (XML) get { - if (string.IsNullOrEmpty(f)) return false; - return f.ToLower().StartsWith("j"); + if (string.IsNullOrEmpty(f)) + { + return false; + } + + return f.StartsWith("j", StringComparison.OrdinalIgnoreCase); } } @@ -90,8 +110,16 @@ namespace Roadie.Library.Models.ThirdPartyApi.Subsonic { get { - if (string.IsNullOrEmpty(p)) return null; - if (p.StartsWith("enc:")) return p.ToLower().Replace("enc:", "").FromHexString(); + if (string.IsNullOrEmpty(p)) + { + return null; + } + + if (p.StartsWith("enc:")) + { + return p.ToLower().Replace("enc:", string.Empty).FromHexString(); + } + return p; } } @@ -100,8 +128,16 @@ namespace Roadie.Library.Models.ThirdPartyApi.Subsonic { get { - if (string.IsNullOrEmpty(id)) return null; - if (id.StartsWith(PlaylistdIdentifier)) return SafeParser.ToGuid(id); + if (string.IsNullOrEmpty(id)) + { + return null; + } + + if (id.StartsWith(PlaylistdIdentifier)) + { + return SafeParser.ToGuid(id); + } + return null; } } @@ -115,8 +151,16 @@ namespace Roadie.Library.Models.ThirdPartyApi.Subsonic { get { - if (string.IsNullOrEmpty(id)) return null; - if (id.StartsWith(ReleaseIdIdentifier)) return SafeParser.ToGuid(id); + if (string.IsNullOrEmpty(id)) + { + return null; + } + + if (id.StartsWith(ReleaseIdIdentifier)) + { + return SafeParser.ToGuid(id); + } + return null; } } @@ -145,8 +189,16 @@ namespace Roadie.Library.Models.ThirdPartyApi.Subsonic { get { - if (string.IsNullOrEmpty(id)) return null; - if (id.StartsWith(TrackIdIdentifier)) return SafeParser.ToGuid(id); + if (string.IsNullOrEmpty(id)) + { + return null; + } + + if (id.StartsWith(TrackIdIdentifier)) + { + return SafeParser.ToGuid(id); + } + return null; } } diff --git a/Roadie.Api.Library/Models/Track.cs b/Roadie.Api.Library/Models/Track.cs index 7613f7b..4be8a15 100644 --- a/Roadie.Api.Library/Models/Track.cs +++ b/Roadie.Api.Library/Models/Track.cs @@ -1,5 +1,4 @@ -using Newtonsoft.Json; -using Roadie.Library.Models.Releases; +using Roadie.Library.Models.Releases; using Roadie.Library.Models.Statistics; using Roadie.Library.Models.Users; using Roadie.Library.Utility; @@ -7,6 +6,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Roadie.Library.Models { @@ -17,33 +17,47 @@ namespace Roadie.Library.Models private IEnumerable _partTitles; - [MaxLength(50)] public string AmgId { get; set; } + [MaxLength(50)] + public string AmgId { get; set; } public ArtistList Artist { get; set; } - public IEnumerable Credits { get; set; } + public Image ArtistThumbnail { get; set; } + public IEnumerable Comments { get; set; } + + public IEnumerable Credits { get; set; } + public int Duration { get; set; } public string DurationTime { get { - if (Duration < 1) return "--:--"; + if (Duration < 1) + { + return "--:--"; + } + return new TimeInfo(Duration).ToFullFormattedString(); } } public long FileSize { get; set; } - [MaxLength(32)] public string Hash { get; set; } - [MaxLength(15)] public string ISRC { get; set; } + [MaxLength(32)] + public string Hash { get; set; } - [MaxLength(50)] public string LastFMId { get; set; } + [MaxLength(15)] + public string ISRC { get; set; } + + [MaxLength(50)] + public string LastFMId { get; set; } public DateTime? LastPlayed { get; set; } public Image MediumThumbnail { get; set; } + [MaxLength(100)] public string MusicBrainzId { get; set; } // When populated a "data:image" base64 byte array of an image to use as new Thumbnail @@ -60,7 +74,11 @@ namespace Roadie.Library.Models { if (_partTitles == null) { - if (string.IsNullOrEmpty(PartTitles)) return null; + if (string.IsNullOrEmpty(PartTitles)) + { + return null; + } + return PartTitles.Replace("|", "\n").Split("\n"); } @@ -70,7 +88,9 @@ namespace Roadie.Library.Models } public int PlayedCount { get; set; } + public short Rating { get; set; } + public ReleaseList Release { get; set; } public string ReleaseMediaId { get; set; } @@ -82,7 +102,10 @@ namespace Roadie.Library.Models public TrackStatistics Statistics { get; set; } public Image Thumbnail { get; set; } - [MaxLength(250)] [Required] public string Title { get; set; } + + [MaxLength(250)] + [Required] + public string Title { get; set; } /// /// Track Artist, not release artist. If this is present then the track has an artist different than the release. @@ -90,15 +113,15 @@ namespace Roadie.Library.Models public ArtistList TrackArtist { get; set; } public Image TrackArtistThumbnail { get; set; } + public DataToken TrackArtistToken { get; set; } + [Required] public short TrackNumber { get; set; } public string TrackPlayUrl { get; set; } + public UserTrack UserRating { get; set; } - public override string ToString() - { - return $"Id [{ Id }], Title [{ Title }]"; - } + public override string ToString() => $"Id [{ Id }], Title [{ Title }]"; } } \ No newline at end of file diff --git a/Roadie.Api.Library/Models/TrackList.cs b/Roadie.Api.Library/Models/TrackList.cs index 64f77de..93f0277 100644 --- a/Roadie.Api.Library/Models/TrackList.cs +++ b/Roadie.Api.Library/Models/TrackList.cs @@ -1,15 +1,15 @@ -using Newtonsoft.Json; +using Roadie.Library.Enums; using Roadie.Library.Models.Releases; using Roadie.Library.Models.Users; using Roadie.Library.Utility; using System; -using System.Linq; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Diagnostics; +using System.Linq; using System.Runtime.Serialization; +using System.Text.Json.Serialization; using Release = Roadie.Library.Data.Release; -using Roadie.Library.Enums; namespace Roadie.Library.Models { @@ -18,6 +18,7 @@ namespace Roadie.Library.Models public sealed class TrackList : EntityInfoModelBase { public ArtistList Artist { get; set; } + public int? Duration { get; set; } public string DurationTime => Duration.HasValue ? new TimeInfo(Duration.Value).ToFullFormattedString() : "--:--"; @@ -25,8 +26,11 @@ namespace Roadie.Library.Models public string DurationTimeShort => Duration.HasValue ? new TimeInfo(Duration.Value).ToShortFormattedString() : "--:--"; public int? FavoriteCount { get; set; } + public int? FileSize { get; set; } + public DateTime? LastPlayed { get; set; } + public int? MediaNumber { get; set; } [MaxLength(65535)] @@ -38,100 +42,84 @@ namespace Roadie.Library.Models { get { - if (string.IsNullOrEmpty(PartTitles)) return null; + if (string.IsNullOrEmpty(PartTitles)) + { + return null; + } + return PartTitles.Split('\n'); } } public int? PlayedCount { get; set; } - public short? Rating { get; set; } - public ReleaseList Release { get; set; } - [JsonIgnore] public DateTime? ReleaseDate { get; set; } - public short? ReleaseRating { get; set; } - public Image Thumbnail { get; set; } - public string Title { get; set; } - public DataToken Track { get; set; } - public ArtistList TrackArtist { get; set; } - [JsonIgnore] public string TrackId => Track?.Value; - [JsonIgnore] public string TrackName => Track?.Text; - [JsonIgnore] public string ReleaseName => Release?.Release?.Text; + public short? Rating { get; set; } + + public ReleaseList Release { get; set; } + + [JsonIgnore] + public DateTime? ReleaseDate { get; set; } + + [JsonIgnore] + public string ReleaseName => Release?.Release?.Text; + + public short? ReleaseRating { get; set; } + public Statuses? Status { get; set; } + public string StatusVerbose => (Status ?? Statuses.Missing).ToString(); + + public Image Thumbnail { get; set; } + + public string Title { get; set; } + + public DataToken Track { get; set; } + + public ArtistList TrackArtist { get; set; } + + [JsonIgnore] + public string TrackId => Track?.Value; + + [JsonIgnore] + public string TrackName => Track?.Text; + public int? TrackNumber { get; set; } + public string TrackPlayUrl { get; set; } + public UserTrack UserRating { get; set; } public int? Year { get { - if (ReleaseDate.HasValue) return ReleaseDate.Value.Year; + if (ReleaseDate.HasValue) + { + return ReleaseDate.Value.Year; + } + return null; } } - public int GetArtistReleaseHashCode() => this.Artist?.Artist?.Value.GetHashCode() + this.Release?.Release?.Value.GetHashCode() ?? 0; - - public override int GetHashCode() => this.Artist?.Artist?.Value.GetHashCode() + this.Release?.Release?.Value.GetHashCode() + this.Track?.Value.GetHashCode() ?? 0; - public static IEnumerable AllIndexesOfArtist(IEnumerable tracks, string artistId) { - if(tracks == null || !tracks.Any()) + if (tracks?.Any() != true) { return new int[0]; } - return tracks.Select((b, i) => b.Artist?.Artist?.Value == artistId ? i : -1).Where(i => i != -1).ToArray(); + return tracks.Select((b, i) => b.Artist?.Artist?.Value == artistId ? i : -1).Where(i => i != -1).ToArray(); } public static IEnumerable AllIndexesOfRelease(IEnumerable tracks, string releaseId) { - if (tracks == null || !tracks.Any()) + if (tracks?.Any() != true) { return new int[0]; } return tracks.Select((b, i) => b.Release?.Release?.Value == releaseId ? i : -1).Where(i => i != -1).ToArray(); } - - /// - /// Ensure that the given list is sorted so that Artist and Release don't repeat in sequence. - /// - public static IEnumerable Shuffle(IEnumerable tracks) - { - - var shuffledTracks = new List(); - var skippedTracks = new List(); - foreach(var track in tracks) - { - var trackArtist = track.Artist?.Artist?.Value ?? track.Release?.Artist?.Value; - var trackRelease = track.Release?.Release?.Value; - if (!shuffledTracks.Any(x => x.Artist?.Artist?.Value == trackArtist && - x.Release?.Release?.Value != trackRelease)) - { - shuffledTracks.Add(track); - } - else - { - skippedTracks.Add(track); - } - } - var result = new List(shuffledTracks); - while (skippedTracks.ToList().Any()) - { - var st = skippedTracks.First(); - var trackArtist = st.Artist?.Artist?.Value ?? st.Release?.Artist?.Value; - var insertAt = AllIndexesOfArtist(result, trackArtist).Last() + 2; - if(insertAt < result.Count() - 1) - { - result.Insert(insertAt, st); - skippedTracks.Remove(st); - } - } - return result; - } - - public static TrackList FromDataTrack(string trackPlayUrl, Data.Track track, int releaseMediaNumber, @@ -173,6 +161,44 @@ namespace Roadie.Library.Models }; } + public int GetArtistReleaseHashCode() => this.Artist?.Artist?.Value.GetHashCode() + this.Release?.Release?.Value.GetHashCode() ?? 0; + public override int GetHashCode() => this.Artist?.Artist?.Value.GetHashCode() + this.Release?.Release?.Value.GetHashCode() + this.Track?.Value.GetHashCode() ?? 0; + + /// + /// Ensure that the given list is sorted so that Artist and Release don't repeat in sequence. + /// + public static IEnumerable Shuffle(IEnumerable tracks) + { + var shuffledTracks = new List(); + var skippedTracks = new List(); + foreach (var track in tracks) + { + var trackArtist = track.Artist?.Artist?.Value ?? track.Release?.Artist?.Value; + var trackRelease = track.Release?.Release?.Value; + if (!shuffledTracks.Any(x => x.Artist?.Artist?.Value == trackArtist && + x.Release?.Release?.Value != trackRelease)) + { + shuffledTracks.Add(track); + } + else + { + skippedTracks.Add(track); + } + } + var result = new List(shuffledTracks); + while (skippedTracks.ToList().Count > 0) + { + var st = skippedTracks[0]; + var trackArtist = st.Artist?.Artist?.Value ?? st.Release?.Artist?.Value; + var insertAt = AllIndexesOfArtist(result, trackArtist).Last() + 2; + if (insertAt < result.Count() - 1) + { + result.Insert(insertAt, st); + skippedTracks.Remove(st); + } + } + return result; + } } } \ No newline at end of file diff --git a/Roadie.Api.Library/Models/Users/User.cs b/Roadie.Api.Library/Models/Users/User.cs index 2576979..99dffd7 100644 --- a/Roadie.Api.Library/Models/Users/User.cs +++ b/Roadie.Api.Library/Models/Users/User.cs @@ -3,6 +3,7 @@ using Roadie.Library.Models.Statistics; using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; namespace Roadie.Library.Models.Users { @@ -11,7 +12,9 @@ namespace Roadie.Library.Models.Users { public const string ActionKeyUserRated = "__userrated__"; public const string DefaultIncludes = "stats"; - [MaxLength(100)] public string ApiToken { get; set; } + + [MaxLength(100)] + public string ApiToken { get; set; } public Image Avatar { get; set; } @@ -20,7 +23,12 @@ namespace Roadie.Library.Models.Users /// public string AvatarData { get; set; } - [Required] [MaxLength(100)] public string ConcurrencyStamp { get; set; } + [Required] + [MaxLength(100)] + public string ConcurrencyStamp { get; set; } + + public short? DefaultRowsPerPage { get; set; } + public bool DoUseHtmlPlayer { get; set; } [Required] @@ -28,18 +36,34 @@ namespace Roadie.Library.Models.Users [DataType(DataType.EmailAddress)] public string Email { get; set; } - [MaxLength(500)] public string FtpDirectory { get; set; } + [MaxLength(500)] + public string FtpDirectory { get; set; } - [MaxLength(500)] public string FtpPassword { get; set; } + [MaxLength(500)] + public string FtpPassword { get; set; } - [MaxLength(250)] public string FtpUrl { get; set; } + [MaxLength(250)] + [DataType(DataType.Url)] + public string FtpUrl { get; set; } - [MaxLength(50)] public string FtpUsername { get; set; } + [MaxLength(50)] + public string FtpUsername { get; set; } + + [JsonPropertyName("UserDbId")] public new int? Id { get; set; } + public bool IsAdmin { get; set; } + public bool IsEditor { get; set; } + public bool IsPrivate { get; set; } + public DateTime LastApiAccess { get; set; } + + public DateTime LastLogin { get; set; } + + public Image MediumThumbnail { get; set; } + /// /// Posted password only used when changing password from profile edits /// @@ -55,23 +79,34 @@ namespace Roadie.Library.Models.Users public string PasswordConfirmation { get; set; } public short? PlayerTrackLimit { get; set; } - [MaxLength(65535)] public string Profile { get; set; } + + [MaxLength(65535)] + public string Profile { get; set; } + public short? RandomReleaseLimit { get; set; } + public short? RecentlyPlayedLimit { get; set; } + public bool RemoveTrackFromQueAfterPlayed { get; set; } - [NotMapped] [AdaptIgnore] public UserStatistics Statistics { get; set; } - [StringLength(50)] [Required] public string Timeformat { get; set; } - [MaxLength(50)] [Required] public string Timezone { get; set; } + [NotMapped] + [AdaptIgnore] + public UserStatistics Statistics { get; set; } - [AdaptMember("RoadieId")] public Guid UserId { get; set; } + [StringLength(50)] + [Required] + public string Timeformat { get; set; } - [Required] [MaxLength(20)] public string UserName { get; set; } - public Image MediumThumbnail { get; set; } + [MaxLength(50)] + [Required] + public string Timezone { get; set; } - public DateTime LastLogin { get; set; } - public DateTime LastApiAccess { get; set; } - public short? DefaultRowsPerPage { get; set; } + [AdaptMember("RoadieId")] + public Guid UserId { get; set; } + + [Required] + [MaxLength(20)] + public string UserName { get; set; } public override string ToString() => $"Id [{Id}], RoadieId [{UserId}], UserName [{UserName}]"; } diff --git a/Roadie.Api.Library/OperationResult.cs b/Roadie.Api.Library/OperationResult.cs index 470a3a2..bb688b8 100644 --- a/Roadie.Api.Library/OperationResult.cs +++ b/Roadie.Api.Library/OperationResult.cs @@ -1,7 +1,7 @@ -using Newtonsoft.Json; -using System; +using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Serialization; using System.Xml.Serialization; namespace Roadie.Library @@ -10,6 +10,7 @@ namespace Roadie.Library public class OperationResult { private List _errors; + private List _messages; [XmlIgnore] @@ -22,12 +23,15 @@ namespace Roadie.Library /// /// Client friendly exceptions /// - [JsonProperty("errors")] + [JsonPropertyName("errors")] public IEnumerable AppExceptions { get { - if (Errors == null || !Errors.Any()) return null; + if (Errors?.Any() != true) + { + return null; + } return Errors.Select(x => new AppException(x.Message)); } @@ -41,7 +45,8 @@ namespace Roadie.Library [JsonIgnore] public IEnumerable Errors { get; set; } - [JsonIgnore] public bool IsAccessDeniedResult { get; set; } + [JsonIgnore] + public bool IsAccessDeniedResult { get; set; } [JsonIgnore] public bool IsNotFoundResult { get; set; } @@ -57,29 +62,13 @@ namespace Roadie.Library public OperationResult(IEnumerable messages = null) { - if (messages != null && messages.Any()) + if (messages?.Any() == true) { AdditionalData = new Dictionary(); messages.ToList().ForEach(AddMessage); } } - public OperationResult(bool isNotFoundResult, IEnumerable messages = null) - { - IsNotFoundResult = isNotFoundResult; - if (messages != null && messages.Any()) - { - AdditionalData = new Dictionary(); - messages.ToList().ForEach(AddMessage); - } - } - - public OperationResult(bool isNotFoundResult, string message) - { - IsNotFoundResult = isNotFoundResult; - AddMessage(message); - } - public OperationResult(string message = null) { AdditionalData = new Dictionary(); @@ -91,6 +80,22 @@ namespace Roadie.Library AddError(error); } + public OperationResult(bool isNotFoundResult, IEnumerable messages = null) + { + IsNotFoundResult = isNotFoundResult; + if (messages?.Any() == true) + { + AdditionalData = new Dictionary(); + messages.ToList().ForEach(AddMessage); + } + } + + public OperationResult(bool isNotFoundResult, string message) + { + IsNotFoundResult = isNotFoundResult; + AddMessage(message); + } + public OperationResult(string message = null, Exception error = null) { AddMessage(message); @@ -101,9 +106,7 @@ namespace Roadie.Library { if (exception != null) { - if (_errors == null) _errors = new List(); - - _errors.Add(exception); + (_errors ?? (_errors = new List())).Add(exception); } } @@ -111,9 +114,7 @@ namespace Roadie.Library { if (!string.IsNullOrEmpty(message)) { - if (_messages == null) _messages = new List(); - - _messages.Add(message); + (_messages ?? (_messages = new List())).Add(message); } } } diff --git a/Roadie.Api.Library/Roadie.Library.csproj b/Roadie.Api.Library/Roadie.Library.csproj index 77afb27..c67f074 100644 --- a/Roadie.Api.Library/Roadie.Library.csproj +++ b/Roadie.Api.Library/Roadie.Library.csproj @@ -18,17 +18,19 @@ + - - - - + + + + + - + - - + + @@ -39,6 +41,7 @@ + diff --git a/Roadie.Api.Library/RoadieProcessingException.cs b/Roadie.Api.Library/RoadieProcessingException.cs new file mode 100644 index 0000000..bba5af1 --- /dev/null +++ b/Roadie.Api.Library/RoadieProcessingException.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Roadie.Library +{ + public sealed class RoadieProcessingException : Exception + { + public RoadieProcessingException() : base() + { + } + + public RoadieProcessingException(string message) + : base(message) + { + } + + public RoadieProcessingException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/Roadie.Api.Library/SearchEngines/MetaData/Audio/AudioMetaData.cs b/Roadie.Api.Library/SearchEngines/MetaData/Audio/AudioMetaData.cs index f2ec034..e7d84b7 100644 --- a/Roadie.Api.Library/SearchEngines/MetaData/Audio/AudioMetaData.cs +++ b/Roadie.Api.Library/SearchEngines/MetaData/Audio/AudioMetaData.cs @@ -1,10 +1,10 @@ -using Newtonsoft.Json; -using Roadie.Library.Extensions; +using Roadie.Library.Extensions; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Text.Json.Serialization; namespace Roadie.Library.MetaData.Audio { @@ -14,10 +14,11 @@ namespace Roadie.Library.MetaData.Audio public sealed class AudioMetaData : IAudioMetaData { public const char ArtistSplitCharacter = '/'; - public const int MinimumYearValue = 1900; public const string SoundTrackArtist = "Sound Tracks"; + private string _artist; + private bool _doModifyArtistNameOnGet = true; private FileInfo _fileInfo; @@ -43,15 +44,20 @@ namespace Roadie.Library.MetaData.Audio { get { - if (_doModifyArtistNameOnGet) - if (!string.IsNullOrEmpty(_artist) && _artist.Contains(ArtistSplitCharacter.ToString())) - return _artist.Split(ArtistSplitCharacter).First(); + if (_doModifyArtistNameOnGet && !string.IsNullOrEmpty(_artist) && _artist.Contains(ArtistSplitCharacter.ToString())) + { + return _artist.Split(ArtistSplitCharacter)[0]; + } + return _artist; } set { _artist = value; - if (!string.IsNullOrEmpty(_artist)) _artist = _artist.Replace(';', ArtistSplitCharacter); + if (!string.IsNullOrEmpty(_artist)) + { + _artist = _artist.Replace(';', ArtistSplitCharacter); + } } } @@ -66,12 +72,36 @@ namespace Roadie.Library.MetaData.Audio get { var result = AudioMetaDataWeights.None; - if (!string.IsNullOrEmpty(Artist)) result |= AudioMetaDataWeights.Artist; - if (!string.IsNullOrEmpty(Title)) result |= AudioMetaDataWeights.Time; - if ((Year ?? 0) > 1) result |= AudioMetaDataWeights.Year; - if ((TrackNumber ?? 0) > 1) result |= AudioMetaDataWeights.TrackNumber; - if ((TotalTrackNumbers ?? 0) > 1) result |= AudioMetaDataWeights.TrackTotalNumber; - if (TotalSeconds > 1) result |= AudioMetaDataWeights.Time; + if (!string.IsNullOrEmpty(Artist)) + { + result |= AudioMetaDataWeights.Artist; + } + + if (!string.IsNullOrEmpty(Title)) + { + result |= AudioMetaDataWeights.Time; + } + + if ((Year ?? 0) > 1) + { + result |= AudioMetaDataWeights.Year; + } + + if ((TrackNumber ?? 0) > 1) + { + result |= AudioMetaDataWeights.TrackNumber; + } + + if ((TotalTrackNumbers ?? 0) > 1) + { + result |= AudioMetaDataWeights.TrackTotalNumber; + } + + if (TotalSeconds > 1) + { + result |= AudioMetaDataWeights.Time; + } + return result; } } @@ -90,7 +120,11 @@ namespace Roadie.Library.MetaData.Audio { get { - if (string.IsNullOrEmpty(Filename)) return null; + if (string.IsNullOrEmpty(Filename)) + { + return null; + } + return Path.GetDirectoryName(Filename); } } @@ -105,7 +139,8 @@ namespace Roadie.Library.MetaData.Audio /// public string DiscSubTitle { get; set; } - [JsonIgnore] public FileInfo FileInfo => _fileInfo ?? (_fileInfo = new FileInfo(Filename)); + [JsonIgnore] + public FileInfo FileInfo => _fileInfo ?? (_fileInfo = new FileInfo(Filename)); /// /// Full filename to the file used to get this AudioMetaData @@ -114,7 +149,8 @@ namespace Roadie.Library.MetaData.Audio public ICollection Genres { get; set; } - [JsonIgnore] public IEnumerable Images { get; set; } + [JsonIgnore] + public IEnumerable Images { get; set; } public string ISRC { get; internal set; } @@ -122,10 +158,13 @@ namespace Roadie.Library.MetaData.Audio { get { - if (Genres != null && Genres.Any()) + if (Genres?.Any() == true) { var soundtrackGenres = new List { "24", "soundtrack" }; - if (Genres.Intersect(soundtrackGenres, StringComparer.OrdinalIgnoreCase).Any()) return true; + if (Genres.Intersect(soundtrackGenres, StringComparer.OrdinalIgnoreCase).Any()) + { + return true; + } } return false; @@ -142,7 +181,7 @@ namespace Roadie.Library.MetaData.Audio Release = Release == null ? null : Release.Equals("Unknown Release") ? null : Release; if (!string.IsNullOrEmpty(Title)) { - var trackNumberTitle = string.Format("Track {0}", TrackNumber); + var trackNumberTitle = $"Track {TrackNumber}"; Title = Title == trackNumberTitle ? null : Title; } @@ -193,7 +232,10 @@ namespace Roadie.Library.MetaData.Audio set { _title = value; - if (IsSoundTrack) Artist = SoundTrackArtist; + if (IsSoundTrack) + { + Artist = SoundTrackArtist; + } } } @@ -206,7 +248,11 @@ namespace Roadie.Library.MetaData.Audio { get { - if (Time == null) return 0; + if (Time == null) + { + return 0; + } + return Time.Value.TotalSeconds; } } @@ -225,9 +271,11 @@ namespace Roadie.Library.MetaData.Audio { string result = null; if (!string.IsNullOrEmpty(_trackArtist)) - result = _trackArtist.Split(ArtistSplitCharacter).First().ToTitleCase(); - result = !_artist?.Equals(result, StringComparison.OrdinalIgnoreCase) ?? false ? result : null; - return result; + { + result = _trackArtist.Split(ArtistSplitCharacter)[0].ToTitleCase(); + } + + return !String.Equals(_artist, result, StringComparison.OrdinalIgnoreCase) ? result : null; } set => _trackArtist = value; } @@ -242,17 +290,33 @@ namespace Roadie.Library.MetaData.Audio { get { - if (string.IsNullOrEmpty(_trackArtist)) return new string[0]; + if (string.IsNullOrEmpty(_trackArtist)) + { + return new string[0]; + } + if (!_trackArtist.Contains(ArtistSplitCharacter.ToString())) { - if (string.IsNullOrEmpty(TrackArtist)) return new string[0]; + if (string.IsNullOrEmpty(TrackArtist)) + { + return new string[0]; + } + return new string[1] { TrackArtist }; } if (!string.IsNullOrEmpty(_artist) || !string.IsNullOrEmpty(_trackArtist)) - if (!_artist.Equals(_trackArtist, StringComparison.OrdinalIgnoreCase)) - return _trackArtist.Split(ArtistSplitCharacter).Where(x => !string.IsNullOrEmpty(x)) - .Select(x => x.ToTitleCase()).OrderBy(x => x).ToArray(); + { + if (!String.Equals(_artist, _trackArtist, StringComparison.OrdinalIgnoreCase)) + { + return _trackArtist.Split(ArtistSplitCharacter) + .Where(x => !string.IsNullOrEmpty(x)) + .Select(x => x.ToTitleCase()) + .OrderBy(x => x) + .ToArray(); + } + } + return new string[0]; } } @@ -280,8 +344,11 @@ namespace Roadie.Library.MetaData.Audio public override bool Equals(object obj) { - var item = obj as AudioMetaData; - if (item == null) return false; + if (!(obj is AudioMetaData item)) + { + return false; + } + return item.GetHashCode() == GetHashCode(); } @@ -290,12 +357,12 @@ namespace Roadie.Library.MetaData.Audio unchecked { var hash = 17; - hash = hash * 23 + Artist.GetHashCode(); - hash = hash * 23 + Release.GetHashCode(); - hash = hash * 23 + Title.GetHashCode(); - hash = hash * 23 + TrackNumber.GetHashCode(); - hash = hash * 23 + AudioBitrate.GetHashCode(); - hash = hash * 23 + AudioSampleRate.GetHashCode(); + hash = (hash * 23) + Artist.GetHashCode(); + hash = (hash * 23) + Release.GetHashCode(); + hash = (hash * 23) + Title.GetHashCode(); + hash = (hash * 23) + TrackNumber.GetHashCode(); + hash = (hash * 23) + AudioBitrate.GetHashCode(); + hash = (hash * 23) + AudioSampleRate.GetHashCode(); return hash; } } @@ -314,9 +381,17 @@ namespace Roadie.Library.MetaData.Audio { var result = $"IsValid: {IsValid}{(IsSoundTrack ? " [SoundTrack ]" : string.Empty)}, ValidWeight {ValidWeight}, Artist: {Artist}"; - if (!string.IsNullOrEmpty(TrackArtist)) result += $", TrackArtist: {TrackArtist}"; + if (!string.IsNullOrEmpty(TrackArtist)) + { + result += $", TrackArtist: {TrackArtist}"; + } + result += $", Release: {Release}, TrackNumber: {TrackNumber}, TrackTotal: {TotalTrackNumbers}"; - if (TotalDiscCount > 1) result += $", Disc: {Disc}/{TotalDiscCount}"; + if (TotalDiscCount > 1) + { + result += $", Disc: {Disc}/{TotalDiscCount}"; + } + result += $", Title: {Title}, Year: {Year}, Duration: {(Time == null ? "-" : Time.Value.ToString())}"; return result; } diff --git a/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/CoverArtEntities.cs b/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/CoverArtEntities.cs index c702ca1..3cf0b4a 100644 --- a/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/CoverArtEntities.cs +++ b/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/CoverArtEntities.cs @@ -13,7 +13,6 @@ public string comment { get; set; } public int edit { get; set; } public bool front { get; set; } - public string id { get; set; } public string image { get; set; } public Thumbnails thumbnails { get; set; } public string[] types { get; set; } diff --git a/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/Entities.cs b/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/Entities.cs index 58fe118..e1fc59b 100644 --- a/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/Entities.cs +++ b/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/Entities.cs @@ -1,7 +1,7 @@ -using Newtonsoft.Json; -using System; +using System; using System.Collections.Generic; using System.Diagnostics; +using System.Text.Json.Serialization; namespace Roadie.Library.MetaData.MusicBrainz { @@ -13,51 +13,59 @@ namespace Roadie.Library.MetaData.MusicBrainz public class CoverArtArchive { public bool artwork { get; set; } + public bool back { get; set; } + public int? count { get; set; } public bool darkened { get; set; } + public bool front { get; set; } } public class Label { public List aliases { get; set; } + public string disambiguation { get; set; } + + public NameAndCount[] genres { get; set; } + public string id { get; set; } - [JsonProperty(PropertyName = "label-code")] + [JsonPropertyName("label-code")] public int? labelcode { get; set; } public string name { get; set; } - [JsonProperty(PropertyName = "sort-name")] + [JsonPropertyName("sort-name")] public string sortname { get; set; } public NameAndCount[] tags { get; set; } - public NameAndCount[] genres { get; set; } } [Serializable] public class LabelInfo { - [JsonProperty(PropertyName = "catalog-number")] + [JsonPropertyName("catalog-number")] public string catalognumber { get; set; } public Label label { get; set; } } - [Serializable] public class Medium { public object format { get; set; } + public int? position { get; set; } + public string title { get; set; } - [JsonProperty(PropertyName = "track-count")] + [JsonPropertyName("track-count")] public short? trackcount { get; set; } - [JsonProperty(PropertyName = "track-offset")] + + [JsonPropertyName("track-offset")] public int? trackoffset { get; set; } public List tracks { get; set; } @@ -67,37 +75,57 @@ namespace Roadie.Library.MetaData.MusicBrainz public class Recording { public List aliases { get; set; } + public string disambiguation { get; set; } - public string id { get; set; } - public int? length { get; set; } - public string title { get; set; } - public bool video { get; set; } - public NameAndCount[] tags { get; set; } + public NameAndCount[] genres { get; set; } + + public string id { get; set; } + + public int? length { get; set; } + + public NameAndCount[] tags { get; set; } + + public string title { get; set; } + + public bool video { get; set; } } [Serializable] public class Relation { public List attributes { get; set; } + public AttributeValues attributevalues { get; set; } + public object begin { get; set; } + public string direction { get; set; } + public object end { get; set; } - public bool ended { get; set; } + + public bool? ended { get; set; } + public string sourcecredit { get; set; } + public string targetcredit { get; set; } + public string targettype { get; set; } + public string type { get; set; } + public string typeid { get; set; } + public MbUrl url { get; set; } } [Serializable] public class RepositoryRelease { - public int Id { get; set; } public string ArtistMbId { get; set; } + + public int Id { get; set; } + public Release Release { get; set; } } @@ -106,46 +134,61 @@ namespace Roadie.Library.MetaData.MusicBrainz public class Release { public List aliases { get; set; } + + [JsonPropertyName("artist-credits")] + public Artist[] artistcredits { get; set; } + public string asin { get; set; } + public string barcode { get; set; } + public string country { get; set; } - [JsonProperty(PropertyName = "cover-art-archive")] + [JsonPropertyName("cover-art-archive")] public CoverArtArchive coverartarchive { get; set; } public string coverThumbnailUrl { get; set; } + public string date { get; set; } + public string disambiguation { get; set; } + public string id { get; set; } + public List imageUrls { get; set; } - [JsonProperty(PropertyName = "label-info")] + [JsonPropertyName("label-info")] public List labelinfo { get; set; } public List media { get; set; } + public string packaging { get; set; } + public string quality { get; set; } + public List relations { get; set; } - [JsonProperty(PropertyName = "release-events")] + [JsonPropertyName("release-events")] public List releaseevents { get; set; } - [JsonProperty(PropertyName = "release-group")] + + [JsonPropertyName("release-group")] public ReleaseGroup releasegroup { get; set; } + public string status { get; set; } - [JsonProperty(PropertyName = "text-representation")] + + [JsonPropertyName("text-representation")] public TextRepresentation textrepresentation { get; set; } + public string title { get; set; } - [JsonProperty("artist-credits")] - public Artist[] artistcredits { get; set; } } [Serializable] public class ReleaseBrowseResult { - [JsonProperty(PropertyName = "release-count")] + [JsonPropertyName("release-count")] public int? releasecount { get; set; } - [JsonProperty(PropertyName = "release-offset")] + [JsonPropertyName("release-offset")] public int? releaseoffset { get; set; } public List releases { get; set; } @@ -163,22 +206,31 @@ namespace Roadie.Library.MetaData.MusicBrainz public class ReleaseGroup { public List aliases { get; set; } + public string disambiguation { get; set; } - [JsonProperty("first-release-date")] + + [JsonPropertyName("first-release-date")] public string firstreleasedate { get; set; } - public string id { get; set; } - [JsonProperty("primary-type")] - public string primarytype { get; set; } - public List secondarytypes { get; set; } - public string title { get; set; } - public NameAndCount[] tags { get; set; } + public NameAndCount[] genres { get; set; } + + public string id { get; set; } + + [JsonPropertyName("primary-type")] + public string primarytype { get; set; } + + public List secondarytypes { get; set; } + + public NameAndCount[] tags { get; set; } + + public string title { get; set; } } [Serializable] public class TextRepresentation { public string language { get; set; } + public string script { get; set; } } @@ -186,20 +238,23 @@ namespace Roadie.Library.MetaData.MusicBrainz public class Track { public string id { get; set; } + public int? length { get; set; } public string number { get; set; } - public string position { get; set; } + + public int? position { get; set; } + public Recording recording { get; set; } public string title { get; set; } - } [Serializable] public class MbUrl { public string id { get; set; } + public string resource { get; set; } } } \ No newline at end of file diff --git a/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/MusicBrainzProvider.cs b/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/MusicBrainzProvider.cs index d7a106f..6774344 100644 --- a/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/MusicBrainzProvider.cs +++ b/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/MusicBrainzProvider.cs @@ -88,7 +88,7 @@ namespace Roadie.Library.MetaData.MusicBrainz Release = release.title, Title = track.title, Time = track.length.HasValue ? (TimeSpan?)TimeSpan.FromMilliseconds(track.length.Value) : null, - TrackNumber = SafeParser.ToNumber(track.position ?? track.number) ?? 0, + TrackNumber = SafeParser.ToNumber(track.position) ?? SafeParser.ToNumber(track.number) ?? 0, Disc = media.position, Year = date > 0 ? (int?)date : null, TotalTrackNumbers = media.trackcount diff --git a/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/MusicBrainzRequestHelper.cs b/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/MusicBrainzRequestHelper.cs index 0b01eb1..246cf53 100644 --- a/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/MusicBrainzRequestHelper.cs +++ b/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/MusicBrainzRequestHelper.cs @@ -1,8 +1,8 @@ -using Newtonsoft.Json; -using Roadie.Library.Utility; +using Roadie.Library.Utility; using System; using System.Diagnostics; using System.Net; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -48,6 +48,7 @@ namespace Roadie.Library.MetaData.MusicBrainz { var tryCount = 0; var result = default(T); + string downloadedString = null; while (result == null && tryCount < MaxRetries) { try @@ -55,7 +56,11 @@ namespace Roadie.Library.MetaData.MusicBrainz using (var webClient = new WebClient()) { webClient.Headers.Add("user-agent", WebHelper.UserAgent); - result = JsonConvert.DeserializeObject(await webClient.DownloadStringTaskAsync(new Uri(url)).ConfigureAwait(false)); + downloadedString = await webClient.DownloadStringTaskAsync(new Uri(url)).ConfigureAwait(false); + if (!string.IsNullOrWhiteSpace(downloadedString)) + { + result = JsonSerializer.Deserialize(downloadedString); + } } } catch (WebException ex) @@ -69,7 +74,7 @@ namespace Roadie.Library.MetaData.MusicBrainz } catch (Exception ex) { - Trace.WriteLine($"GetAsync: [{ ex.ToString() }]", "Warning"); + Trace.WriteLine($"GetAsync: DownloadedString [{ downloadedString }], Exception: [{ ex }]", "Warning"); Thread.Sleep(100); } finally diff --git a/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/ResultEntities.cs b/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/ResultEntities.cs index 8f3aa74..342096a 100644 --- a/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/ResultEntities.cs +++ b/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/ResultEntities.cs @@ -1,140 +1,194 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; +using System; using System.Diagnostics; -using System.Text; +using System.Text.Json.Serialization; namespace Roadie.Library.MetaData.MusicBrainz { [Serializable] public class ArtistResult { - public DateTime created { get; set; } - public int count { get; set; } - public int offset { get; set; } public Artist[] artists { get; set; } + + public int count { get; set; } + + public DateTime created { get; set; } + + public int offset { get; set; } } [Serializable] public class RepositoryArtist { - public int Id { get; set; } - public string ArtistName { get; set; } - public string ArtistMbId { get; set; } public Artist Artist { get; set; } + + public string ArtistMbId { get; set; } + + public string ArtistName { get; set; } + + public int Id { get; set; } } [DebuggerDisplay("name: {name}")] [Serializable] public class Artist { - public string id { get; set; } - public string type { get; set; } - [JsonProperty("type-id")] - public string typeid { get; set; } - public int score { get; set; } - public string name { get; set; } - [JsonProperty("sort-name")] - public string sortname { get; set; } - public string gender { get; set; } - public string country { get; set; } - public Area area { get; set; } - [JsonProperty("begin_area")] - public BeginAndEndArea beginarea { get; set; } - [JsonProperty("end_area")] - public BeginAndEndArea endarea { get; set; } - public string[] ipis { get; set; } - [JsonProperty("life-span")] - public LifeSpan lifespan { get; set; } public Alias[] aliases { get; set; } - public NameAndCount[] tags { get; set; } - public IsniList[] isnilist { get; set; } + + public Area area { get; set; } + + [JsonPropertyName("begin_area")] + public BeginAndEndArea beginarea { get; set; } + + public string country { get; set; } + + [JsonPropertyName("end_area")] + public BeginAndEndArea endarea { get; set; } + + public string gender { get; set; } + public NameAndCount[] genres { get; set; } + + public string id { get; set; } + + public string[] ipis { get; set; } + + public IsniList[] isnilist { get; set; } + + [JsonPropertyName("life-span")] + public LifeSpan lifespan { get; set; } + + public string name { get; set; } + public Relation[] relations { get; set; } + + public int score { get; set; } + + [JsonPropertyName("sort-name")] + public string sortname { get; set; } + + public NameAndCount[] tags { get; set; } + + public string type { get; set; } + + [JsonPropertyName("type-id")] + public string typeid { get; set; } } [Serializable] public class Relations { - [JsonProperty("attribute-ids")] + [JsonPropertyName("attribute-ids")] public string[] attributeids { get; set; } + public string[] attributes { get; set; } - public string direction { get; set; } - [JsonProperty("target-credit")] - public string targetcredit { get; set; } - [JsonProperty("type-id")] - public string typeid { get; set; } - [JsonProperty("target-type")] - public string targettype { get; set; } - public string type { get; set; } - public bool ended { get; set; } - public Url url { get; set; } + public string[] attributevalues { get; set; } - [JsonProperty("source-credit")] - public string sourcecredit { get; set; } - public object end { get; set; } + public object begin { get; set; } + + public string direction { get; set; } + + public object end { get; set; } + + public bool? ended { get; set; } + + [JsonPropertyName("source-credit")] + public string sourcecredit { get; set; } + + [JsonPropertyName("target-credit")] + public string targetcredit { get; set; } + + [JsonPropertyName("target-type")] + public string targettype { get; set; } + + public string type { get; set; } + + [JsonPropertyName("type-id")] + public string typeid { get; set; } + + public Url url { get; set; } } [Serializable] public class Url { - public string resource { get; set; } public string id { get; set; } - } + public string resource { get; set; } + } [Serializable] public class Area { public string id { get; set; } - public string type { get; set; } - [JsonProperty("type-id")] - public string typeid { get; set; } - public string name { get; set; } - [JsonProperty("sort-name")] - public string sortname { get; set; } - [JsonProperty("life-span")] - public LifeSpan lifespan { get; set; } - [JsonProperty("iso-3166-1-codes")] + + [JsonPropertyName("iso-3166-1-codes")] public string[] iso31661codes { get; set; } + + [JsonPropertyName("life-span")] + public LifeSpan lifespan { get; set; } + + public string name { get; set; } + + [JsonPropertyName("sort-name")] + public string sortname { get; set; } + + public string type { get; set; } + + [JsonPropertyName("type-id")] + public string typeid { get; set; } } [Serializable] public class LifeSpan { - public string end { get; set; } - public string ended { get; set; } public string begin { get; set; } + + public string end { get; set; } + + public bool? ended { get; set; } } [Serializable] public class BeginAndEndArea { public string id { get; set; } - public string type { get; set; } - public string typeid { get; set; } - public string name { get; set; } - [JsonProperty("sort-name")] - public string sortname { get; set; } - [JsonProperty("life-span")] + + [JsonPropertyName("life-span")] public LifeSpan lifespan { get; set; } + + public string name { get; set; } + + [JsonPropertyName("sort-name")] + public string sortname { get; set; } + + public string type { get; set; } + + public string typeid { get; set; } } [Serializable] public class Alias { - [JsonProperty("sort-name")] - public string sortname { get; set; } - [JsonProperty("type-id")] - public string typeid { get; set; } - public string name { get; set; } - public string locale { get; set; } - public string type { get; set; } - public string primary { get; set; } public string begin { get; set; } + public string end { get; set; } - public bool ended { get; set; } + + public bool? ended { get; set; } + + public string locale { get; set; } + + public string name { get; set; } + + public bool? primary { get; set; } + + [JsonPropertyName("sort-name")] + public string sortname { get; set; } + + public string type { get; set; } + + [JsonPropertyName("type-id")] + public string typeid { get; set; } } [Serializable] @@ -147,7 +201,7 @@ namespace Roadie.Library.MetaData.MusicBrainz public class NameAndCount { public int? count { get; set; } + public string name { get; set; } } - } diff --git a/Roadie.Api.Services/Roadie.Api.Services.csproj b/Roadie.Api.Services/Roadie.Api.Services.csproj index 35e16b6..da919f1 100644 --- a/Roadie.Api.Services/Roadie.Api.Services.csproj +++ b/Roadie.Api.Services/Roadie.Api.Services.csproj @@ -7,10 +7,10 @@ - + - + diff --git a/Roadie.Api.Services/ServiceBase.cs b/Roadie.Api.Services/ServiceBase.cs index c7f1b7d..b6376cf 100644 --- a/Roadie.Api.Services/ServiceBase.cs +++ b/Roadie.Api.Services/ServiceBase.cs @@ -102,78 +102,56 @@ namespace Roadie.Api.Services return await GetArtist(artistByName.RoadieId).ConfigureAwait(false); } - protected async Task GetArtist(Guid id) + protected Task GetArtist(Guid id) { - if (id == Guid.Empty) - { - return null; - } - return await CacheManager.GetAsync(data.Artist.CacheUrn(id), async () => + return CacheManager.GetAsync(data.Artist.CacheUrn(id), async () => { return await DbContext.Artists .Include(x => x.Genres) .Include("Genres.Genre") - .FirstOrDefaultAsync(x => x.RoadieId == id).ConfigureAwait(false); - }, data.Artist.CacheRegionUrn(id)).ConfigureAwait(false); + .FirstOrDefaultAsync(x => x.RoadieId == id) + .ConfigureAwait(false); + }, data.Artist.CacheRegionUrn(id)); } - protected async Task GetCollection(Guid id) + protected Task GetCollection(Guid id) { - if (id == Guid.Empty) - { - return null; - } - return await CacheManager.GetAsync(data.Collection.CacheUrn(id), async () => + return CacheManager.GetAsync(data.Collection.CacheUrn(id), async () => { return await DbContext.Collections.FirstOrDefaultAsync(x => x.RoadieId == id).ConfigureAwait(false); - }, data.Collection.CacheRegionUrn(id)).ConfigureAwait(false); + }, data.Collection.CacheRegionUrn(id)); } - protected async Task GetGenre(Guid id) + protected Task GetGenre(Guid id) { - if (id == Guid.Empty) - { - return null; - } - return await CacheManager.GetAsync(data.Genre.CacheUrn(id), async () => + return CacheManager.GetAsync(data.Genre.CacheUrn(id), async () => { return await DbContext.Genres.FirstOrDefaultAsync(x => x.RoadieId == id).ConfigureAwait(false); - }, data.Genre.CacheRegionUrn(id)).ConfigureAwait(false); + }, data.Genre.CacheRegionUrn(id)); } - protected async Task GetLabel(Guid id) + protected Task GetLabel(Guid id) { - if (id == Guid.Empty) - { - return null; - } - return await CacheManager.GetAsync(data.Label.CacheUrn(id), async () => + return CacheManager.GetAsync(data.Label.CacheUrn(id), async () => { return await DbContext.Labels.FirstOrDefaultAsync(x => x.RoadieId == id).ConfigureAwait(false); - }, data.Label.CacheRegionUrn(id)).ConfigureAwait(false); + }, data.Label.CacheRegionUrn(id)); } - protected async Task GetPlaylist(Guid id) + protected Task GetPlaylist(Guid id) { - if (id == Guid.Empty) - { - return null; - } - return await CacheManager.GetAsync(data.Playlist.CacheUrn(id), async () => + return CacheManager.GetAsync(data.Playlist.CacheUrn(id), async () => { return await DbContext.Playlists .Include(x => x.User) - .FirstOrDefaultAsync(x => x.RoadieId == id).ConfigureAwait(false); - }, data.Playlist.CacheRegionUrn(id)).ConfigureAwait(false); + .FirstOrDefaultAsync(x => x.RoadieId == id) + .ConfigureAwait(false); + }, data.Playlist.CacheRegionUrn(id)); } - protected async Task GetRelease(Guid id) + protected Task GetRelease(Guid id) { - if (id == Guid.Empty) - { - return null; - } - return await CacheManager.GetAsync(data.Release.CacheUrn(id), async () => + return CacheManager.GetAsync(data.Release.CacheUrn(id), async () => { return await DbContext.Releases .Include(x => x.Artist) @@ -183,7 +161,7 @@ namespace Roadie.Api.Services .Include("Medias.Tracks") .Include("Medias.Tracks.TrackArtist") .FirstOrDefaultAsync(x => x.RoadieId == id).ConfigureAwait(false); - }, data.Release.CacheRegionUrn(id)).ConfigureAwait(false); + }, data.Release.CacheRegionUrn(id)); } /// @@ -199,13 +177,9 @@ namespace Roadie.Api.Services } // Only read operations - protected async Task GetTrack(Guid id) + protected Task GetTrack(Guid id) { - if(id == Guid.Empty) - { - return null; - } - return await CacheManager.GetAsync(data.Track.CacheUrn(id), async () => + return CacheManager.GetAsync(data.Track.CacheUrn(id), async () => { return await DbContext.Tracks .Include(x => x.ReleaseMedia) @@ -213,7 +187,7 @@ namespace Roadie.Api.Services .Include(x => x.ReleaseMedia.Release.Artist) .Include(x => x.TrackArtist) .FirstOrDefaultAsync(x => x.RoadieId == id).ConfigureAwait(false); - }, data.Track.CacheRegionUrn(id)).ConfigureAwait(false); + }, data.Track.CacheRegionUrn(id)); } protected async Task GetUser(string username) @@ -229,13 +203,9 @@ namespace Roadie.Api.Services return await GetUser(userByUsername?.RoadieId).ConfigureAwait(false); } - protected async Task GetUser(Guid? id) + protected Task GetUser(Guid? id) { - if (!id.HasValue) - { - return null; - } - return await CacheManager.GetAsync(User.CacheUrn(id.Value), async () => + return CacheManager.GetAsync(User.CacheUrn(id.Value), async () => { return await DbContext.Users .Include(x => x.UserRoles) @@ -245,7 +215,7 @@ namespace Roadie.Api.Services .Include(x => x.UserQues) .Include("UserQues.Track") .FirstOrDefaultAsync(x => x.RoadieId == id).ConfigureAwait(false); - }, User.CacheRegionUrn(id.Value)).ConfigureAwait(false); + }, User.CacheRegionUrn(id.Value)); } protected string MakeLastFmUrl(string artistName, string releaseTitle) => "http://www.last.fm/music/" + HttpEncoder.UrlEncode($"{artistName}/{releaseTitle}"); diff --git a/Roadie.Api.Services/SubsonicService.cs b/Roadie.Api.Services/SubsonicService.cs index 2ff0902..cda0815 100644 --- a/Roadie.Api.Services/SubsonicService.cs +++ b/Roadie.Api.Services/SubsonicService.cs @@ -2,25 +2,23 @@ using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using Roadie.Library.Caching; using Roadie.Library.Configuration; using Roadie.Library.Data.Context; using Roadie.Library.Encoding; using Roadie.Library.Enums; using Roadie.Library.Extensions; -using Roadie.Library.Identity; using Roadie.Library.Imaging; using Roadie.Library.Models; using Roadie.Library.Models.Pagination; using Roadie.Library.Models.Playlists; using Roadie.Library.Models.Releases; -using Roadie.Library.Models.Users; using Roadie.Library.Utility; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using data = Roadie.Library.Data; using subsonic = Roadie.Library.Models.ThirdPartyApi.Subsonic; @@ -39,19 +37,12 @@ namespace Roadie.Api.Services public const string SubsonicVersion = "1.16.1"; private IArtistService ArtistService { get; } - private IBookmarkService BookmarkService { get; } - private IImageService ImageService { get; } - private IPlayActivityService PlayActivityService { get; } - private IPlaylistService PlaylistService { get; } - private IReleaseService ReleaseService { get; } - private ITrackService TrackService { get; } - private UserManager UserManger { get; } public SubsonicService(IRoadieSettings configuration, @@ -88,8 +79,11 @@ namespace Roadie.Api.Services Library.Models.Users.User roadieUser) { if (string.IsNullOrEmpty(request.Message)) + { return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.RequiredParameterMissing, "Message is required"); + subsonic.ErrorCodes.RequiredParameterMissing, "Message is required"); + } + var chatMessage = new data.ChatMessage { UserId = roadieUser.Id.Value, @@ -115,8 +109,11 @@ namespace Roadie.Api.Services public async Task> AuthenticateAsync(subsonic.Request request) { if (request == null || string.IsNullOrEmpty(request?.u)) + { return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.WrongUsernameOrPassword, "Unknown Username"); + subsonic.ErrorCodes.WrongUsernameOrPassword, "Unknown Username"); + } + try { var user = DbContext.Users.FirstOrDefault(x => x.UserName == request.u); @@ -129,25 +126,35 @@ namespace Roadie.Api.Services var password = request.Password; if (!string.IsNullOrEmpty(request.s)) + { try { var token = HashHelper.CreateMD5((user.ApiToken ?? user.Email) + request.s); - if (!token.Equals(request.t, StringComparison.OrdinalIgnoreCase)) user = null; + if (!token.Equals(request.t, StringComparison.OrdinalIgnoreCase)) + { + user = null; + } } catch { } + } if (user != null && !string.IsNullOrEmpty(user.PasswordHash) && !string.IsNullOrEmpty(password)) + { try { var hashCheck = UserManger.PasswordHasher.VerifyHashedPassword(user, user.PasswordHash, password); - if (hashCheck == PasswordVerificationResult.Failed) user = null; + if (hashCheck == PasswordVerificationResult.Failed) + { + user = null; + } } catch { } + } if (user != null) { @@ -177,7 +184,7 @@ namespace Roadie.Api.Services catch (Exception ex) { Logger.LogError(ex, - "Subsonic.Authenticate, Error CheckPassword [" + JsonConvert.SerializeObject(request) + "]"); + $"Subsonic.Authenticate, Error CheckPassword [{JsonSerializer.Serialize(request)}]"); } return null; @@ -191,14 +198,20 @@ namespace Roadie.Api.Services Library.Models.Users.User roadieUser, int position, string comment) { if (!request.TrackId.HasValue) + { return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Track Id [{request.id}]"); + subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Track Id [{request.id}]"); + } + var track = GetTrack(request.TrackId.Value); if (track == null) + { return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Track Id [{request.TrackId.Value}]"); + subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Track Id [{request.TrackId.Value}]"); + } + var userBookmark = DbContext.Bookmarks.FirstOrDefault(x => - x.UserId == roadieUser.Id && x.BookmarkTargetId == track.Id && x.BookmarkType == BookmarkType.Track); + x.UserId == roadieUser.Id && x.BookmarkTargetId == track.Id && x.BookmarkType == BookmarkType.Track); var createdBookmark = false; if (userBookmark == null) { @@ -270,15 +283,21 @@ namespace Roadie.Api.Services playlist = DbContext.Playlists.Include(x => x.Tracks) .FirstOrDefault(x => x.RoadieId == request.PlaylistId); if (playlist == null) + { return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid PlaylistId [{playlistId}]"); + subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid PlaylistId [{playlistId}]"); + } // When Create is called again on an existing delete all existing tracks and add given if (playlist.Tracks?.Any() == true) + { DbContext.PlaylistTracks.RemoveRange(playlist.Tracks); + } + var listNumber = playlist.Tracks?.Any() == true - ? playlist.Tracks?.Max(x => x.ListNumber) ?? 0 - : 0; + ? playlist.Tracks?.Max(x => x.ListNumber) ?? 0 + : 0; foreach (var submittedTrack in submittedTracks) + { if (playlist.Tracks?.Any(x => x.TrackId == submittedTrack.Id) != true) { listNumber++; @@ -289,6 +308,7 @@ namespace Roadie.Api.Services TrackId = submittedTrack.Id }); } + } playlist.Name = name ?? playlist.Name; playlist.LastUpdated = DateTime.UtcNow; @@ -320,9 +340,8 @@ namespace Roadie.Api.Services } await DbContext.SaveChangesAsync().ConfigureAwait(false); - Logger.LogTrace( - $"Subsonic: User `{roadieUser}` {(didCreate ? "created" : "modified")} Playlist `{playlist}` added [{songRoadieIds.Length}] Tracks."); - request.id = subsonic.Request.PlaylistdIdentifier + playlist.RoadieId; + Logger.LogTrace($"Subsonic: User `{roadieUser}` {(didCreate ? "created" : "modified")} Playlist `{playlist}` added [{songRoadieIds.Length}] Tracks."); + request.id = $"{subsonic.Request.PlaylistdIdentifier}{playlist.RoadieId}"; return await GetPlaylistAsync(request, roadieUser).ConfigureAwait(false); } @@ -333,14 +352,20 @@ namespace Roadie.Api.Services Library.Models.Users.User roadieUser) { if (!request.TrackId.HasValue) + { return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Track Id [{request.id}]"); + subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Track Id [{request.id}]"); + } + var track = GetTrack(request.TrackId.Value); if (track == null) + { return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Track Id [{request.TrackId.Value}]"); + subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Track Id [{request.TrackId.Value}]"); + } + var userBookmark = DbContext.Bookmarks.FirstOrDefault(x => - x.UserId == roadieUser.Id && x.BookmarkTargetId == track.Id && x.BookmarkType == BookmarkType.Track); + x.UserId == roadieUser.Id && x.BookmarkTargetId == track.Id && x.BookmarkType == BookmarkType.Track); if (userBookmark != null) { DbContext.Bookmarks.Remove(userBookmark); @@ -408,12 +433,18 @@ namespace Roadie.Api.Services { var releaseId = SafeParser.ToGuid(request.id); if (!releaseId.HasValue) + { return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Release [{request.ReleaseId}]"); + subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Release [{request.ReleaseId}]"); + } + var release = await GetRelease(releaseId.Value).ConfigureAwait(false); if (release == null) + { return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Release [{request.ReleaseId}]"); + subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Release [{request.ReleaseId}]"); + } + var trackPagedRequest = request.PagedRequest; trackPagedRequest.Sort = "TrackNumber"; trackPagedRequest.Order = "ASC"; @@ -434,12 +465,12 @@ namespace Roadie.Api.Services Item = new subsonic.AlbumWithSongsID3 { artist = release.Artist.Name, - artistId = subsonic.Request.ArtistIdIdentifier + release.Artist.RoadieId, - coverArt = subsonic.Request.ReleaseIdIdentifier + release.RoadieId, + artistId = $"{subsonic.Request.ArtistIdIdentifier}{release.Artist.RoadieId}", + coverArt = $"{subsonic.Request.ReleaseIdIdentifier}{release.RoadieId}", created = release.CreatedDate, duration = release.Duration.ToSecondsFromMilliseconds(), genre = (genre?.Genre.Name), - id = subsonic.Request.ReleaseIdIdentifier + release.RoadieId, + id = $"{subsonic.Request.ReleaseIdIdentifier}{release.RoadieId}", name = release.Title, playCount = releaseTracks.Rows.Sum(x => x.PlayedCount) ?? 0, playCountSpecified = releaseTracks.Rows.Any(), @@ -455,7 +486,7 @@ namespace Roadie.Api.Services } catch (Exception ex) { - Logger.LogError(ex, "GetAlbum Request [{0}], User [{1}]", JsonConvert.SerializeObject(request), + Logger.LogError(ex, "GetAlbum Request [{0}], User [{1}]", JsonSerializer.Serialize(request), roadieUser.ToString()); } @@ -470,10 +501,16 @@ namespace Roadie.Api.Services { var releaseId = SafeParser.ToGuid(request.id); if (!releaseId.HasValue) + { return new subsonic.SubsonicOperationResult(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Release [{request.id}]"); + } + var release = await GetRelease(releaseId.Value).ConfigureAwait(false); if (release == null) + { return new subsonic.SubsonicOperationResult(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Release [{request.id}]"); + } + switch (version) { case subsonic.AlbumInfoVersion.One: @@ -542,7 +579,9 @@ namespace Roadie.Api.Services } if (!releaseResult.IsSuccess) + { return new subsonic.SubsonicOperationResult(releaseResult.Message); + } switch (version) { @@ -593,16 +632,22 @@ namespace Roadie.Api.Services { var artistId = SafeParser.ToGuid(request.id); if (!artistId.HasValue) + { return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Release [{request.id}]"); + subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Release [{request.id}]"); + } + var pagedRequest = request.PagedRequest; pagedRequest.Sort = "Id"; pagedRequest.FilterToArtistId = artistId.Value; var artistResult = await ArtistService.ListAsync(roadieUser, pagedRequest).ConfigureAwait(false); var artist = artistResult.Rows.FirstOrDefault(); if (artist == null) + { return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Release [{request.id}]"); + subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Release [{request.id}]"); + } + var artistReleaseResult = await ReleaseService.ListAsync(roadieUser, pagedRequest).ConfigureAwait(false); return new subsonic.SubsonicOperationResult { @@ -612,8 +657,7 @@ namespace Roadie.Api.Services version = SubsonicVersion, status = subsonic.ResponseStatus.ok, ItemElementName = subsonic.ItemChoiceType.artist, - Item = SubsonicArtistWithAlbumsID3ForArtist(artist, - SubsonicAlbumID3ForReleases(artistReleaseResult.Rows)) + Item = SubsonicArtistWithAlbumsID3ForArtist(artist, SubsonicAlbumID3ForReleases(artistReleaseResult.Rows)) } }; } @@ -625,7 +669,10 @@ namespace Roadie.Api.Services { var artistId = SafeParser.ToGuid(request.id); if (!artistId.HasValue) + { return new subsonic.SubsonicOperationResult(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid ArtistId [{request.id}]"); + } + var artist = await GetArtist(artistId.Value).ConfigureAwait(false); if (artist == null) { @@ -751,42 +798,68 @@ namespace Roadie.Api.Services if (request.ArtistId != null) { var artistImage = await ImageService.ArtistImageAsync(request.ArtistId.Value, size, size).ConfigureAwait(false); - if (!artistImage.IsSuccess) return artistImage.Adapt>(); + if (!artistImage.IsSuccess) + { + return artistImage.Adapt>(); + } + result.Data = new Library.Models.Image(artistImage.Data.Bytes); } else if (request.TrackId != null) { var trackimage = await ImageService.TrackImageAsync(request.TrackId.Value, size, size).ConfigureAwait(false); - if (!trackimage.IsSuccess) return trackimage.Adapt>(); + if (!trackimage.IsSuccess) + { + return trackimage.Adapt>(); + } + result.Data = new Library.Models.Image(trackimage.Data.Bytes); } else if (request.CollectionId != null) { var collectionImage = await ImageService.CollectionImageAsync(request.CollectionId.Value, size, size).ConfigureAwait(false); if (!collectionImage.IsSuccess) + { return collectionImage.Adapt>(); + } + result.Data = new Library.Models.Image(collectionImage.Data.Bytes); } else if (request.ReleaseId != null) { var releaseimage = await ImageService.ReleaseImageAsync(request.ReleaseId.Value, size, size).ConfigureAwait(false); - if (!releaseimage.IsSuccess) return releaseimage.Adapt>(); + if (!releaseimage.IsSuccess) + { + return releaseimage.Adapt>(); + } + result.Data = new Library.Models.Image(releaseimage.Data.Bytes); } else if (request.PlaylistId != null) { var playlistImage = await ImageService.PlaylistImageAsync(request.PlaylistId.Value, size, size).ConfigureAwait(false); - if (!playlistImage.IsSuccess) return playlistImage.Adapt>(); + if (!playlistImage.IsSuccess) + { + return playlistImage.Adapt>(); + } + result.Data = new Library.Models.Image(playlistImage.Data.Bytes); } else if (!string.IsNullOrEmpty(request.u)) { var user = await GetUser(request.u).ConfigureAwait(false); if (user == null) + { return new subsonic.SubsonicFileOperationResult( - subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Username [{request.u}]"); + subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Username [{request.u}]"); + } + var userImage = await ImageService.UserImageAsync(user.RoadieId, size, size).ConfigureAwait(false); - if (!userImage.IsSuccess) return userImage.Adapt>(); + if (!userImage.IsSuccess) + { + return userImage.Adapt>(); + } + result.Data = new Library.Models.Image(userImage.Data.Bytes); } @@ -925,9 +998,12 @@ namespace Roadie.Api.Services var artistId = SafeParser.ToGuid(request.id); var artist = await GetArtist(artistId.Value).ConfigureAwait(false); if (artist == null) + { return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid ArtistId [{request.id}]"); - directory.id = subsonic.Request.ArtistIdIdentifier + artist.RoadieId; + subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid ArtistId [{request.id}]"); + } + + directory.id = $"{subsonic.Request.ArtistIdIdentifier}{artist.RoadieId}"; directory.name = artist.Name; var artistRating = user == null ? null @@ -943,7 +1019,7 @@ namespace Roadie.Api.Services pagedRequest.Sort = "Release.Text"; var artistReleases = await ReleaseService.ListAsync(roadieUser, pagedRequest).ConfigureAwait(false); directory.child = SubsonicChildrenForReleases(artistReleases.Rows, - subsonic.Request.ArtistIdIdentifier + artist.RoadieId); + $"{subsonic.Request.ArtistIdIdentifier}{artist.RoadieId}"); } // Request to get albums for in a Collection else if (request.CollectionId != null) @@ -951,15 +1027,18 @@ namespace Roadie.Api.Services var collectionId = SafeParser.ToGuid(request.id); var collection = await GetCollection(collectionId.Value).ConfigureAwait(false); if (collection == null) + { return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid CollectionId [{request.id}]"); - directory.id = subsonic.Request.CollectionIdentifier + collection.RoadieId; + subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid CollectionId [{request.id}]"); + } + + directory.id = $"{subsonic.Request.CollectionIdentifier}{collection.RoadieId}"; directory.name = collection.Name; var pagedRequest = request.PagedRequest; pagedRequest.FilterToCollectionId = collection.RoadieId; var collectionReleases = await ReleaseService.ListAsync(roadieUser, pagedRequest).ConfigureAwait(false); directory.child = SubsonicChildrenForReleases(collectionReleases.Rows, - subsonic.Request.CollectionIdentifier + collection.RoadieId); + $"{subsonic.Request.CollectionIdentifier}{collection.RoadieId}"); } // Request to get Tracks for an Album else if (request.ReleaseId.HasValue) @@ -967,15 +1046,18 @@ namespace Roadie.Api.Services var releaseId = SafeParser.ToGuid(request.id); var release = await GetRelease(releaseId.Value).ConfigureAwait(false); if (release == null) + { return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid ReleaseId [{request.id}]"); - directory.id = subsonic.Request.ReleaseIdIdentifier + release.RoadieId; + subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid ReleaseId [{request.id}]"); + } + + directory.id = $"{subsonic.Request.ReleaseIdIdentifier}{release.RoadieId}"; directory.name = release.Title; var releaseRating = user == null ? null : await DbContext.UserReleases.FirstOrDefaultAsync(x => x.UserId == user.Id && x.ReleaseId == release.Id).ConfigureAwait(false); directory.averageRating = release.Rating ?? 0; - directory.parent = subsonic.Request.ArtistIdIdentifier + release.Artist.RoadieId; + directory.parent = $"{subsonic.Request.ArtistIdIdentifier}{release.Artist.RoadieId}"; if (releaseRating?.IsFavorite ?? false) { directory.starred = releaseRating.LastUpdated ?? releaseRating.CreatedDate; @@ -993,7 +1075,7 @@ namespace Roadie.Api.Services { return new subsonic.SubsonicOperationResult( subsonic.ErrorCodes.TheRequestedDataWasNotFound, - $"Unknown GetMusicDirectory Type [{JsonConvert.SerializeObject(request)}], id [{request.id}]"); + $"Unknown GetMusicDirectory Type [{JsonSerializer.Serialize(request)}], id [{request.id}]"); } return new subsonic.SubsonicOperationResult @@ -1082,24 +1164,26 @@ namespace Roadie.Api.Services /// /// Returns a listing of files in a saved playlist. /// - public async Task> GetPlaylistAsync(subsonic.Request request, - Library.Models.Users.User roadieUser) + public async Task> GetPlaylistAsync(subsonic.Request request, Library.Models.Users.User roadieUser) { var playListId = SafeParser.ToGuid(request.id); if (!playListId.HasValue) - return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid PlaylistId [{request.id}]"); + { + return new subsonic.SubsonicOperationResult(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid PlaylistId [{request.id}]"); + } var pagedRequest = request.PagedRequest; pagedRequest.Sort = "Id"; pagedRequest.FilterToPlaylistId = playListId.Value; var playlistResult = await PlaylistService.ListAsync(pagedRequest, roadieUser).ConfigureAwait(false); var playlist = playlistResult.Rows.FirstOrDefault(); if (playlist == null) - return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid PlaylistId [{request.id}]"); + { + return new subsonic.SubsonicOperationResult(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid PlaylistId [{request.id}]"); + } // For a playlist to show all the tracks in the playlist set the limit to the playlist size pagedRequest.Limit = playlist.PlaylistCount ?? pagedRequest.Limit; var tracksForPlaylist = await TrackService.ListAsync(pagedRequest, roadieUser).ConfigureAwait(false); + var detail = await SubsonicPlaylistForPlaylist(playlist, tracksForPlaylist.Rows).ConfigureAwait(false); return new subsonic.SubsonicOperationResult { IsSuccess = true, @@ -1108,7 +1192,7 @@ namespace Roadie.Api.Services version = SubsonicVersion, status = subsonic.ResponseStatus.ok, ItemElementName = subsonic.ItemChoiceType.playlist, - Item = SubsonicPlaylistForPlaylist(playlist, tracksForPlaylist.Rows) + Item = detail } }; } @@ -1296,16 +1380,22 @@ namespace Roadie.Api.Services Library.Models.Users.User roadieUser) { if (!request.TrackId.HasValue) + { return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Track [{request.id}]"); + subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Track [{request.id}]"); + } + var pagedRequest = request.PagedRequest; pagedRequest.FilterToTrackId = request.TrackId.Value; pagedRequest.Sort = "Id"; var trackResult = await TrackService.ListAsync(pagedRequest, roadieUser).ConfigureAwait(false); var track = trackResult.Rows.FirstOrDefault(); if (track == null) + { return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Track [{request.id}]"); + subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Track [{request.id}]"); + } + return new subsonic.SubsonicOperationResult { IsSuccess = true, @@ -1413,11 +1503,20 @@ namespace Roadie.Api.Services { data.Artist artist = null; if (!string.IsNullOrEmpty(request.ArtistName)) + { artist = await GetArtist(request.ArtistName).ConfigureAwait(false); - else if (request.ArtistId.HasValue) artist = await GetArtist(request.ArtistId.Value).ConfigureAwait(false); + } + else if (request.ArtistId.HasValue) + { + artist = await GetArtist(request.ArtistId.Value).ConfigureAwait(false); + } + if (artist == null) + { return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Unknown Artist [{request.ArtistName}]"); + subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Unknown Artist [{request.ArtistName}]"); + } + var pagedRequest = request.PagedRequest; pagedRequest.FilterToArtistId = artist.RoadieId; pagedRequest.FilterTopPlayedOnly = true; @@ -1449,8 +1548,11 @@ namespace Roadie.Api.Services { var user = await GetUser(username).ConfigureAwait(false); if (user == null) + { return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Username [{username}]"); + subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Username [{username}]"); + } + return new subsonic.SubsonicOperationResult { IsSuccess = true, @@ -1559,7 +1661,7 @@ namespace Roadie.Api.Services public async Task> SearchAsync(subsonic.Request request, Library.Models.Users.User roadieUser, subsonic.SearchVersion version) { - var query = HttpEncoder.UrlDecode(request.Query).Replace("*", "").Replace("%", "").Replace(";", ""); + var query = HttpEncoder.UrlDecode(request.Query).Replace("*", string.Empty).Replace("%", string.Empty).Replace(";", string.Empty); // Search artists with query returning ArtistCount skipping ArtistOffset var artistPagedRequest = request.PagedRequest; @@ -1644,44 +1746,52 @@ namespace Roadie.Api.Services { var user = await GetUser(roadieUser.UserId).ConfigureAwait(false); if (user == null) + { return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.UserIsNotAuthorizedForGivenOperation, $"Invalid User [{roadieUser}]"); + subsonic.ErrorCodes.UserIsNotAuthorizedForGivenOperation, $"Invalid User [{roadieUser}]"); + } // Id can be a song, album or artist if (request.TrackId.HasValue) { var starResult = await SetTrackRating(request.TrackId.Value, user, rating).ConfigureAwait(false); if (starResult.IsSuccess) + { return new subsonic.SubsonicOperationResult { IsSuccess = true, Data = new subsonic.Response() }; + } } else if (request.ReleaseId.HasValue) { var starResult = await SetReleaseRating(request.ReleaseId.Value, user, rating).ConfigureAwait(false); if (starResult.IsSuccess) + { return new subsonic.SubsonicOperationResult { IsSuccess = true, Data = new subsonic.Response() }; + } } else if (request.ArtistId.HasValue) { var starResult = await SetArtistRating(request.ArtistId.Value, user, rating).ConfigureAwait(false); if (starResult.IsSuccess) + { return new subsonic.SubsonicOperationResult { IsSuccess = true, Data = new subsonic.Response() }; + } } return new subsonic.SubsonicOperationResult( subsonic.ErrorCodes.TheRequestedDataWasNotFound, - $"Unknown Star Id [{JsonConvert.SerializeObject(request)}]"); + $"Unknown Star Id [{JsonSerializer.Serialize(request)}]"); } /// @@ -1692,39 +1802,47 @@ namespace Roadie.Api.Services { var user = await GetUser(roadieUser.UserId).ConfigureAwait(false); if (user == null) + { return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.UserIsNotAuthorizedForGivenOperation, $"Invalid User [{roadieUser}]"); + subsonic.ErrorCodes.UserIsNotAuthorizedForGivenOperation, $"Invalid User [{roadieUser}]"); + } // Id can be a song, album or artist if (request.TrackId.HasValue) { var starResult = await ToggleTrackStar(request.TrackId.Value, user, star).ConfigureAwait(false); if (starResult.IsSuccess) + { return new subsonic.SubsonicOperationResult { IsSuccess = true, Data = new subsonic.Response() }; + } } else if (request.ReleaseId.HasValue) { var starResult = await ToggleReleaseStar(request.ReleaseId.Value, user, star).ConfigureAwait(false); if (starResult.IsSuccess) + { return new subsonic.SubsonicOperationResult { IsSuccess = true, Data = new subsonic.Response() }; + } } else if (request.ArtistId.HasValue) { var starResult = await ToggleArtistStar(request.ArtistId.Value, user, star).ConfigureAwait(false); if (starResult.IsSuccess) + { return new subsonic.SubsonicOperationResult { IsSuccess = true, Data = new subsonic.Response() }; + } } else if (albumIds?.Any() == true) { @@ -1735,8 +1853,10 @@ namespace Roadie.Api.Services { var starResult = await ToggleReleaseStar(releaseId.Value, user, star).ConfigureAwait(false); if (!starResult.IsSuccess) + { return new subsonic.SubsonicOperationResult(starResult.ErrorCode.Value, - starResult.Messages.FirstOrDefault()); + starResult.Messages.FirstOrDefault()); + } } } } @@ -1749,15 +1869,17 @@ namespace Roadie.Api.Services { var starResult = await ToggleReleaseStar(artistId.Value, user, star).ConfigureAwait(false); if (!starResult.IsNotFoundResult) + { return new subsonic.SubsonicOperationResult(starResult.ErrorCode.Value, - starResult.Messages.FirstOrDefault()); + starResult.Messages.FirstOrDefault()); + } } } } return new subsonic.SubsonicOperationResult( subsonic.ErrorCodes.TheRequestedDataWasNotFound, - $"Unknown Star Id [{JsonConvert.SerializeObject(request)}]"); + $"Unknown Star Id [{JsonSerializer.Serialize(request)}]"); } /// @@ -1776,16 +1898,24 @@ namespace Roadie.Api.Services { request.id = playListId ?? request.id; if (!request.PlaylistId.HasValue) + { return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Playlist Id [{request.id}]"); + subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Playlist Id [{request.id}]"); + } + var playlist = await GetPlaylist(request.PlaylistId.Value).ConfigureAwait(false); if (playlist == null) + { return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Playlist Id [{request.TrackId.Value}]"); + subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Playlist Id [{request.TrackId.Value}]"); + } + if (playlist.UserId != roadieUser.Id && !roadieUser.IsAdmin) + { return new subsonic.SubsonicOperationResult( - subsonic.ErrorCodes.UserIsNotAuthorizedForGivenOperation, - "User is not allowed to update playlist."); + subsonic.ErrorCodes.UserIsNotAuthorizedForGivenOperation, + "User is not allowed to update playlist."); + } playlist.Name = name ?? playlist.Name; playlist.IsPublic = isPublic ?? playlist.IsPublic; @@ -1801,6 +1931,7 @@ namespace Roadie.Api.Services var listNumber = playlist.Tracks?.Max(x => x.ListNumber) ?? 0; foreach (var submittedTrack in submittedTracks) + { if (playlist.Tracks == null || playlist.Tracks?.Any(x => x.TrackId == submittedTrack.Id) != true) { listNumber++; @@ -1811,12 +1942,15 @@ namespace Roadie.Api.Services TrackId = submittedTrack.Id }); } + } } if (songIndexesToRemove?.Any() == true) + { // Remove tracks from playlist // Not clear from API documentation if this is zero based, wait until someone calls it to get values passed. - throw new NotImplementedException($"Request [{JsonConvert.SerializeObject(request)}]"); + throw new NotImplementedException($"Request [{JsonSerializer.Serialize(request)}]"); + } await DbContext.SaveChangesAsync().ConfigureAwait(false); @@ -1836,21 +1970,25 @@ namespace Roadie.Api.Services #region Privates - private async Task AllowedUsers() + private Task AllowedUsers() { - return await CacheManager.GetAsync(CacheManagerBase.SystemCacheRegionUrn + ":active_usernames", async () => - { - return await DbContext.Users.Where(x => x.IsActive ?? false).Select(x => x.UserName).ToArrayAsync().ConfigureAwait(false); - }, CacheManagerBase.SystemCacheRegionUrn).ConfigureAwait(false); + return CacheManager.GetAsync(CacheManagerBase.SystemCacheRegionUrn + ":active_usernames", async () => await DbContext.Users.Where(x => x.IsActive ?? false).Select(x => x.UserName).ToArrayAsync().ConfigureAwait(false), CacheManagerBase.SystemCacheRegionUrn); } + //private async Task AllowedUsers() + //{ + // return await CacheManager.GetAsync(CacheManagerBase.SystemCacheRegionUrn + ":active_usernames", async () => + // { + // return await DbContext.Users.Where(x => x.IsActive ?? false).Select(x => x.UserName).ToArrayAsync().ConfigureAwait(false); + // }, CacheManagerBase.SystemCacheRegionUrn).ConfigureAwait(false); + //} + private subsonic.MusicFolder CollectionMusicFolder() { return MusicFolders().First(x => x.id == 1); } - private async Task> GetArtistsAction( - subsonic.Request request, Library.Models.Users.User roadieUser) + private async Task> GetArtistsAction(subsonic.Request request, Library.Models.Users.User roadieUser) { var indexes = new List(); var musicFolder = MusicFolders().Find(x => x.id == (request.MusicFolderId ?? 2)); @@ -1867,12 +2005,13 @@ namespace Roadie.Api.Services pagedRequest.Sort = "Artist.Text"; var artistList = await ArtistService.ListAsync(roadieUser, pagedRequest).ConfigureAwait(false); foreach (var artistGroup in artistList.Rows.GroupBy(x => x.Artist.Text.Substring(0, 1))) + { indexes.Add(new subsonic.IndexID3 { name = artistGroup.Key, artist = SubsonicArtistID3sForArtists(artistGroup) }); - ; + } } return new subsonic.SubsonicOperationResult @@ -1894,8 +2033,7 @@ namespace Roadie.Api.Services private async Task> GetIndexesAction( subsonic.Request request, Library.Models.Users.User roadieUser, long? ifModifiedSince = null) { - var modifiedSinceFilter = - ifModifiedSince.HasValue ? (DateTime?)ifModifiedSince.Value.FromUnixTime() : null; + var modifiedSinceFilter = ifModifiedSince?.FromUnixTime(); var musicFolderFilter = !request.MusicFolderId.HasValue ? new subsonic.MusicFolder() : MusicFolders().Find(x => x.id == request.MusicFolderId.Value); @@ -1908,6 +2046,7 @@ namespace Roadie.Api.Services let first = c.Name.Substring(0, 1) orderby first select first).Distinct().ToArray()) + { indexes.Add(new subsonic.Index { name = collectionFirstLetter, @@ -1924,6 +2063,7 @@ namespace Roadie.Api.Services userRating = 0 }).ToArray() }); + } } else { @@ -1934,12 +2074,13 @@ namespace Roadie.Api.Services pagedRequest.Sort = "Artist.Text"; var artistList = await ArtistService.ListAsync(roadieUser, pagedRequest).ConfigureAwait(false); foreach (var artistGroup in artistList.Rows.GroupBy(x => x.Artist.Text.Substring(0, 1))) + { indexes.Add(new subsonic.Index { name = artistGroup.Key, artist = SubsonicArtistsForArtists(artistGroup) }); - ; + } } return new subsonic.SubsonicOperationResult @@ -1968,18 +2109,13 @@ namespace Roadie.Api.Services }; } - private subsonic.MusicFolder MusicMusicFolder() - { - return MusicFolders().First(x => x.id == 2); - } - - private new async Task> SetArtistRating(Guid artistId, - Library.Identity.User user, short rating) + private new async Task> SetArtistRating(Guid artistId, Library.Identity.User user, short rating) { var r = await base.SetArtistRating(artistId, user, rating).ConfigureAwait(false); if (r.IsNotFoundResult) - return new subsonic.SubsonicOperationResult(subsonic.ErrorCodes.TheRequestedDataWasNotFound, - $"Invalid Artist Id [{artistId}]"); + { + return new subsonic.SubsonicOperationResult(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Artist Id [{artistId}]"); + } return new subsonic.SubsonicOperationResult { IsSuccess = r.IsSuccess, @@ -1992,8 +2128,11 @@ namespace Roadie.Api.Services { var r = await base.SetReleaseRating(releaseId, user, rating).ConfigureAwait(false); if (r.IsNotFoundResult) + { return new subsonic.SubsonicOperationResult(subsonic.ErrorCodes.TheRequestedDataWasNotFound, - $"Invalid Release Id [{releaseId}]"); + $"Invalid Release Id [{releaseId}]"); + } + return new subsonic.SubsonicOperationResult { IsSuccess = r.IsSuccess, @@ -2006,8 +2145,11 @@ namespace Roadie.Api.Services { var r = await base.SetTrackRating(trackId, user, rating).ConfigureAwait(false); if (r.IsNotFoundResult) + { return new subsonic.SubsonicOperationResult(subsonic.ErrorCodes.TheRequestedDataWasNotFound, - $"Invalid Track Id [{trackId}]"); + $"Invalid Track Id [{trackId}]"); + } + return new subsonic.SubsonicOperationResult { IsSuccess = r.IsSuccess, @@ -2019,13 +2161,13 @@ namespace Roadie.Api.Services { return new subsonic.AlbumID3 { - id = subsonic.Request.ReleaseIdIdentifier + r.Id, + id = $"{subsonic.Request.ReleaseIdIdentifier}{r.Id}", artistId = r.Artist.Value, name = r.Release.Text, songCount = r.TrackCount ?? 0, duration = r.Duration.ToSecondsFromMilliseconds(), artist = r.Artist.Text, - coverArt = subsonic.Request.ReleaseIdIdentifier + r.Id, + coverArt = $"{subsonic.Request.ReleaseIdIdentifier}{r.Id}", created = r.CreatedDate.Value, genre = r.Genre.Text, playCount = r.TrackPlayedCount ?? 0, @@ -2039,7 +2181,11 @@ namespace Roadie.Api.Services private subsonic.AlbumID3[] SubsonicAlbumID3ForReleases(IEnumerable r) { - if (r == null || !r.Any()) return new subsonic.AlbumID3[0]; + if (r?.Any() != true) + { + return new subsonic.AlbumID3[0]; + } + return r.Select(x => SubsonicAlbumID3ForRelease(x)).ToArray(); } @@ -2047,7 +2193,7 @@ namespace Roadie.Api.Services { return new subsonic.Artist { - id = subsonic.Request.ArtistIdIdentifier + artist.Artist.Value, + id = $"{subsonic.Request.ArtistIdIdentifier}{artist.Artist.Value}", name = artist.Artist.Text, artistImageUrl = ImageHelper.MakeArtistThumbnailImage(Configuration, HttpContext, artist.Id).Url, averageRating = artist.Rating ?? 0, @@ -2055,7 +2201,7 @@ namespace Roadie.Api.Services starred = artist.UserRating?.RatedDate ?? DateTime.UtcNow, starredSpecified = artist.UserRating?.IsFavorite ?? false, userRating = artist.UserRating != null ? artist.UserRating.Rating ?? 0 : 0, - userRatingSpecified = artist.UserRating != null && artist.UserRating.Rating != null + userRatingSpecified = artist.UserRating?.Rating != null }; } @@ -2064,7 +2210,7 @@ namespace Roadie.Api.Services var artistImageUrl = ImageHelper.MakeArtistThumbnailImage(Configuration, HttpContext, artist.Id).Url; return new subsonic.ArtistID3 { - id = subsonic.Request.ArtistIdIdentifier + artist.Artist.Value, + id = $"{subsonic.Request.ArtistIdIdentifier}{artist.Artist.Value}", name = artist.Artist.Text, albumCount = artist.ReleaseCount ?? 0, coverArt = artistImageUrl, @@ -2076,7 +2222,11 @@ namespace Roadie.Api.Services private subsonic.ArtistID3[] SubsonicArtistID3sForArtists(IEnumerable artists) { - if (artists == null || !artists.Any()) return new subsonic.ArtistID3[0]; + if (artists?.Any() != true) + { + return new subsonic.ArtistID3[0]; + } + return artists.Select(x => SubsonicArtistID3ForArtist(x)).ToArray(); } @@ -2085,11 +2235,11 @@ namespace Roadie.Api.Services return new subsonic.ArtistInfo2 { biography = artist.BioContext, - largeImageUrl = ImageHelper.MakeImage(Configuration, HttpContext, artist.RoadieId, "artist", Configuration.LargeImageSize).Url, - mediumImageUrl = ImageHelper.MakeImage(Configuration, HttpContext, artist.RoadieId, "artist", Configuration.MediumImageSize).Url, + largeImageUrl = ImageHelper.MakeImage(Configuration, HttpContext, artist.RoadieId, nameof(artist), Configuration.LargeImageSize).Url, + mediumImageUrl = ImageHelper.MakeImage(Configuration, HttpContext, artist.RoadieId, nameof(artist), Configuration.MediumImageSize).Url, musicBrainzId = artist.MusicBrainzId, similarArtist = new subsonic.ArtistID3[0], - smallImageUrl = ImageHelper.MakeImage(Configuration, HttpContext, artist.RoadieId, "artist", Configuration.SmallImageSize).Url + smallImageUrl = ImageHelper.MakeImage(Configuration, HttpContext, artist.RoadieId, nameof(artist), Configuration.SmallImageSize).Url }; } @@ -2098,29 +2248,32 @@ namespace Roadie.Api.Services return new subsonic.ArtistInfo { biography = artist.BioContext, - largeImageUrl = ImageHelper.MakeImage(Configuration, HttpContext, artist.RoadieId, "artist", Configuration.LargeImageSize).Url, - mediumImageUrl = ImageHelper.MakeImage(Configuration, HttpContext, artist.RoadieId, "artist", Configuration.MediumImageSize).Url, + largeImageUrl = ImageHelper.MakeImage(Configuration, HttpContext, artist.RoadieId, nameof(artist), Configuration.LargeImageSize).Url, + mediumImageUrl = ImageHelper.MakeImage(Configuration, HttpContext, artist.RoadieId, nameof(artist), Configuration.MediumImageSize).Url, musicBrainzId = artist.MusicBrainzId, similarArtist = new subsonic.Artist[0], - smallImageUrl = ImageHelper.MakeImage(Configuration, HttpContext, artist.RoadieId, "artist", Configuration.SmallImageSize).Url + smallImageUrl = ImageHelper.MakeImage(Configuration, HttpContext, artist.RoadieId, nameof(artist), Configuration.SmallImageSize).Url }; } private subsonic.Artist[] SubsonicArtistsForArtists(IEnumerable artists) { - if (artists == null || !artists.Any()) return new subsonic.Artist[0]; + if (artists?.Any() != true) + { + return new subsonic.Artist[0]; + } + return artists.Select(x => SubsonicArtistForArtist(x)).ToArray(); } - private subsonic.ArtistWithAlbumsID3 SubsonicArtistWithAlbumsID3ForArtist(ArtistList artist, - subsonic.AlbumID3[] releases) + private subsonic.ArtistWithAlbumsID3 SubsonicArtistWithAlbumsID3ForArtist(ArtistList artist, subsonic.AlbumID3[] releases) { var artistImageUrl = ImageHelper.MakeArtistThumbnailImage(Configuration, HttpContext, artist.Id).Url; return new subsonic.ArtistWithAlbumsID3 { - id = subsonic.Request.ArtistIdIdentifier + artist.Artist.Value, + id = $"{subsonic.Request.ArtistIdIdentifier}{artist.Artist.Value}", album = releases, - albumCount = releases.Count(), + albumCount = releases.Length, artistImageUrl = artistImageUrl, coverArt = artistImageUrl, name = artist.Artist.Text, @@ -2145,7 +2298,11 @@ namespace Roadie.Api.Services private subsonic.Bookmark[] SubsonicBookmarksForBookmarks(IEnumerable bb, IEnumerable childTracks) { - if (bb == null || !bb.Any()) return new subsonic.Bookmark[0]; + if (bb?.Any() != true) + { + return new subsonic.Bookmark[0]; + } + var result = new List(); foreach (var bookmark in bb) { @@ -2153,8 +2310,7 @@ namespace Roadie.Api.Services switch (bookmark.Type.Value) { case BookmarkType.Track: - child = SubsonicChildForTrack(childTracks.FirstOrDefault(x => - x.Id == SafeParser.ToGuid(bookmark.Bookmark.Value))); + child = SubsonicChildForTrack(childTracks.FirstOrDefault(x => x.Id == SafeParser.ToGuid(bookmark.Bookmark.Value))); break; default: @@ -2171,13 +2327,13 @@ namespace Roadie.Api.Services { return new subsonic.Child { - id = subsonic.Request.ReleaseIdIdentifier + r.Id, + id = $"{subsonic.Request.ReleaseIdIdentifier}{r.Id}", album = r.Release.Text, - albumId = subsonic.Request.ReleaseIdIdentifier + r.Id, + albumId = $"{subsonic.Request.ReleaseIdIdentifier}{r.Id}", artist = r.Artist.Text, averageRating = r.Rating ?? 0, averageRatingSpecified = true, - coverArt = subsonic.Request.ReleaseIdIdentifier + r.Id, + coverArt = $"{subsonic.Request.ReleaseIdIdentifier}{r.Id}", created = r.CreatedDate.Value, createdSpecified = true, genre = r.Genre.Text, @@ -2190,7 +2346,7 @@ namespace Roadie.Api.Services starredSpecified = r.UserRating?.IsFavorite ?? false, title = r.Release.Text, userRating = r.UserRating != null ? r.UserRating.Rating ?? 0 : 0, - userRatingSpecified = r.UserRating != null && r.UserRating.Rating != null, + userRatingSpecified = r.UserRating?.Rating != null, year = SafeParser.ToNumber(r.ReleaseYear), yearSpecified = true }; @@ -2208,17 +2364,17 @@ namespace Roadie.Api.Services } return new subsonic.Child { - id = subsonic.Request.TrackIdIdentifier + t.Id, + id = $"{subsonic.Request.TrackIdIdentifier}{t.Id}", album = t.Release.Release.Text, - albumId = subsonic.Request.ReleaseIdIdentifier + t.Release.Release.Value, + albumId = $"{subsonic.Request.ReleaseIdIdentifier}{t.Release.Release.Value}", artist = t.Artist.Artist.Text, - artistId = subsonic.Request.ArtistIdIdentifier + t.Artist.Artist.Value, + artistId = $"{subsonic.Request.ArtistIdIdentifier}{t.Artist.Artist.Value}", averageRating = t.Rating ?? 0, averageRatingSpecified = true, bitRate = 320, bitRateSpecified = true, contentType = MimeTypeHelper.Mp3MimeType, - coverArt = subsonic.Request.TrackIdIdentifier + t.Id, + coverArt = $"{subsonic.Request.TrackIdIdentifier}{t.Id}", created = t.CreatedDate.Value, createdSpecified = true, discNumber = t.MediaNumber ?? 0, @@ -2226,7 +2382,7 @@ namespace Roadie.Api.Services duration = t.Duration.ToSecondsFromMilliseconds(), durationSpecified = true, isDir = false, - parent = subsonic.Request.ReleaseIdIdentifier + t.Release.Release.Value, + parent = $"{subsonic.Request.ReleaseIdIdentifier}{t.Release.Release.Value}", path = $"{t.Artist.Artist.Text}/{t.Release.Release.Text}/{t.TrackNumber} - {t.Title}.mp3", playCountSpecified = true, size = t.FileSize ?? 0, @@ -2253,13 +2409,19 @@ namespace Roadie.Api.Services private subsonic.Child[] SubsonicChildrenForReleases(IEnumerable r, string parent) { - if (r == null || !r.Any()) return new subsonic.Child[0]; + if (r?.Any() != true) + { + return new subsonic.Child[0]; + } return r.Select(x => SubsonicChildForRelease(x, parent, $"{x.Artist.Text}/{x.Release.Text}/")).ToArray(); } private subsonic.Child[] SubsonicChildrenForTracks(IEnumerable tracks) { - if (tracks == null || !tracks.Any()) return new subsonic.Child[0]; + if (tracks?.Any() != true) + { + return new subsonic.Child[0]; + } return tracks.Select(x => SubsonicChildForTrack(x)).ToArray(); } @@ -2272,7 +2434,7 @@ namespace Roadie.Api.Services changed = playlist.LastUpdated ?? playlist.CreatedDate ?? DateTime.UtcNow, created = playlist.CreatedDate ?? DateTime.UtcNow, duration = playlist.Duration.ToSecondsFromMilliseconds(), - id = subsonic.Request.PlaylistdIdentifier + playlist.Id, + id = $"{subsonic.Request.PlaylistdIdentifier}{playlist.Id}", name = playlist.Playlist.Text, owner = playlist.User.Text, @public = playlist.IsPublic, @@ -2289,7 +2451,7 @@ namespace Roadie.Api.Services return new subsonic.Playlist[0]; } var result = new List(); - foreach(var playlist in playlists) + foreach (var playlist in playlists) { result.Add(await SubsonicPlaylistForPlaylist(playlist).ConfigureAwait(false)); } diff --git a/Roadie.Api.Services/TrackService.cs b/Roadie.Api.Services/TrackService.cs index bd61a5d..1e9954b 100644 --- a/Roadie.Api.Services/TrackService.cs +++ b/Roadie.Api.Services/TrackService.cs @@ -2,7 +2,6 @@ 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; @@ -24,6 +23,7 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Linq.Dynamic.Core; +using System.Text.Json; using System.Threading.Tasks; using data = Roadie.Library.Data; @@ -791,7 +791,7 @@ namespace Roadie.Api.Services } catch (Exception ex) { - Logger.LogError(ex, "Error In List, Request [{0}], User [{1}]", JsonConvert.SerializeObject(request), roadieUser); + Logger.LogError(ex, "Error In List, Request [{0}], User [{1}]", JsonSerializer.Serialize(request), roadieUser); return new Library.Models.Pagination.PagedResult { Message = "An Error has occured" diff --git a/Roadie.Api.Services/UserService.cs b/Roadie.Api.Services/UserService.cs index 3351cdd..91f3cfe 100644 --- a/Roadie.Api.Services/UserService.cs +++ b/Roadie.Api.Services/UserService.cs @@ -2,7 +2,6 @@ using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using Roadie.Library; using Roadie.Library.Caching; using Roadie.Library.Configuration; @@ -23,6 +22,7 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Linq.Dynamic.Core; +using System.Text.Json; using System.Threading.Tasks; using data = Roadie.Library.Data; using models = Roadie.Library.Models; @@ -593,7 +593,7 @@ namespace Roadie.Api.Services result.AdditionalData.Add("Timing", sw.ElapsedMilliseconds); Logger.LogTrace( - $"User `{roadieUser}` set rating [{rating}] on TrackId [{trackId}]. Result [{JsonConvert.SerializeObject(result)}]"); + $"User `{roadieUser}` set rating [{rating}] on TrackId [{trackId}]. Result [{JsonSerializer.Serialize(result)}]"); return result; } diff --git a/Roadie.Api/Controllers/EntityControllerBase.cs b/Roadie.Api/Controllers/EntityControllerBase.cs index d94785c..06debd2 100644 --- a/Roadie.Api/Controllers/EntityControllerBase.cs +++ b/Roadie.Api/Controllers/EntityControllerBase.cs @@ -2,7 +2,6 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using Roadie.Api.Services; using Roadie.Library.Caching; using Roadie.Library.Configuration; @@ -13,6 +12,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net; +using System.Text.Json; using System.Threading.Tasks; using models = Roadie.Library.Models.Users; @@ -146,7 +146,7 @@ namespace Roadie.Api.Controllers }; await playActivityService.NowPlayingAsync(user, scrobble).ConfigureAwait(false); sw.Stop(); - Logger.LogTrace($"StreamTrack ElapsedTime [{sw.ElapsedMilliseconds}], Timings [{JsonConvert.SerializeObject(timings)}], StreamInfo `{info?.Data}`"); + Logger.LogTrace($"StreamTrack ElapsedTime [{sw.ElapsedMilliseconds}], Timings [{JsonSerializer.Serialize(timings)}], StreamInfo `{info?.Data}`"); return new EmptyResult(); } diff --git a/Roadie.Api/Controllers/SubsonicController.cs b/Roadie.Api/Controllers/SubsonicController.cs index e60ac3e..69e4b80 100644 --- a/Roadie.Api/Controllers/SubsonicController.cs +++ b/Roadie.Api/Controllers/SubsonicController.cs @@ -2,7 +2,6 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using Roadie.Api.ModelBinding; using Roadie.Api.Services; using Roadie.Library.Caching; @@ -15,6 +14,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net; +using System.Text.Json; using System.Threading.Tasks; using User = Roadie.Library.Models.Users.User; @@ -364,7 +364,7 @@ namespace Roadie.Api.Controllers if (!result.IsSuccess) { - Logger.LogWarning($"GetCoverArt Failed For [{JsonConvert.SerializeObject(request)}]"); + Logger.LogWarning($"GetCoverArt Failed For [{ JsonSerializer.Serialize(request)}]"); return StatusCode((int)HttpStatusCode.InternalServerError); } @@ -901,7 +901,7 @@ namespace Roadie.Api.Controllers } else { - jsonResult = $"{{ \"subsonic-response\": {{ \"status\":\"{status}\", \"version\": \"{version}\", \"{responseType}\":{((response?.Data != null ? JsonConvert.SerializeObject(response.Data.Item) : string.Empty))}}}}}"; + jsonResult = $"{{ \"subsonic-response\": {{ \"status\":\"{status}\", \"version\": \"{version}\", \"{responseType}\":{((response?.Data != null ? JsonSerializer.Serialize(response.Data.Item) : string.Empty))}}}}}"; } if ((request?.f ?? string.Empty).Equals("jsonp", StringComparison.OrdinalIgnoreCase)) diff --git a/Roadie.Api/Controllers/UserController.cs b/Roadie.Api/Controllers/UserController.cs index b5822a4..2343c2f 100644 --- a/Roadie.Api/Controllers/UserController.cs +++ b/Roadie.Api/Controllers/UserController.cs @@ -21,9 +21,7 @@ namespace Roadie.Api.Controllers [Authorize] public class UserController : EntityControllerBase { - private IHttpContext RoadieHttpContext { get; } - private IUserService UserService { get; } private readonly ITokenService TokenService; @@ -65,8 +63,14 @@ namespace Roadie.Api.Controllers public async Task Get(Guid id, string inc = null) { var user = await CurrentUserModel().ConfigureAwait(false); - var result = await CacheManager.GetAsync($"urn:user_model_by_id:{id}", - async () => await UserService.ByIdAsync(user, id, (inc ?? Library.Models.Users.User.DefaultIncludes).ToLower().Split(",")).ConfigureAwait(false), ControllerCacheRegionUrn).ConfigureAwait(false); + var result = await CacheManager.GetAsync($"urn:user_model_by_id:{id}", async () => + { + return await UserService.ByIdAsync(user, id, (inc ?? Library.Models.Users.User.DefaultIncludes) + .ToLower() + .Split(",")) + .ConfigureAwait(false); + }, + ControllerCacheRegionUrn).ConfigureAwait(false); if (result?.IsNotFoundResult != false) { return NotFound(); @@ -386,11 +390,5 @@ namespace Roadie.Api.Controllers DefaultRowsPerPage = modelUser.DefaultRowsPerPage ?? RoadieSettings.DefaultRowsPerPage }); } - - public class PagingParams - { - public int Limit { get; set; } = 5; - public int Page { get; set; } = 1; - } } } \ No newline at end of file diff --git a/Roadie.Api/Program.cs b/Roadie.Api/Program.cs index f9427bb..a3d6ab8 100644 --- a/Roadie.Api/Program.cs +++ b/Roadie.Api/Program.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; +using Pastel; using Serilog; using System; using System.Diagnostics; @@ -31,19 +32,19 @@ namespace Roadie.Api #if DEBUG // Logging Output tests - Log.Verbose(":: Log Test: Verbose (Trace,None)"); // Microsoft.Extensions.Logging.LogLevel.Trace and Microsoft.Extensions.Logging.LogLevel.None - Log.Debug(":: Log Test: Debug"); // Microsoft.Extensions.Logging.LogLevel.Debug - Log.Information(":: Log Test: Information"); // Microsoft.Extensions.Logging.LogLevel.Information - Log.Warning(":: Log Test: Warning"); // Microsoft.Extensions.Logging.LogLevel.Warning - Log.Error(new Exception("Log Test Exception"), "Log Test Error Message"); // Microsoft.Extensions.Logging.LogLevel.Error - Log.Fatal(":: Log Test: Fatal (Critial)"); // Microsoft.Extensions.Logging.LogLevel.Critical - Trace.WriteLine(":: Log Test: Trace WriteLine()"); + Log.Verbose(":: Log Test: Verbose (Trace,None), Test 'value', Test: Value"); // Microsoft.Extensions.Logging.LogLevel.Trace and Microsoft.Extensions.Logging.LogLevel.None + Log.Debug(":: Log Test: Debug, Test 'value', Test: Value"); // Microsoft.Extensions.Logging.LogLevel.Debug + Log.Information(":: Log Test: Information, Test 'value', Test: Value"); // Microsoft.Extensions.Logging.LogLevel.Information + Log.Warning(":: Log Test: Warning, Test 'value', Test: Value"); // Microsoft.Extensions.Logging.LogLevel.Warning + Log.Error(new Exception("Log Test Exception"), "Log Test Error Message, Test 'value', Test: Value"); // Microsoft.Extensions.Logging.LogLevel.Error + Log.Fatal(":: Log Test: Fatal (Critial), Test 'value', Test: Value"); // Microsoft.Extensions.Logging.LogLevel.Critical + Trace.WriteLine(":: Log Test: Trace WriteLine(), Test 'value', Test: Value"); #endif Console.WriteLine(""); - Console.WriteLine(@" ____ __ __ ____ __ ____ __ ____ __ "); - Console.WriteLine(@"( _ \ / \ / _\ ( \( )( __) / _\ ( _ \( ) "); - Console.WriteLine(@" ) /( O )/ \ ) D ( )( ) _) / \ ) __/ )( "); - Console.WriteLine(@"(__\_) \__/ \_/\_/(____/(__)(____) \_/\_/(__) (__) "); + Console.WriteLine(@" ____ __ __ ____ __ ____ __ ____ __ ".Pastel("#FEFF0E")); + Console.WriteLine(@"( _ \ / \ / _\ ( \( )( __) / _\ ( _ \( ) ".Pastel("#F49014")); + Console.WriteLine(@" ) /( O )/ \ ) D ( )( ) _) / \ ) __/ )( ".Pastel("#E30014")); + Console.WriteLine(@"(__\_) \__/ \_/\_/(____/(__)(____) \_/\_/(__) (__) ".Pastel("#DB0083")); Console.WriteLine(""); CreateHostBuilder(args).Build().Run(); diff --git a/Roadie.Api/Roadie.Api.csproj b/Roadie.Api/Roadie.Api.csproj index 7f93191..e2904f5 100644 --- a/Roadie.Api/Roadie.Api.csproj +++ b/Roadie.Api/Roadie.Api.csproj @@ -1,7 +1,7 @@  - 1.1.2 + 1.1.3 @@ -31,15 +31,16 @@ - - + + - - - + + + + @@ -49,7 +50,7 @@ - + diff --git a/Roadie.Api/RoadieSerilogThemes.cs b/Roadie.Api/RoadieSerilogThemes.cs new file mode 100644 index 0000000..d9e9616 --- /dev/null +++ b/Roadie.Api/RoadieSerilogThemes.cs @@ -0,0 +1,33 @@ +using Serilog.Sinks.SystemConsole.Themes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Roadie.Api +{ + public static class RoadieSerilogThemes + { + public static AnsiConsoleTheme RoadieRainbow { get; } = new AnsiConsoleTheme( + new Dictionary + { + [ConsoleThemeStyle.Text] = "\x1b[38;5;0034m", + [ConsoleThemeStyle.SecondaryText] = "\x1b[38;5;0025m", + [ConsoleThemeStyle.TertiaryText] = "\x1b[38;5;0089m", + [ConsoleThemeStyle.Invalid] = "\x1b[38;5;0126m", + [ConsoleThemeStyle.Null] = "\x1b[38;5;0100m", + [ConsoleThemeStyle.Name] = "\x1b[38;5;0007m", + [ConsoleThemeStyle.String] = "\x1b[38;5;0117m", + [ConsoleThemeStyle.Number] = "\x1b[38;5;0200m", + [ConsoleThemeStyle.Boolean] = "\x1b[38;5;0027m", + [ConsoleThemeStyle.Scalar] = "\x1b[38;5;0085m", + [ConsoleThemeStyle.LevelVerbose] = "\x1b[38;5;0007m", + [ConsoleThemeStyle.LevelDebug] = "\x1b[38;5;0015m", + [ConsoleThemeStyle.LevelInformation] = "\x1b[38;5;0015m", + [ConsoleThemeStyle.LevelWarning] = "\x1b[38;5;0011m", + [ConsoleThemeStyle.LevelError] = "\x1b[38;5;0015m\x1b[48;5;0196m", + [ConsoleThemeStyle.LevelFatal] = "\x1b[38;5;0011m\x1b[48;5;0009m", + } + ); + } +} diff --git a/Roadie.Api/Startup.cs b/Roadie.Api/Startup.cs index 2e720b9..7761180 100644 --- a/Roadie.Api/Startup.cs +++ b/Roadie.Api/Startup.cs @@ -16,7 +16,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; -using Newtonsoft.Json; using Roadie.Api.Hubs; using Roadie.Api.ModelBinding; using Roadie.Api.Services; @@ -113,10 +112,17 @@ namespace Roadie.Api services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(options => + { + var logger = options.GetService>(); + return new Utf8JsonCacheSerializer(logger); + }); + services.AddSingleton(options => { var logger = options.GetService>(); - return new MemoryCacheManager(logger, new CachePolicy(TimeSpan.FromHours(4))); + var serializer = options.GetService(); + return new MemoryCacheManager(logger, serializer, new CachePolicy(TimeSpan.FromHours(4))); }); var dbFolder = new DirectoryInfo(settings.FileDatabaseOptions.DatabaseFolder); @@ -326,11 +332,7 @@ namespace Roadie.Api options.RespectBrowserAcceptHeader = true; // false by default options.ModelBinderProviders.Insert(0, new SubsonicRequestBinderProvider()); }) - .AddNewtonsoftJson(options => - { - options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; - options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; - }) + .AddJsonOptions(options => options.JsonSerializerOptions.IgnoreNullValues = true) .AddXmlSerializerFormatters(); services.Configure(options => diff --git a/Roadie.Api/appsettings.json b/Roadie.Api/appsettings.json index 917ad66..94bfef0 100644 --- a/Roadie.Api/appsettings.json +++ b/Roadie.Api/appsettings.json @@ -20,8 +20,8 @@ { "Name": "Console", "Args": { - "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console", - "restrictedToMinimumLevel": "Information" + "theme": "Roadie.Api.RoadieSerilogThemes::RoadieRainbow, Roadie.Api", + "restrictedToMinimumLevel": "Verbose" } }, { diff --git a/Roadie.Dlna/Server/Handlers/MediaMount_SOAP.cs b/Roadie.Dlna/Server/Handlers/MediaMount_SOAP.cs index b0f80c7..ee50429 100644 --- a/Roadie.Dlna/Server/Handlers/MediaMount_SOAP.cs +++ b/Roadie.Dlna/Server/Handlers/MediaMount_SOAP.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using Roadie.Dlna.Server.Metadata; using Roadie.Dlna.Utility; using System; @@ -13,23 +12,16 @@ namespace Roadie.Dlna.Server internal partial class MediaMount { private const string NS_DC = "http://purl.org/dc/elements/1.1/"; - private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"; - private const string NS_DLNA = "urn:schemas-dlna-org:metadata-1-0/"; - private const string NS_SEC = "http://www.sec.co.kr/"; - private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/"; - private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/"; - private static readonly IDictionary soapCache = new LeastRecentlyUsedDictionary(200); - - private static readonly XmlNamespaceManager namespaceMgr = CreateNamespaceManager(); - private static readonly string featureList = Encoding.UTF8.GetString(ResourceHelper.GetResourceData("x_featurelist.xml") ?? new byte[0]); + private static readonly IDictionary soapCache = new LeastRecentlyUsedDictionary(200); + private static void AddBookmarkInfo(IMediaResource resource, XmlElement item) { var bookmarkable = resource as IBookmarkable; @@ -79,10 +71,7 @@ namespace Roadie.Dlna.Server var res = result.CreateElement(string.Empty, "res", NS_DIDL); res.InnerText = curl; - res.SetAttribute("protocolInfo", string.Format( - "http-get:*:{1}:DLNA.ORG_PN={0};DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS={2}", - c.PN, DlnaMaps.Mime[c.Type], DlnaMaps.DefaultStreaming - )); + res.SetAttribute("protocolInfo", $"http-get:*:{DlnaMaps.Mime[c.Type]}:DLNA.ORG_PN={c.PN};DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS={DlnaMaps.DefaultStreaming}"); var width = c.MetaWidth; var height = c.MetaHeight; if (width.HasValue && height.HasValue) @@ -270,10 +259,7 @@ namespace Roadie.Dlna.Server res.SetAttribute("duration", prop); } - res.SetAttribute("protocolInfo", string.Format( - "http-get:*:{1}:DLNA.ORG_PN={0};DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS={2}", - resource.PN, DlnaMaps.Mime[resource.Type], DlnaMaps.DefaultStreaming - )); + res.SetAttribute("protocolInfo", $"http-get:*:{DlnaMaps.Mime[resource.Type]}:DLNA.ORG_PN={resource.PN};DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS={DlnaMaps.DefaultStreaming}"); item.AppendChild(res); AddCover(request, resource, item); @@ -509,7 +495,7 @@ namespace Roadie.Dlna.Server { Logger.LogError(ex, $"ProcessSoapRequest. Error Loading Request From [{ request.RemoteEndpoint.Address }], Body [{ request.Body }]"); } - var sparams = new RawHeaders(); + var sparams = new RawHeaders(); var body = soap.SelectSingleNode("//soap:Body", namespaceMgr); if (body == null) { @@ -625,5 +611,7 @@ namespace Roadie.Dlna.Server rv.Headers.Add("EXT", string.Empty); return rv; } + + private static readonly XmlNamespaceManager namespaceMgr = CreateNamespaceManager(); } } \ No newline at end of file diff --git a/libraries/libwebp_x64.dll b/libraries/libwebp_x64.dll new file mode 100644 index 0000000..9fcabfd Binary files /dev/null and b/libraries/libwebp_x64.dll differ