resolves #39, resolves #40

This commit is contained in:
Steven Hildreth 2020-06-21 15:39:14 -05:00
parent 3cfd12a330
commit f556c28c29
83 changed files with 2451 additions and 1310 deletions

View file

@ -22,7 +22,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="3.0.0" /> <PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.4" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -47,7 +47,7 @@ namespace Roadie.Library.Tests
IConfiguration configuration = configurationBuilder.Build(); IConfiguration configuration = configurationBuilder.Build();
configuration.GetSection("RoadieSettings").Bind(settings); configuration.GetSection("RoadieSettings").Bind(settings);
Configuration = 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(); HttpEncoder = new Encoding.DummyHttpEncoder();
} }

View file

@ -290,5 +290,22 @@ namespace Roadie.Library.Tests
var d = input.ToSecondsFromMilliseconds(); var d = input.ToSecondsFromMilliseconds();
Assert.Equal(shouldBe, d); 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());
}
} }
} }

View file

@ -42,7 +42,7 @@ namespace Roadie.Library.Tests
configuration.GetSection("RoadieSettings").Bind(settings); configuration.GetSection("RoadieSettings").Bind(settings);
settings.ConnectionString = configuration.GetConnectionString("RoadieDatabaseConnection"); settings.ConnectionString = configuration.GetConnectionString("RoadieDatabaseConnection");
Configuration = settings; 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<ID3TagsHelper>(); var tagHelperLooper = new EventMessageLogger<ID3TagsHelper>();
tagHelperLooper.Messages += MessageLoggerMessages; tagHelperLooper.Messages += MessageLoggerMessages;
TagsHelper = new ID3TagsHelper(Configuration, CacheManager, tagHelperLooper); TagsHelper = new ID3TagsHelper(Configuration, CacheManager, tagHelperLooper);

View file

@ -45,7 +45,7 @@ namespace Roadie.Library.Tests
configuration.GetSection("RoadieSettings").Bind(settings); configuration.GetSection("RoadieSettings").Bind(settings);
settings.ConnectionString = configuration.GetConnectionString("RoadieDatabaseConnection"); settings.ConnectionString = configuration.GetConnectionString("RoadieDatabaseConnection");
Configuration = settings; 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<ID3TagsHelper>(); var tagHelperLooper = new EventMessageLogger<ID3TagsHelper>();
tagHelperLooper.Messages += MessageLoggerMessages; tagHelperLooper.Messages += MessageLoggerMessages;
TagsHelper = new ID3TagsHelper(Configuration, CacheManager, tagHelperLooper); TagsHelper = new ID3TagsHelper(Configuration, CacheManager, tagHelperLooper);

View file

@ -23,7 +23,7 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.4" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.5" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.2"> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.2">

View file

@ -35,7 +35,7 @@ namespace Roadie.Library.Tests
IConfiguration configuration = configurationBuilder.Build(); IConfiguration configuration = configurationBuilder.Build();
configuration.GetSection("RoadieSettings").Bind(settings); configuration.GetSection("RoadieSettings").Bind(settings);
Configuration = 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(); HttpEncoder = new Encoding.DummyHttpEncoder();
} }

View file

@ -1,5 +1,4 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -10,19 +9,15 @@ namespace Roadie.Library.Caching
public const string SystemCacheRegionUrn = "urn:system"; public const string SystemCacheRegionUrn = "urn:system";
protected readonly CachePolicy _defaultPolicy; protected readonly CachePolicy _defaultPolicy;
protected readonly JsonSerializerSettings _serializerSettings;
protected ILogger Logger { get; } protected ILogger Logger { get; }
protected ICacheSerializer CacheSerializer { get; }
public CacheManagerBase(ILogger logger, CachePolicy defaultPolicy) public CacheManagerBase(ILogger logger, ICacheSerializer cacheSerializer, CachePolicy defaultPolicy)
{ {
Logger = logger; Logger = logger;
CacheSerializer = cacheSerializer;
_defaultPolicy = defaultPolicy; _defaultPolicy = defaultPolicy;
_serializerSettings = new JsonSerializerSettings
{
ContractResolver = new IgnoreJsonAttributesResolver(),
Formatting = Formatting.Indented
};
} }
public abstract bool Add<TCacheValue>(string key, TCacheValue value); public abstract bool Add<TCacheValue>(string key, TCacheValue value);
@ -55,24 +50,5 @@ namespace Roadie.Library.Caching
public abstract bool Remove(string key, string region); public abstract bool Remove(string key, string region);
protected TOut Deserialize<TOut>(string s)
{
if (string.IsNullOrEmpty(s)) return default(TOut);
try
{
return JsonConvert.DeserializeObject<TOut>(s, _serializerSettings);
}
catch (Exception ex)
{
Logger.LogError(ex);
}
return default(TOut);
}
protected string Serialize(object o)
{
return JsonConvert.SerializeObject(o, _serializerSettings);
}
} }
} }

View file

@ -11,8 +11,8 @@ namespace Roadie.Library.Caching
private Dictionary<string, object> Cache { get; } private Dictionary<string, object> Cache { get; }
public DictionaryCacheManager(ILogger logger, CachePolicy defaultPolicy) public DictionaryCacheManager(ILogger logger, ICacheSerializer cacheSerializer, CachePolicy defaultPolicy)
: base(logger, defaultPolicy) : base(logger, cacheSerializer, defaultPolicy)
{ {
Cache = new Dictionary<string, object>(); Cache = new Dictionary<string, object>();
} }

View file

@ -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<TOut>(string s);
}
}

View file

@ -9,8 +9,8 @@ namespace Roadie.Library.Caching
{ {
private MemoryCache _cache; private MemoryCache _cache;
public MemoryCacheManager(ILogger logger, CachePolicy defaultPolicy) public MemoryCacheManager(ILogger logger, ICacheSerializer cacheSerializer, CachePolicy defaultPolicy)
: base(logger, defaultPolicy) : base(logger, cacheSerializer, defaultPolicy)
{ {
_cache = new MemoryCache(new MemoryCacheOptions()); _cache = new MemoryCache(new MemoryCacheOptions());
} }

View file

@ -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<TOut>(string s)
{
if (string.IsNullOrEmpty(s))
{
return default(TOut);
}
try
{
return JsonConvert.DeserializeObject<TOut>(s, _serializerSettings);
}
catch (Exception ex)
{
Logger.LogError(ex);
}
return default(TOut);
}
public string Serialize(object o) => JsonConvert.SerializeObject(o, _serializerSettings);
}
}

View file

@ -26,8 +26,8 @@ namespace Roadie.Library.Caching
private IDatabase Redis => _redis ?? (_redis = Connection.GetDatabase()); private IDatabase Redis => _redis ?? (_redis = Connection.GetDatabase());
public RedisCacheManager(ILogger logger, CachePolicy defaultPolicy) public RedisCacheManager(ILogger logger, ICacheSerializer cacheSerializer, CachePolicy defaultPolicy)
: base(logger, defaultPolicy) : base(logger, cacheSerializer, defaultPolicy)
{ {
} }
@ -49,7 +49,7 @@ namespace Roadie.Library.Caching
public override bool Add<TCacheValue>(string key, TCacheValue value, string region, CachePolicy policy) public override bool Add<TCacheValue>(string key, TCacheValue value, string region, CachePolicy policy)
{ {
if (_doTraceLogging) Logger.LogTrace("Added [{0}], Region [{1}]", key, region); 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() public override void Clear()
@ -135,7 +135,7 @@ namespace Roadie.Library.Caching
private TOut Get<TOut>(string key, string region, CachePolicy policy) private TOut Get<TOut>(string key, string region, CachePolicy policy)
{ {
var result = Deserialize<TOut>(Redis.StringGet(key)); var result = CacheSerializer.Deserialize<TOut>(Redis.StringGet(key));
if (result == null) if (result == null)
{ {
if (_doTraceLogging) Logger.LogTrace("Get Cache Miss Key [{0}], Region [{1}]", key, region); if (_doTraceLogging) Logger.LogTrace("Get Cache Miss Key [{0}], Region [{1}]", key, region);

View file

@ -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<TOut>(string s)
{
if (string.IsNullOrEmpty(s))
{
return default(TOut);
}
try
{
return JsonSerializer.Deserialize<TOut>(s);
}
catch (Exception ex)
{
Logger.LogError(ex);
}
return default(TOut);
}
}
}

View file

@ -46,6 +46,8 @@ namespace Roadie.Library.Configuration
public string UnknownFolder { get; set; } public string UnknownFolder { get; set; }
public bool DoDetectFeatureFragments { get; set; }
public Processing() public Processing()
{ {
DoAudioCleanup = true; DoAudioCleanup = true;
@ -55,6 +57,7 @@ namespace Roadie.Library.Configuration
DoParseFromLastFM = true; DoParseFromLastFM = true;
DoParseFromMusicBrainz = true; DoParseFromMusicBrainz = true;
DoSaveEditsToTags = true; DoSaveEditsToTags = true;
DoDetectFeatureFragments = true;
MaximumArtistImagesToAdd = 12; MaximumArtistImagesToAdd = 12;
MaximumReleaseImagesToAdd = 12; MaximumReleaseImagesToAdd = 12;

View file

@ -1,7 +1,5 @@
using CsvHelper; using CsvHelper;
using Newtonsoft.Json;
using Roadie.Library.Configuration; using Roadie.Library.Configuration;
using Roadie.Library.Enums;
using Roadie.Library.Extensions; using Roadie.Library.Extensions;
using Roadie.Library.Utility; using Roadie.Library.Utility;
using System; using System;
@ -14,6 +12,10 @@ namespace Roadie.Library.Data
{ {
public partial class Collection public partial class Collection
{ {
public const string ArtistPosition = "artist";
public const string PositionPosition = "position";
public const string ReleasePosition = "release";
/// <summary> /// <summary>
/// If the given value in either Artist or Release starts with this then the next value is the database Id, example "1,~4,~19" /// If the given value in either Artist or Release starts with this then the next value is the database Id, example "1,~4,~19"
/// </summary> /// </summary>
@ -35,7 +37,10 @@ namespace Roadie.Library.Data
foreach (var pos in ListInCSVFormat.Split(',')) foreach (var pos in ListInCSVFormat.Split(','))
{ {
looper++; 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(',')) foreach (var pos in ListInCSVFormat.Split(','))
{ {
looper++; 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(',')) foreach (var pos in ListInCSVFormat.Split(','))
{ {
looper++; 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, MissingFieldFound = null,
HasHeaderRecord = false HasHeaderRecord = false
}; };
configuration.BadDataFound = context => configuration.BadDataFound = context => Trace.WriteLine($"PositionArtistReleases: Bad data found on row '{context.RawRow}'", "Warning");
{
Trace.WriteLine($"PositionArtistReleases: Bad data found on row '{context.RawRow}'", "Warning");
};
using (var csv = new CsvReader(sr, configuration)) using (var csv = new CsvReader(sr, configuration))
{ {
while (csv.Read()) while (csv.Read())
@ -160,6 +168,4 @@ namespace Roadie.Library.Data
return $"Id [{Id}], Name [{Name}], RoadieId [{RoadieId}]"; return $"Id [{Id}], Name [{Name}], RoadieId [{RoadieId}]";
} }
} }
} }

View file

@ -1,6 +1,5 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Roadie.Library.Caching; using Roadie.Library.Caching;
using Roadie.Library.Configuration; using Roadie.Library.Configuration;
using Roadie.Library.Data; using Roadie.Library.Data;
@ -19,6 +18,7 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using discogs = Roadie.Library.SearchEngines.MetaData.Discogs; using discogs = Roadie.Library.SearchEngines.MetaData.Discogs;
using lastfm = Roadie.Library.MetaData.LastFm; using lastfm = Roadie.Library.MetaData.LastFm;
@ -251,7 +251,7 @@ namespace Roadie.Library.Engines
} }
if (!string.IsNullOrEmpty(releaseRoadieDataFilename) && File.Exists(releaseRoadieDataFilename)) if (!string.IsNullOrEmpty(releaseRoadieDataFilename) && File.Exists(releaseRoadieDataFilename))
{ {
artist = JsonConvert.DeserializeObject<Artist>(File.ReadAllText(releaseRoadieDataFilename)); artist = JsonSerializer.Deserialize<Artist>(File.ReadAllText(releaseRoadieDataFilename));
var addResult = await Add(artist).ConfigureAwait(false); var addResult = await Add(artist).ConfigureAwait(false);
if (!addResult.IsSuccess) if (!addResult.IsSuccess)
{ {

View file

@ -30,7 +30,11 @@ namespace Roadie.Library.Engines
{ {
public class ReleaseLookupEngine : LookupEngineBase, IReleaseLookupEngine public class ReleaseLookupEngine : LookupEngineBase, IReleaseLookupEngine
{ {
private IArtistLookupEngine ArtistLookupEngine { get; }
private ILabelLookupEngine LabelLookupEngine { get; }
public List<int> _addedReleaseIds = new List<int>(); public List<int> _addedReleaseIds = new List<int>();
public List<int> _addedTrackIds = new List<int>(); public List<int> _addedTrackIds = new List<int>();
public IEnumerable<int> AddedReleaseIds => _addedReleaseIds; public IEnumerable<int> AddedReleaseIds => _addedReleaseIds;
@ -49,10 +53,6 @@ namespace Roadie.Library.Engines
public IReleaseSearchEngine WikipediaReleaseSearchEngine { get; } public IReleaseSearchEngine WikipediaReleaseSearchEngine { get; }
private IArtistLookupEngine ArtistLookupEngine { get; }
private ILabelLookupEngine LabelLookupEngine { get; }
public ReleaseLookupEngine(IRoadieSettings configuration, IHttpEncoder httpEncoder, IRoadieDbContext context, public ReleaseLookupEngine(IRoadieSettings configuration, IHttpEncoder httpEncoder, IRoadieDbContext context,
ICacheManager cacheManager, ILogger<ReleaseLookupEngine> logger, IArtistLookupEngine artistLookupEngine, ICacheManager cacheManager, ILogger<ReleaseLookupEngine> logger, IArtistLookupEngine artistLookupEngine,
ILabelLookupEngine labelLookupEngine, musicbrainz.IMusicBrainzProvider musicBrainzProvider, lastfm.ILastFmHelper lastFmHelper, ILabelLookupEngine labelLookupEngine, musicbrainz.IMusicBrainzProvider musicBrainzProvider, lastfm.ILastFmHelper lastFmHelper,
@ -117,7 +117,11 @@ namespace Roadie.Library.Engines
{ {
var genreName = releaseGenreTable.ToAlphanumericName().ToTitleCase(); var genreName = releaseGenreTable.ToAlphanumericName().ToTitleCase();
var normalizedName = genreName.ToUpper(); var normalizedName = genreName.ToUpper();
if (string.IsNullOrEmpty(genreName)) continue; if (string.IsNullOrEmpty(genreName))
{
continue;
}
if (genreName.Length > 100) if (genreName.Length > 100)
{ {
var originalName = genreName; 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<Track>();
foreach (var newTrack in newReleaseMedia.Tracks)
{
int? trackArtistId = null;
string partTitles = null;
if (newTrack.TrackArtist != null)
{ {
Status = Statuses.Incomplete, if (!release.IsCastRecording)
MediaNumber = newReleaseMedia.MediaNumber,
SubTitle = newReleaseMedia.SubTitle,
TrackCount = newReleaseMedia.TrackCount,
ReleaseId = release.Id
};
var releasemediatracks = new List<Track>();
foreach (var newTrack in newReleaseMedia.Tracks)
{
int? trackArtistId = null;
string partTitles = null;
if (newTrack.TrackArtist != null)
{ {
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); trackArtistId = trackArtistData.Data.Id;
if (trackArtistData.IsSuccess)
{
trackArtistId = trackArtistData.Data.Id;
}
}
else if (newTrack.TrackArtists?.Any() == true)
{
partTitles = string.Join("/", newTrack.TrackArtists);
}
else
{
partTitles = newTrack.TrackArtist.Name;
} }
} }
else if (newTrack.TrackArtists?.Any() == true)
releasemediatracks.Add(new Track
{ {
ArtistId = trackArtistId, partTitles = string.Join("/", newTrack.TrackArtists);
PartTitles = partTitles, }
Status = Statuses.Incomplete, else
TrackNumber = newTrack.TrackNumber, {
MusicBrainzId = newTrack.MusicBrainzId, partTitles = newTrack.TrackArtist.Name;
SpotifyId = newTrack.SpotifyId, }
AmgId = newTrack.AmgId,
Title = newTrack.Title,
AlternateNames = newTrack.AlternateNames,
Duration = newTrack.Duration,
Tags = newTrack.Tags,
ISRC = newTrack.ISRC,
LastFMId = newTrack.LastFMId
});
} }
releasemedia.Tracks = releasemediatracks; releasemediatracks.Add(new Track
DbContext.ReleaseMedias.Add(releasemedia); {
_addedTrackIds.AddRange(releasemedia.Tracks.Select(x => x.Id)); 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 releasemedia.Tracks = releasemediatracks;
{ DbContext.ReleaseMedias.Add(releasemedia);
await DbContext.SaveChangesAsync().ConfigureAwait(false); _addedTrackIds.AddRange(releasemedia.Tracks.Select(x => x.Id));
} }
catch (Exception ex)
{ try
Logger.LogError(ex); {
} await DbContext.SaveChangesAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.LogError(ex);
} }
} }
sw.Stop(); sw.Stop();
@ -314,21 +315,21 @@ namespace Roadie.Library.Engines
var specialSearchNameEnd = $"|{specialSearchName}"; var specialSearchNameEnd = $"|{specialSearchName}";
return await (from a in DbContext.Releases return await (from a in DbContext.Releases
where a.ArtistId == artist.Id where a.ArtistId == artist.Id
where a.Title.ToLower() == searchName || where a.Title.ToLower() == searchName ||
a.Title.ToLower() == specialSearchName || a.Title.ToLower() == specialSearchName ||
a.SortTitle.ToLower() == searchName || a.SortTitle.ToLower() == searchName ||
a.SortTitle.ToLower() == searchSortName || a.SortTitle.ToLower() == searchSortName ||
a.SortTitle.ToLower() == specialSearchName || a.SortTitle.ToLower() == specialSearchName ||
a.AlternateNames.ToLower().Equals(searchName) || a.AlternateNames.ToLower().Equals(searchName) ||
a.AlternateNames.ToLower().StartsWith(searchNameStart) || a.AlternateNames.ToLower().StartsWith(searchNameStart) ||
a.AlternateNames.ToLower().Contains(searchNameIn) || a.AlternateNames.ToLower().Contains(searchNameIn) ||
a.AlternateNames.ToLower().EndsWith(searchNameEnd) || a.AlternateNames.ToLower().EndsWith(searchNameEnd) ||
a.AlternateNames.ToLower().Equals(specialSearchName) || a.AlternateNames.ToLower().Equals(specialSearchName) ||
a.AlternateNames.ToLower().StartsWith(specialSearchNameStart) || a.AlternateNames.ToLower().StartsWith(specialSearchNameStart) ||
a.AlternateNames.ToLower().Contains(specialSearchNameIn) || a.AlternateNames.ToLower().Contains(specialSearchNameIn) ||
a.AlternateNames.ToLower().EndsWith(specialSearchNameEnd) a.AlternateNames.ToLower().EndsWith(specialSearchNameEnd)
select a select a
).FirstOrDefaultAsync().ConfigureAwait(false); ).FirstOrDefaultAsync().ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
@ -348,7 +349,7 @@ namespace Roadie.Library.Engines
var sw = new Stopwatch(); var sw = new Stopwatch();
sw.Start(); sw.Start();
var cacheRegion = new Release { Artist = artist, Title = metaData.Release }.CacheRegion; 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<Release>(cacheKey, cacheRegion); var resultInCache = CacheManager.Get<Release>(cacheKey, cacheRegion);
if (resultInCache != null) 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<Release> return new OperationResult<Release>
{ {
IsSuccess = release != null, IsSuccess = release != null,
@ -437,7 +442,11 @@ namespace Roadie.Library.Engines
var resultsExceptions = new List<Exception>(); var resultsExceptions = new List<Exception>();
var releaseGenres = new List<string>(); var releaseGenres = new List<string>();
// Add any Genre found in the given MetaData // 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<ReleaseLabelSearchResult>(); var releaseLabels = new List<ReleaseLabelSearchResult>();
var releaseMedias = new List<ReleaseMediaSearchResult>(); var releaseMedias = new List<ReleaseMediaSearchResult>();
var releaseImages = new List<IImage>(); var releaseImages = new List<IImage>();
@ -464,10 +473,26 @@ namespace Roadie.Library.Engines
result.AlternateNames = result.AlternateNames.AddToDelimitedList(i.AlternateNames); result.AlternateNames = result.AlternateNames.AddToDelimitedList(i.AlternateNames);
} }
if (i.Tags != null) result.Tags = result.Tags.AddToDelimitedList(i.Tags); if (i.Tags != null)
if (i.Urls != null) result.URLs = result.URLs.AddToDelimitedList(i.Urls); {
if (i.ImageUrls != null) releaseImageUrls.AddRange(i.ImageUrls); result.Tags = result.Tags.AddToDelimitedList(i.Tags);
if (i.ReleaseGenres != null) releaseGenres.AddRange(i.ReleaseGenres); }
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)) if (!string.IsNullOrEmpty(i.ReleaseThumbnailUrl))
{ {
releaseImages.Add(new Imaging.Image() releaseImages.Add(new Imaging.Image()
@ -486,11 +511,22 @@ namespace Roadie.Library.Engines
? SafeParser.ToEnum<ReleaseType>(i.ReleaseType) ? SafeParser.ToEnum<ReleaseType>(i.ReleaseType)
: result.ReleaseType : result.ReleaseType
}); });
if (i.ReleaseLabel != null) releaseLabels.AddRange(i.ReleaseLabel); if (i.ReleaseLabel != null)
if (i.ReleaseMedia != null) releaseMedias.AddRange(i.ReleaseMedia); {
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(); sw2.Stop();
Logger.LogTrace($"PerformMetaDataProvidersReleaseSearch: ITunesArtistSearchEngine Complete [{ sw2.ElapsedMilliseconds }]"); Logger.LogTrace($"PerformMetaDataProvidersReleaseSearch: ITunesArtistSearchEngine Complete [{ sw2.ElapsedMilliseconds }]");
} }
@ -508,13 +544,32 @@ namespace Roadie.Library.Engines
{ {
var mb = mbResult.Data.First(); var mb = mbResult.Data.First();
if (mb.AlternateNames != null) if (mb.AlternateNames != null)
{
result.AlternateNames = result.AlternateNames.AddToDelimitedList(mb.AlternateNames); 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.Tags != null)
if (mb.ReleaseGenres != null) releaseGenres.AddRange(mb.ReleaseGenres); {
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) && 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 }); result.AlternateNames.AddToDelimitedList(new[] { mb.ReleaseTitle });
} }
@ -541,11 +596,22 @@ namespace Roadie.Library.Engines
? SafeParser.ToEnum<ReleaseType>(mb.ReleaseType) ? SafeParser.ToEnum<ReleaseType>(mb.ReleaseType)
: result.ReleaseType : result.ReleaseType
}); });
if (mb.ReleaseLabel != null) releaseLabels.AddRange(mb.ReleaseLabel); if (mb.ReleaseLabel != null)
if (mb.ReleaseMedia != null) releaseMedias.AddRange(mb.ReleaseMedia); {
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(); sw2.Stop();
Logger.LogTrace($"PerformMetaDataProvidersReleaseSearch: MusicBrainzReleaseSearchEngine Complete [{ sw2.ElapsedMilliseconds }]"); Logger.LogTrace($"PerformMetaDataProvidersReleaseSearch: MusicBrainzReleaseSearchEngine Complete [{ sw2.ElapsedMilliseconds }]");
} }
@ -564,13 +630,32 @@ namespace Roadie.Library.Engines
{ {
var l = lastFmResult.Data.First(); var l = lastFmResult.Data.First();
if (l.AlternateNames != null) if (l.AlternateNames != null)
{
result.AlternateNames = result.AlternateNames.AddToDelimitedList(l.AlternateNames); 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.Tags != null)
if (l.ReleaseGenres != null) releaseGenres.AddRange(l.ReleaseGenres); {
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) && 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 }); result.AlternateNames.AddToDelimitedList(new[] { l.ReleaseTitle });
} }
@ -596,11 +681,22 @@ namespace Roadie.Library.Engines
? SafeParser.ToEnum<ReleaseType>(l.ReleaseType) ? SafeParser.ToEnum<ReleaseType>(l.ReleaseType)
: result.ReleaseType : result.ReleaseType
}); });
if (l.ReleaseLabel != null) releaseLabels.AddRange(l.ReleaseLabel); if (l.ReleaseLabel != null)
if (l.ReleaseMedia != null) releaseMedias.AddRange(l.ReleaseMedia); {
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(); sw2.Stop();
Logger.LogTrace($"PerformMetaDataProvidersReleaseSearch: LastFmReleaseSearchEngine Complete [{ sw2.ElapsedMilliseconds }]"); Logger.LogTrace($"PerformMetaDataProvidersReleaseSearch: LastFmReleaseSearchEngine Complete [{ sw2.ElapsedMilliseconds }]");
} }
@ -617,12 +713,28 @@ namespace Roadie.Library.Engines
if (spotifyResult.IsSuccess) if (spotifyResult.IsSuccess)
{ {
var s = spotifyResult.Data.First(); var s = spotifyResult.Data.First();
if (s.Tags != null) result.Tags = result.Tags.AddToDelimitedList(s.Tags); if (s.Tags != null)
if (s.Urls != null) result.URLs = result.URLs.AddToDelimitedList(s.Urls); {
if (s.ImageUrls != null) releaseImageUrls.AddRange(s.ImageUrls); result.Tags = result.Tags.AddToDelimitedList(s.Tags);
if (s.ReleaseGenres != null) releaseGenres.AddRange(s.ReleaseGenres); }
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) && 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 }); result.AlternateNames.AddToDelimitedList(new[] { s.ReleaseTitle });
} }
@ -647,11 +759,22 @@ namespace Roadie.Library.Engines
? SafeParser.ToEnum<ReleaseType>(s.ReleaseType) ? SafeParser.ToEnum<ReleaseType>(s.ReleaseType)
: result.ReleaseType : result.ReleaseType
}); });
if (s.ReleaseLabel != null) releaseLabels.AddRange(s.ReleaseLabel); if (s.ReleaseLabel != null)
if (s.ReleaseMedia != null) releaseMedias.AddRange(s.ReleaseMedia); {
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(); sw2.Stop();
Logger.LogTrace($"PerformMetaDataProvidersReleaseSearch: SpotifyReleaseSearchEngine Complete [{ sw2.ElapsedMilliseconds }]"); Logger.LogTrace($"PerformMetaDataProvidersReleaseSearch: SpotifyReleaseSearchEngine Complete [{ sw2.ElapsedMilliseconds }]");
} }
@ -668,12 +791,23 @@ namespace Roadie.Library.Engines
if (discogsResult.IsSuccess) if (discogsResult.IsSuccess)
{ {
var d = discogsResult.Data.First(); var d = discogsResult.Data.First();
if (d.Urls != null) result.URLs = result.URLs.AddToDelimitedList(d.Urls); if (d.Urls != null)
if (d.ImageUrls != null) releaseImageUrls.AddRange(d.ImageUrls); {
result.URLs = result.URLs.AddToDelimitedList(d.Urls);
}
if (d.ImageUrls != null)
{
releaseImageUrls.AddRange(d.ImageUrls);
}
if (d.AlternateNames != null) if (d.AlternateNames != null)
{
result.AlternateNames = result.AlternateNames.AddToDelimitedList(d.AlternateNames); result.AlternateNames = result.AlternateNames.AddToDelimitedList(d.AlternateNames);
}
if (!string.IsNullOrEmpty(d.ReleaseTitle) && 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 }); result.AlternateNames.AddToDelimitedList(new[] { d.ReleaseTitle });
} }
@ -694,11 +828,22 @@ namespace Roadie.Library.Engines
? SafeParser.ToEnum<ReleaseType>(d.ReleaseType) ? SafeParser.ToEnum<ReleaseType>(d.ReleaseType)
: result.ReleaseType : result.ReleaseType
}); });
if (d.ReleaseLabel != null) releaseLabels.AddRange(d.ReleaseLabel); if (d.ReleaseLabel != null)
if (d.ReleaseMedia != null) releaseMedias.AddRange(d.ReleaseMedia); {
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(); sw2.Stop();
Logger.LogTrace($"PerformMetaDataProvidersReleaseSearch: DiscogsReleaseSearchEngine Complete [{ sw2.ElapsedMilliseconds }]"); Logger.LogTrace($"PerformMetaDataProvidersReleaseSearch: DiscogsReleaseSearchEngine Complete [{ sw2.ElapsedMilliseconds }]");
} }
@ -885,7 +1030,10 @@ namespace Roadie.Library.Engines
: rmTrack.AlternateNames.AddToDelimitedList(releaseTrack.AlternateNames); : rmTrack.AlternateNames.AddToDelimitedList(releaseTrack.AlternateNames);
rmTrack.ISRC ??= releaseTrack.ISRC; rmTrack.ISRC ??= releaseTrack.ISRC;
rmTrack.LastFMId ??= releaseTrack.LastFMId; rmTrack.LastFMId ??= releaseTrack.LastFMId;
if (!foundTrack) rmTracks.Add(rmTrack); if (!foundTrack)
{
rmTracks.Add(rmTrack);
}
} }
rm.Tracks = rmTracks; rm.Tracks = rmTracks;
@ -916,7 +1064,10 @@ namespace Roadie.Library.Engines
{ {
var imageCoverByReleaseName = Array.Find(imageFilesInFolder, x => var imageCoverByReleaseName = Array.Find(imageFilesInFolder, x =>
x == result.Title || x == result.Title.ToFileNameFriendly()); x == result.Title || x == result.Title.ToFileNameFriendly());
if (imageCoverByReleaseName != null) coverFileName = imageCoverByReleaseName; if (imageCoverByReleaseName != null)
{
coverFileName = imageCoverByReleaseName;
}
} }
} }
else if (cover.Any()) else if (cover.Any())

View file

@ -15,6 +15,5 @@ namespace Roadie.Library.Extensions
var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
return Convert.ToInt64((date - epoch).TotalSeconds); return Convert.ToInt64((date - epoch).TotalSeconds);
} }
} }
} }

View file

@ -1,5 +1,4 @@
using Roadie.Library.Utility; using System;
using System;
namespace Roadie.Library.Extensions namespace Roadie.Library.Extensions
{ {
@ -17,7 +16,11 @@ namespace Roadie.Library.Extensions
public static TimeSpan? ToTimeSpan(this decimal? value) public static TimeSpan? ToTimeSpan(this decimal? value)
{ {
if (!value.HasValue) return null; if (!value.HasValue)
{
return null;
}
return TimeSpan.FromSeconds((double)value); return TimeSpan.FromSeconds((double)value);
} }
} }

View file

@ -1,8 +1,6 @@
using Roadie.Library.Utility; using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
namespace Roadie.Library.Extensions namespace Roadie.Library.Extensions
{ {
@ -10,7 +8,7 @@ namespace Roadie.Library.Extensions
{ {
public static string ToTimings(this IDictionary<string, long> values) public static string ToTimings(this IDictionary<string, long> values)
{ {
if(values == null || !values.Any()) if (values == null || !values.Any())
{ {
return null; return null;
} }
@ -24,7 +22,5 @@ namespace Roadie.Library.Extensions
} }
return string.Join(", ", timings); return string.Join(", ", timings);
} }
} }
} }

View file

@ -1,5 +1,5 @@
using Newtonsoft.Json; using System;
using System; using System.Text.Json;
namespace Roadie.Library.Extensions namespace Roadie.Library.Extensions
{ {
@ -7,12 +7,15 @@ namespace Roadie.Library.Extensions
{ {
public static string Serialize(this Exception input) public static string Serialize(this Exception input)
{ {
if (input == null) return null; if (input == null)
var settings = new JsonSerializerSettings
{ {
NullValueHandling = NullValueHandling.Ignore return null;
}; }
return JsonConvert.SerializeObject(input, Formatting.Indented, settings); return JsonSerializer.Serialize(input, new JsonSerializerOptions
{
IgnoreNullValues = true,
WriteIndented = true
});
} }
} }
} }

View file

@ -18,10 +18,16 @@ namespace Roadie.Library.Extensions
/// <returns>The copied object.</returns> /// <returns>The copied object.</returns>
public static T Clone<T>(this T source) public static T Clone<T>(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 // 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(); IFormatter formatter = new BinaryFormatter();
using (Stream stream = new MemoryStream()) using (Stream stream = new MemoryStream())
@ -37,8 +43,12 @@ namespace Roadie.Library.Extensions
var oProperties = OriginalEntity.GetType().GetProperties(); var oProperties = OriginalEntity.GetType().GetProperties();
foreach (var CurrentProperty in oProperties.Where(p => p.CanWrite)) foreach (var CurrentProperty in oProperties.Where(p => p.CanWrite))
{
if (CurrentProperty.GetValue(NewEntity, null) != null) if (CurrentProperty.GetValue(NewEntity, null) != null)
{
CurrentProperty.SetValue(OriginalEntity, CurrentProperty.GetValue(NewEntity, null), null); CurrentProperty.SetValue(OriginalEntity, CurrentProperty.GetValue(NewEntity, null), null);
}
}
return OriginalEntity; return OriginalEntity;
} }
@ -47,7 +57,11 @@ namespace Roadie.Library.Extensions
{ {
var fi = source.GetType().GetField(source.ToString()); var fi = source.GetType().GetField(source.ToString());
var attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); 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(); return source.ToString();
} }
} }

View file

@ -6,7 +6,11 @@ namespace Roadie.Library.Extensions
{ {
public static int? Or(this int? value, int? alternative) 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; return value.HasValue ? value : alternative;
} }

View file

@ -31,7 +31,6 @@ namespace Roadie.Library.Extensions
} }
} }
public static string ToDelimitedList<T>(this IList<T> list, char delimiter = '|') public static string ToDelimitedList<T>(this IList<T> list, char delimiter = '|')
{ {
return ((ICollection<T>)list).ToDelimitedList(delimiter); return ((ICollection<T>)list).ToDelimitedList(delimiter);
@ -39,7 +38,11 @@ namespace Roadie.Library.Extensions
public static string ToDelimitedList<T>(this IEnumerable<T> list, char delimiter = '|') public static string ToDelimitedList<T>(this IEnumerable<T> list, char delimiter = '|')
{ {
if (list == null || !list.Any()) return null; if (list == null || !list.Any())
{
return null;
}
return string.Join(delimiter.ToString(), list); return string.Join(delimiter.ToString(), list);
} }
} }

View file

@ -6,7 +6,11 @@ namespace Roadie.Library.Extensions
{ {
public static string ToFileSize(this long? l) 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); return string.Format(new FileSizeFormatProvider(), "{0:fs}", l);
} }
} }

View file

@ -4,14 +4,26 @@
{ {
public static short? Or(this short? value, short? alternative) 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; return value.HasValue ? value : alternative;
} }
public static short? TakeLarger(this short? value, short? alternative) public static short? TakeLarger(this short? value, short? alternative)
{ {
if (!value.HasValue && !alternative.HasValue) return null; if (!value.HasValue && !alternative.HasValue)
if (!value.HasValue && alternative.HasValue) return alternative.Value; {
return null;
}
if (!value.HasValue && alternative.HasValue)
{
return alternative.Value;
}
return value.Value > alternative.Value ? value.Value : alternative.Value; return value.Value > alternative.Value ? value.Value : alternative.Value;
} }
} }

View file

@ -8,7 +8,6 @@ using System.Linq;
using System.Net; using System.Net;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Web;
namespace Roadie.Library.Extensions namespace Roadie.Library.Extensions
{ {
@ -88,7 +87,7 @@ namespace Roadie.Library.Extensions
var rs = removeStringsRegex ?? settings.Processing.RemoveStringsRegex; var rs = removeStringsRegex ?? settings.Processing.RemoveStringsRegex;
if (!string.IsNullOrEmpty(rs)) if (!string.IsNullOrEmpty(rs))
{ {
result = Regex.Replace(result, rs, "", RegexOptions.IgnoreCase); result = Regex.Replace(result, rs, string.Empty, RegexOptions.IgnoreCase);
} }
if (result.Length > 5) if (result.Length > 5)
{ {
@ -106,9 +105,25 @@ namespace Roadie.Library.Extensions
return Regex.Replace(result, @"\s+", " ").Trim(); 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) 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("]"); var firstPart = input.Split(' ').First().SafeReplace("[").SafeReplace("]");
return SafeParser.ToNumber<long>(firstPart) > 0; return SafeParser.ToNumber<long>(firstPart) > 0;
} }
@ -116,32 +131,59 @@ namespace Roadie.Library.Extensions
public static string FromHexString(this string hexString) public static string FromHexString(this string hexString)
{ {
var bytes = new byte[hexString.Length / 2]; 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" return System.Text.Encoding.UTF8.GetString(bytes); // returns: "Hello world" for "48656C6C6F20776F726C64"
} }
public static bool IsValidFilename(this string input) public static bool IsValidFilename(this string input)
{ {
var containsABadCharacter = new Regex("[" + Regex.Escape(new string(Path.GetInvalidPathChars())) + "]"); var containsABadCharacter = new Regex($"[{Regex.Escape(new string(Path.GetInvalidPathChars()))}]");
if (containsABadCharacter.IsMatch(input)) return false; if (containsABadCharacter.IsMatch(input))
; {
return false;
};
return true; return true;
} }
public static bool IsValueInDelimitedList(this string input, string value, char delimiter = '|') 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); var p = input.Split(delimiter);
return !p.Any() ? false : p.Any(x => x.Trim().Equals(value, StringComparison.OrdinalIgnoreCase)); 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) public static string NormalizeName(this string input)
{ {
if (string.IsNullOrEmpty(input)) return input; if (string.IsNullOrEmpty(input))
{
return input;
}
input = input.ToLower(); input = input.ToLower();
var removeParts = new List<string> { " ft. ", " ft ", " feat ", " feat. " }; var removeParts = new List<string> { " 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; var cultInfo = new CultureInfo("en-US", false).TextInfo;
return cultInfo.ToTitleCase(input).Trim(); return cultInfo.ToTitleCase(input).Trim();
} }
@ -158,7 +200,10 @@ namespace Roadie.Library.Extensions
for (var i = 0; i < normalizedString.Length; i++) for (var i = 0; i < normalizedString.Length; i++)
{ {
var c = normalizedString[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(); return stringBuilder.ToString();
@ -166,7 +211,11 @@ namespace Roadie.Library.Extensions
public static string RemoveFirst(this string input, string remove = "") 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); var index = input.IndexOf(remove);
return index < 0 return index < 0
? input ? input
@ -175,7 +224,11 @@ namespace Roadie.Library.Extensions
public static string RemoveStartsWith(this string input, string remove = "") 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 index = input.IndexOf(remove);
var result = input; var result = input;
while (index == 0) while (index == 0)
@ -194,7 +247,11 @@ namespace Roadie.Library.Extensions
(sb, c) => (sb, c) =>
{ {
string r; 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); return sb.Append(c);
}).ToString(); }).ToString();
} }
@ -211,21 +268,33 @@ namespace Roadie.Library.Extensions
public static string SafeReplace(this string input, string replace, string replaceWith = " ") 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); return input.Replace(replace, replaceWith);
} }
public static string ScrubHtml(this string value) public static string ScrubHtml(this string value)
{ {
var step1 = Regex.Replace(value, @"<[^>]+>|&nbsp;", "").Trim(); var step1 = Regex.Replace(value, @"<[^>]+>|&nbsp;", string.Empty).Trim();
var step2 = Regex.Replace(step1, @"\s{2,}", " "); var step2 = Regex.Replace(step1, @"\s{2,}", " ");
return step2; return step2;
} }
public static string StripStartingNumber(this string input) public static string StripStartingNumber(this string input)
{ {
if (string.IsNullOrEmpty(input)) return null; if (string.IsNullOrEmpty(input))
if (input.DoesStartWithNumber()) return string.Join(" ", input.Split(' ').Skip(1)); {
return null;
}
if (input.DoesStartWithNumber())
{
return string.Join(" ", input.Split(' ').Skip(1));
}
return input; return input;
} }
@ -240,23 +309,40 @@ namespace Roadie.Library.Extensions
.Replace("%", "per"); .Replace("%", "per");
input = WebUtility.HtmlDecode(input); input = WebUtility.HtmlDecode(input);
input = input.ScrubHtml().ToLower() input = input.ScrubHtml().ToLower()
.Replace("&", "and"); .Replace("&", "and");
var arr = input.ToCharArray(); var arr = input.ToCharArray();
arr = Array.FindAll(arr, c => (c == ',' && !stripCommas) || (char.IsWhiteSpace(c) && !stripSpaces) || char.IsLetterOrDigit(c)); arr = Array.FindAll(arr, c => (c == ',' && !stripCommas) || (char.IsWhiteSpace(c) && !stripSpaces) || char.IsLetterOrDigit(c));
input = new string(arr).RemoveDiacritics().RemoveUnicodeAccents().Translit(); 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; return input;
} }
public static string ToContentDispositionFriendly(this string input) public static string ToContentDispositionFriendly(this string input)
{ {
if (string.IsNullOrEmpty(input)) return null; if (string.IsNullOrEmpty(input))
{
return null;
}
return input.Replace(',', ' '); return input.Replace(',', ' ');
} }
public static string ToCSV(this IEnumerable<string> input)
{
if (input == null || !input.Any())
{
return null;
}
return string.Join(",", input);
}
public static string ToFileNameFriendly(this string 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(); return Regex.Replace(PathSanitizer.SanitizeFilename(input, ' '), @"\s+", " ").Trim();
} }
@ -275,14 +361,21 @@ namespace Roadie.Library.Extensions
var sb = new StringBuilder(); var sb = new StringBuilder();
var bytes = System.Text.Encoding.UTF8.GetBytes(str); 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" return sb.ToString(); // returns: "48656C6C6F20776F726C64" for "Hello world"
} }
public static IEnumerable<string> ToListFromDelimited(this string input, char delimiter = '|') public static IEnumerable<string> 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); return input.Split(delimiter);
} }
@ -300,7 +393,7 @@ namespace Roadie.Library.Extensions
{ {
if (r.StartsWith("The ")) if (r.StartsWith("The "))
{ {
r = r.Replace("The ", "") + ", The"; r = $"{(r.Replace("The ", string.Empty))}, The";
} }
} }
return r.NameCase(); return r.NameCase();
@ -309,21 +402,36 @@ namespace Roadie.Library.Extensions
public static int? ToTrackDuration(this string input) 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 try
{ {
var parts = input.Contains(":") ? input.Split(':').ToList() : new List<string> { input }; var parts = input.Contains(":") ? input.Split(':').ToList() : new List<string> { input };
while (parts.Count() < 3) parts.Insert(0, "00:"); while (parts.Count() < 3)
{
parts.Insert(0, "00:");
}
var tsRaw = string.Empty; var tsRaw = string.Empty;
foreach (var part in parts) foreach (var part in parts)
{ {
if (tsRaw.Length > 0) tsRaw += ":"; if (tsRaw.Length > 0)
{
tsRaw += ":";
}
tsRaw += part.PadLeft(2, '0').Substring(0, 2); tsRaw += part.PadLeft(2, '0').Substring(0, 2);
} }
var ts = TimeSpan.MinValue; var ts = TimeSpan.MinValue;
var success = TimeSpan.TryParse(tsRaw, out ts); var success = TimeSpan.TryParse(tsRaw, out ts);
if (success) return (int?)ts.TotalMilliseconds; if (success)
{
return (int?)ts.TotalMilliseconds;
}
} }
catch catch
{ {
@ -366,27 +474,11 @@ namespace Roadie.Library.Extensions
public static string TrimEnd(this string input, string suffixToRemove) public static string TrimEnd(this string input, string suffixToRemove)
{ {
if (input != null && suffixToRemove != null && input.EndsWith(suffixToRemove)) if (input != null && suffixToRemove != null && input.EndsWith(suffixToRemove))
{
return input.Substring(0, input.Length - suffixToRemove.Length); return input.Substring(0, input.Length - suffixToRemove.Length);
}
return input; 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<string> input)
{
if(input == null || !input.Any())
{
return null;
}
return string.Join(",", input);
}
} }
} }

View file

@ -6,7 +6,11 @@ namespace Roadie.Library.Extensions
{ {
public static string ToDuration(this TimeSpan input) 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"); return input.ToString(@"ddd\.hh\:mm\:ss");
} }
} }

View file

@ -1,5 +1,4 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Roadie.Library.Caching; using Roadie.Library.Caching;
using Roadie.Library.Configuration; using Roadie.Library.Configuration;
using Roadie.Library.Encoding; using Roadie.Library.Encoding;
@ -14,6 +13,7 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Roadie.Library.FilePlugins namespace Roadie.Library.FilePlugins
@ -98,9 +98,16 @@ namespace Roadie.Library.FilePlugins
return result; return result;
} }
if (CheckMakeFolder(artistFolder)) Logger.LogTrace("Created ArtistFolder [{0}]", artistFolder); if (CheckMakeFolder(artistFolder))
if (CheckMakeFolder(releaseFolder)) Logger.LogTrace("Created ReleaseFolder [{0}]", releaseFolder); {
Logger.LogTrace("Created ArtistFolder [{0}]", artistFolder);
}
if (CheckMakeFolder(releaseFolder))
{
Logger.LogTrace("Created ReleaseFolder [{0}]", releaseFolder);
}
string imageFilename = null;
try try
{ {
// See if file folder parent folder (likely file is in release folder) has primary artist image if so then move to artist folder // 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) if (artistImages.Count > 0)
{ {
var artistImage = artistImages[0]; var artistImage = artistImages[0];
var artistImageFilename = Path.Combine(artistFolder, ImageHelper.ArtistImageFilename); imageFilename = Path.Combine(artistFolder, ImageHelper.ArtistImageFilename);
if (artistImageFilename != artistImage.FullName) if (imageFilename != artistImage.FullName)
{ {
// Read image and convert to jpeg // Read image and convert to jpeg
var imageBytes = File.ReadAllBytes(artistImage.FullName); var imageBytes = File.ReadAllBytes(artistImage.FullName);
@ -120,7 +127,7 @@ namespace Roadie.Library.FilePlugins
// Move artist image to artist folder // Move artist image to artist folder
if (!doJustInfo) if (!doJustInfo)
{ {
File.WriteAllBytes(artistImageFilename, imageBytes); File.WriteAllBytes(imageFilename, imageBytes);
artistImage.Delete(); artistImage.Delete();
} }
@ -179,7 +186,10 @@ namespace Roadie.Library.FilePlugins
// Read image and convert to jpeg // Read image and convert to jpeg
var imageBytes = File.ReadAllBytes(releaseImage.FullName); var imageBytes = File.ReadAllBytes(releaseImage.FullName);
imageBytes = ImageHelper.ConvertToJpegFormat(imageBytes); imageBytes = ImageHelper.ConvertToJpegFormat(imageBytes);
if(imageBytes == null)
{
Logger.LogWarning($"Unable to read image [{ releaseImage.FullName }]");
}
// Move cover to release folder // Move cover to release folder
if (!doJustInfo) if (!doJustInfo)
{ {
@ -199,8 +209,7 @@ namespace Roadie.Library.FilePlugins
foreach (var releaseImage in releaseImages) foreach (var releaseImage in releaseImages)
{ {
looper++; looper++;
var releaseImageFilename = Path.Combine(releaseFolder, var releaseImageFilename = Path.Combine(releaseFolder, string.Format(ImageHelper.ReleaseSecondaryImageFilename, looper.ToString("00")));
string.Format(ImageHelper.ReleaseSecondaryImageFilename, looper.ToString("00")));
if (releaseImageFilename != releaseImage.FullName) if (releaseImageFilename != releaseImage.FullName)
{ {
// Read image and convert to jpeg // Read image and convert to jpeg
@ -221,7 +230,7 @@ namespace Roadie.Library.FilePlugins
} }
catch (Exception ex) 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); var doesFileExistsForTrack = File.Exists(destinationName);
@ -291,8 +300,7 @@ namespace Roadie.Library.FilePlugins
} }
sw.Stop(); sw.Stop();
Logger.LogTrace("<< Audio: Process Complete. Result `{0}`, ElapsedTime [{1}]", Logger.LogTrace("<< Audio: Process Complete. Result `{0}`, ElapsedTime [{1}]", JsonSerializer.Serialize(result), sw.ElapsedMilliseconds);
JsonConvert.SerializeObject(result), sw.ElapsedMilliseconds);
return result; return result;
} }

View file

@ -1,4 +1,5 @@
using Roadie.Library.Configuration; using ImageMagick;
using Roadie.Library.Configuration;
using Roadie.Library.Enums; using Roadie.Library.Enums;
using Roadie.Library.Extensions; using Roadie.Library.Extensions;
using Roadie.Library.MetaData.Audio; using Roadie.Library.MetaData.Audio;
@ -20,121 +21,123 @@ namespace Roadie.Library.Imaging
public static class ImageHelper public static class ImageHelper
{ {
public static string ArtistImageFilename = "artist.jpg"; public static string ArtistImageFilename = "artist.jpg";
public static string ArtistSecondaryImageFilename = "artist {0}.jpg"; // Replace with counter of image public static string ArtistSecondaryImageFilename = "artist {0}.jpg"; // Replace with counter of image
public static string ReleaseCoverFilename = "cover.jpg"; public static string ReleaseCoverFilename = "cover.jpg";
public static string ReleaseSecondaryImageFilename = "release {0}.jpg"; // Replace with counter of image 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;
}
/// <summary> /// <summary>
/// Only user avatars are GIF to allow for animation. /// Only user avatars are GIF to allow for animation.
/// </summary> /// </summary>
public static byte[] ConvertToGifFormat(byte[] imageBytes) public static byte[] ConvertToGifFormat(byte[] imageBytes)
{ {
if(imageBytes?.Any() != true) if (imageBytes?.Any() != true)
{ {
return null; return null;
} }
try try
{ {
using(var outStream = new MemoryStream()) using (var outStream = new MemoryStream())
{ {
IImageFormat imageFormat = null; 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); image.SaveAsGif(outStream);
} }
return outStream.ToArray(); return outStream.ToArray();
} }
} catch(Exception ex) }
catch (Exception ex)
{ {
Trace.WriteLine($"Error Converting Image to Gif [{ ex.Message }]", "Warning"); Trace.WriteLine($"Error Converting Image to Gif [{ ex.Message }]", "Warning");
} }
return null; 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<FileInfo> FindImagesByName(DirectoryInfo directory, public static IEnumerable<FileInfo> FindImagesByName(DirectoryInfo directory,
string name, string name,
SearchOption folderSearchOptions = SearchOption.AllDirectories) SearchOption folderSearchOptions = SearchOption.AllDirectories)
{ {
var result = new List<FileInfo>(); var result = new List<FileInfo>();
if(directory?.Exists != true || string.IsNullOrEmpty(name)) if (directory?.Exists != true || string.IsNullOrEmpty(name))
{
return result; return result;
}
var imageFilesInFolder = ImageFilesInFolder(directory.FullName, folderSearchOptions); var imageFilesInFolder = ImageFilesInFolder(directory.FullName, folderSearchOptions);
if(imageFilesInFolder?.Any() != true) if (imageFilesInFolder?.Any() != true)
{
return result; return result;
if(imageFilesInFolder.Length > 0) }
if (imageFilesInFolder.Length > 0)
{ {
name = name.ToAlphanumericName(); name = name.ToAlphanumericName();
foreach(var imageFileInFolder in imageFilesInFolder) foreach (var imageFileInFolder in imageFilesInFolder)
{ {
var image = new FileInfo(imageFileInFolder); var image = new FileInfo(imageFileInFolder);
var filenameWithoutExtension = Path.GetFileName(imageFileInFolder).ToAlphanumericName(); var filenameWithoutExtension = Path.GetFileName(imageFileInFolder).ToAlphanumericName();
var imageName = image.Name.ToAlphanumericName(); var imageName = image.Name.ToAlphanumericName();
if(imageName.Equals(name) || filenameWithoutExtension.Equals(name)) if (imageName.Equals(name) || filenameWithoutExtension.Equals(name))
{
result.Add(image); result.Add(image);
}
} }
} }
return result; 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<FileInfo> FindImageTypeInDirectory(DirectoryInfo directory, public static IEnumerable<FileInfo> FindImageTypeInDirectory(DirectoryInfo directory,
ImageType type, ImageType type,
SearchOption folderSearchOptions = SearchOption.AllDirectories) SearchOption folderSearchOptions = SearchOption.AllDirectories)
@ -149,10 +152,10 @@ namespace Roadie.Library.Imaging
{ {
return result; return result;
} }
foreach(var imageFile in imageFilesInFolder) foreach (var imageFile in imageFilesInFolder)
{ {
var image = new FileInfo(imageFile); var image = new FileInfo(imageFile);
switch(type) switch (type)
{ {
case ImageType.Artist: case ImageType.Artist:
if (IsArtistImage(image)) if (IsArtistImage(image))
@ -197,174 +200,21 @@ namespace Roadie.Library.Imaging
string[] patterns = null, string[] patterns = null,
SearchOption options = SearchOption.TopDirectoryOnly) SearchOption options = SearchOption.TopDirectoryOnly)
{ {
if(!Directory.Exists(path)) if (!Directory.Exists(path))
{ {
return new string[0]; return new string[0];
} }
if(patterns == null || patterns.Length == 0) if (patterns == null || patterns.Length == 0)
{ {
return Directory.GetFiles(path, "*", options); return Directory.GetFiles(path, "*", options);
} }
if(patterns.Length == 1) if (patterns.Length == 1)
{ {
return Directory.GetFiles(path, patterns[0], options); return Directory.GetFiles(path, patterns[0], options);
} }
return patterns.SelectMany(pattern => Directory.GetFiles(path, pattern, options)).Distinct().ToArray(); 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;
/// <summary>
/// Resize a given image to given dimensions if needed
/// </summary>
/// <param name="imageBytes">Image bytes to resize</param>
/// <param name="width">Resize to width</param>
/// <param name="height">Resize to height</param>
/// <param name="forceResize">Force resize</param>
/// <returns>Tuple with bool for did resize and byte array of image</returns>
public static Tuple<bool, byte[]> 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<bool, byte[]>(resized, outStream.ToArray());
}
} catch(Exception ex)
{
Trace.WriteLine($"Error Resizing Image [{ex}]", "Warning");
}
return null;
}
/// <summary> /// <summary>
/// Get image data from all sources for either fileanme or MetaData /// Get image data from all sources for either fileanme or MetaData
/// </summary> /// </summary>
@ -380,6 +230,29 @@ namespace Roadie.Library.Imaging
return ImageForFilename(configuration, filename); 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); }
/// <summary> /// <summary>
/// Does image exist with the same filename /// Does image exist with the same filename
/// </summary> /// </summary>
@ -389,7 +262,7 @@ namespace Roadie.Library.Imaging
{ {
AudioMetaDataImage imageMetaData = null; AudioMetaDataImage imageMetaData = null;
if(string.IsNullOrEmpty(filename)) if (string.IsNullOrEmpty(filename))
{ {
return imageMetaData; return imageMetaData;
} }
@ -397,9 +270,9 @@ namespace Roadie.Library.Imaging
{ {
var fileInfo = new FileInfo(filename); var fileInfo = new FileInfo(filename);
var ReleaseCover = Path.ChangeExtension(filename, "jpg"); 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 imageMetaData = new AudioMetaDataImage
{ {
@ -408,23 +281,31 @@ namespace Roadie.Library.Imaging
MimeType = Library.Processors.FileProcessor.DetermineFileType(fileInfo) MimeType = Library.Processors.FileProcessor.DetermineFileType(fileInfo)
}; };
} }
} else }
else
{ {
// Is there a picture in filename folder (for the Release) // Is there a picture in filename folder (for the Release)
var pictures = fileInfo.Directory.GetFiles("*.jpg"); var pictures = fileInfo.Directory.GetFiles("*.jpg");
var tagImages = new List<AudioMetaDataImage>(); var tagImages = new List<AudioMetaDataImage>();
if(pictures?.Any() == true) if (pictures?.Any() == true)
{ {
// See if there is a "cover" or "front" jpg file if so use it // See if there is a "cover" or "front" jpg file if so use it
FileInfo picture = Array.Find(pictures, FileInfo picture = Array.Find(pictures,
x => x.Name.Equals("cover", StringComparison.OrdinalIgnoreCase)); x => x.Name.Equals("cover", StringComparison.OrdinalIgnoreCase));
if(picture == null) if (picture == null)
{
picture = Array.Find(pictures, picture = Array.Find(pictures,
x => x.Name.Equals("front", StringComparison.OrdinalIgnoreCase)); x => x.Name.Equals("front", StringComparison.OrdinalIgnoreCase));
if(picture == null) }
if (picture == null)
{
picture = pictures[0]; picture = pictures[0];
if(picture != null) }
using(var processor = new ImageProcessor(configuration))
if (picture != null)
{
using (var processor = new ImageProcessor(configuration))
{ {
imageMetaData = new AudioMetaDataImage imageMetaData = new AudioMetaDataImage
{ {
@ -433,11 +314,14 @@ namespace Roadie.Library.Imaging
MimeType = Library.Processors.FileProcessor.DetermineFileType(picture) MimeType = Library.Processors.FileProcessor.DetermineFileType(picture)
}; };
} }
}
} }
} }
} catch(FileNotFoundException) }
catch (FileNotFoundException)
{ {
} catch(Exception ex) }
catch (Exception ex)
{ {
Trace.WriteLine(ex, ex.Serialize()); Trace.WriteLine(ex, ex.Serialize());
} }
@ -445,58 +329,132 @@ namespace Roadie.Library.Imaging
return imageMetaData; return imageMetaData;
} }
public static models.Image MakeThumbnailImage(IRoadieSettings configuration, public static string[] ImageMimeTypes()
IHttpContext httpContext, { return new string[4] { "image/bmp", "image/jpeg", "image/png", "image/gif" }; }
Guid id,
string type, public static ImageSearchResult ImageSearchResultForImageUrl(string imageUrl)
int? width = null,
int? height = null,
bool includeCachebuster = false)
{ {
return MakeImage(configuration, if (!WebHelper.IsStringUrl(imageUrl))
httpContext, {
id, return null;
type, }
width ?? configuration.ThumbnailImageSize.Width,
height ?? configuration.ThumbnailImageSize.Height, var result = new ImageSearchResult();
null, var imageBytes = WebHelper.BytesForImageUrl(imageUrl);
includeCachebuster); 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) public static bool IsArtistImage(FileInfo fileinfo)
{ return new models.Image($"{httpContext.ImageBaseUrl}/{type}.jpg", null, null); } {
if (fileinfo == null)
{
return false;
}
public static models.Image MakePlaylistThumbnailImage(IRoadieSettings configuration, return Regex.IsMatch(fileinfo.Name,
IHttpContext httpContext, @"(band|artist|group|photo)\.(jpg|jpeg|png|bmp|gif)",
Guid id) RegexOptions.IgnoreCase);
{ return MakeThumbnailImage(configuration, httpContext, id, "playlist"); } }
public static models.Image MakeReleaseThumbnailImage(IRoadieSettings configuration, public static bool IsArtistSecondaryImage(FileInfo fileinfo)
IHttpContext httpContext, {
Guid id) if (fileinfo == null)
{ return MakeThumbnailImage(configuration, httpContext, id, "release"); } {
return false;
}
public static models.Image MakeTrackThumbnailImage(IRoadieSettings configuration, return Regex.IsMatch(fileinfo.Name,
IHttpContext httpContext, @"(artist_logo|logo|photo[-_\s]*[0-9]+|(artist[\s_-]+[0-9]+)|(band[\s_-]+[0-9]+))\.(jpg|jpeg|png|bmp|gif)",
Guid id) RegexOptions.IgnoreCase);
{ return MakeThumbnailImage(configuration, httpContext, id, "track"); } }
public static models.Image MakeUserThumbnailImage(IRoadieSettings configuration, public static bool IsImageBetterQuality(string image1, string compareToImage)
IHttpContext httpContext, {
Guid id) if (string.IsNullOrEmpty(compareToImage))
{ return MakeThumbnailImage(configuration, httpContext, id, "user"); } {
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, public static bool IsLabelImage(FileInfo fileinfo)
IHttpContext httpContext, {
Guid id) if (fileinfo == null)
{ return MakeThumbnailImage(configuration, httpContext, id, "label"); } {
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, public static models.Image MakeArtistThumbnailImage(IRoadieSettings configuration,
IHttpContext httpContext, IHttpContext httpContext,
Guid? id) Guid? id)
{ {
if(!id.HasValue) if (!id.HasValue)
{
return null; return null;
}
return MakeThumbnailImage(configuration, httpContext, id.Value, "artist"); return MakeThumbnailImage(configuration, httpContext, id.Value, "artist");
} }
@ -522,7 +480,7 @@ namespace Roadie.Library.Imaging
int imageId, int imageId,
string caption = null) string caption = null)
{ {
if(type == ImageType.ArtistSecondary) if (type == ImageType.ArtistSecondary)
{ {
return new models.Image($"{httpContext.ImageBaseUrl}/artist-secondary/{id}/{imageId}", return new models.Image($"{httpContext.ImageBaseUrl}/artist-secondary/{id}/{imageId}",
caption, caption,
@ -538,8 +496,12 @@ namespace Roadie.Library.Imaging
Guid id) Guid id)
{ return MakeThumbnailImage(configuration, httpContext, id, "genre"); } { return MakeThumbnailImage(configuration, httpContext, id, "genre"); }
public static models.Image MakeUnknownImage(IHttpContext httpContext, string unknownType = "unknown") public static models.Image MakeImage(IRoadieSettings configuration,
{ return new models.Image($"{httpContext.ImageBaseUrl}/{ unknownType }.jpg"); } 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, public static models.Image MakeImage(IRoadieSettings configuration,
IHttpContext httpContext, IHttpContext httpContext,
@ -554,13 +516,6 @@ namespace Roadie.Library.Imaging
$"{httpContext.ImageBaseUrl}/{id}/{configuration.SmallImageSize.Width}/{configuration.SmallImageSize.Height}"); $"{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, public static models.Image MakeImage(IRoadieSettings configuration,
IHttpContext httpContext, IHttpContext httpContext,
Guid id, Guid id,
@ -570,14 +525,135 @@ namespace Roadie.Library.Imaging
string caption = null, string caption = null,
bool includeCachebuster = false) bool includeCachebuster = false)
{ {
if(width.HasValue && if (width.HasValue &&
height.HasValue && height.HasValue &&
(width.Value != configuration.ThumbnailImageSize.Width || (width.Value != configuration.ThumbnailImageSize.Width ||
height.Value != configuration.ThumbnailImageSize.Height)) height.Value != configuration.ThumbnailImageSize.Height))
{
return new models.Image($"{httpContext.ImageBaseUrl}/{type}/{id}/{width}/{height}/{(includeCachebuster ? DateTime.UtcNow.Ticks.ToString() : string.Empty)}", return new models.Image($"{httpContext.ImageBaseUrl}/{type}/{id}/{width}/{height}/{(includeCachebuster ? DateTime.UtcNow.Ticks.ToString() : string.Empty)}",
caption, caption,
$"{httpContext.ImageBaseUrl}/{type}/{id}/{configuration.ThumbnailImageSize.Width}/{configuration.ThumbnailImageSize.Height}"); $"{httpContext.ImageBaseUrl}/{type}/{id}/{configuration.ThumbnailImageSize.Width}/{configuration.ThumbnailImageSize.Height}");
}
return new models.Image($"{httpContext.ImageBaseUrl}/{type}/{id}", caption, null); 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;
/// <summary>
/// Resize a given image to given dimensions if needed
/// </summary>
/// <param name="imageBytes">Image bytes to resize</param>
/// <param name="width">Resize to width</param>
/// <param name="height">Resize to height</param>
/// <param name="forceResize">Force resize</param>
/// <returns>Tuple with bool for did resize and byte array of image</returns>
public static Tuple<bool, byte[]> 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<bool, byte[]>(resized, outStream.ToArray());
}
}
catch (Exception ex)
{
Trace.WriteLine($"Error Resizing Image [{ex}]", "Warning");
}
return null;
}
} }
} }

View file

@ -3,7 +3,6 @@ using HashidsNet;
using Mapster; using Mapster;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Roadie.Library.Caching; using Roadie.Library.Caching;
using Roadie.Library.Configuration; using Roadie.Library.Configuration;
using Roadie.Library.Enums; using Roadie.Library.Enums;
@ -21,6 +20,7 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Management.Automation; using System.Management.Automation;
using System.Text.Json;
namespace Roadie.Library.Inspect namespace Roadie.Library.Inspect
{ {
@ -28,7 +28,13 @@ namespace Roadie.Library.Inspect
{ {
private const string Salt = "6856F2EE-5965-4345-884B-2CCA457AAF59"; 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<IInspectorDirectoryPlugin> _directoryPlugins; private IEnumerable<IInspectorDirectoryPlugin> _directoryPlugins;
private IEnumerable<IInspectorFilePlugin> _filePlugins; private IEnumerable<IInspectorFilePlugin> _filePlugins;
public DictionaryCacheManager CacheManager { get; } public DictionaryCacheManager CacheManager { get; }
@ -47,6 +53,7 @@ namespace Roadie.Library.Inspect
.SelectMany(s => s.GetTypes()) .SelectMany(s => s.GetTypes())
.Where(p => type.IsAssignableFrom(p)); .Where(p => type.IsAssignableFrom(p));
foreach (var t in types) foreach (var t in types)
{
if (t.GetInterface("IInspectorDirectoryPlugin") != null && !t.IsAbstract && !t.IsInterface) if (t.GetInterface("IInspectorDirectoryPlugin") != null && !t.IsAbstract && !t.IsInterface)
{ {
var plugin = Activator.CreateInstance(t, Configuration, CacheManager, Logger, TagsHelper) as IInspectorDirectoryPlugin; 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}]"); Console.WriteLine($"╠╣ Not Loading Disabled Plugin [{plugin.Description}]");
} }
} }
}
} }
catch (Exception ex) 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() public Inspector()
{ {
Console.WriteLine("Roadie Media Inspector");
MessageLogger = new EventMessageLogger<Inspector>(); MessageLogger = new EventMessageLogger<Inspector>();
MessageLogger.Messages += MessageLogger_Messages; MessageLogger.Messages += MessageLogger_Messages;
@ -135,31 +133,141 @@ namespace Roadie.Library.Inspect
configuration.GetSection("RoadieSettings").Bind(settings); configuration.GetSection("RoadieSettings").Bind(settings);
settings.ConnectionString = configuration.GetConnectionString("RoadieDatabaseConnection"); settings.ConnectionString = configuration.GetConnectionString("RoadieDatabaseConnection");
Configuration = settings; 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<ID3TagsHelper>(); var tagHelperLooper = new EventMessageLogger<ID3TagsHelper>();
tagHelperLooper.Messages += MessageLogger_Messages; tagHelperLooper.Messages += MessageLogger_Messages;
TagsHelper = new ID3TagsHelper(Configuration, CacheManager, tagHelperLooper); TagsHelper = new ID3TagsHelper(Configuration, CacheManager, tagHelperLooper);
} }
public static string ArtistInspectorToken(AudioMetaData metaData) => ToToken(metaData.Artist); 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)
{ {
var hashids = new Hashids(Salt); if (!image.Exists)
var numbers = 0;
var bytes = System.Text.Encoding.ASCII.GetBytes(input);
var looper = bytes.Length / 4;
for (var i = 0; i < looper; i++)
{ {
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) public void Inspect(bool doCopy, bool isReadOnly, string directoryToInspect, string destination, bool dontAppendSubFolder, bool dontDeleteEmptyFolders, bool dontRunPreScripts)
{ {
Configuration.Inspector.IsInReadOnlyMode = isReadOnly; Configuration.Inspector.IsInReadOnlyMode = isReadOnly;
@ -171,13 +279,32 @@ namespace Roadie.Library.Inspect
Trace.Listeners.Add(new LoggingTraceListener()); 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.BackgroundColor = ConsoleColor.White;
Console.ForegroundColor = ConsoleColor.Blue; Console.ForegroundColor = ConsoleColor.Blue;
Console.WriteLine($"✨ Inspector Start, UTC [{DateTime.UtcNow.ToString("s")}]"); Console.WriteLine($"✨ Inspector Start, UTC [{DateTime.UtcNow.ToString("s")}]");
Console.ResetColor(); 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; string scriptResult = null;
// Run PreInspect script // Run PreInspect script
dontRunPreScripts = File.Exists(Configuration.Processing.PreInspectScript) && dontRunPreScripts;
if (dontRunPreScripts) if (dontRunPreScripts)
{ {
Console.BackgroundColor = ConsoleColor.Blue; Console.BackgroundColor = ConsoleColor.Blue;
@ -198,7 +325,10 @@ namespace Roadie.Library.Inspect
} }
// Create a new destination subfolder for each Inspector run by Current timestamp // Create a new destination subfolder for each Inspector run by Current timestamp
var dest = Path.Combine(destination, DateTime.UtcNow.ToString("yyyyMMddHHmm")); 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 // Get all the directorys in the directory
var directoryDirectories = Directory.GetDirectories(directoryToInspect, "*.*", SearchOption.AllDirectories); var directoryDirectories = Directory.GetDirectories(directoryToInspect, "*.*", SearchOption.AllDirectories);
var directories = new List<string> var directories = new List<string>
@ -239,7 +369,7 @@ namespace Roadie.Library.Inspect
var pluginResult = plugin.Process(directoryInfo); var pluginResult = plugin.Process(directoryInfo);
if (!pluginResult.IsSuccess) if (!pluginResult.IsSuccess)
{ {
Console.WriteLine($"📛 Plugin Failed: Error [{JsonConvert.SerializeObject(pluginResult)}]"); Console.WriteLine($"📛 Plugin Failed: Error [{JsonSerializer.Serialize(pluginResult)}]");
return; return;
} }
if (!string.IsNullOrEmpty(pluginResult.Data)) if (!string.IsNullOrEmpty(pluginResult.Data))
@ -261,7 +391,11 @@ namespace Roadie.Library.Inspect
Console.WriteLine($"╟─ 🎵 Inspecting [{fileInfo.FullName}]"); Console.WriteLine($"╟─ 🎵 Inspecting [{fileInfo.FullName}]");
var tagLib = TagsHelper.MetaDataForFile(fileInfo.FullName, true); var tagLib = TagsHelper.MetaDataForFile(fileInfo.FullName, true);
Console.ForegroundColor = ConsoleColor.Cyan; 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.WriteLine($"╟ (Pre ) : {tagLib.Data}");
Console.ResetColor(); Console.ResetColor();
tagLib.Data.Filename = fileInfo.FullName; tagLib.Data.Filename = fileInfo.FullName;
@ -270,7 +404,7 @@ namespace Roadie.Library.Inspect
{ {
Console.ForegroundColor = ConsoleColor.DarkYellow; Console.ForegroundColor = ConsoleColor.DarkYellow;
Console.WriteLine($"╟ ❗ INVALID: Missing: {ID3TagsHelper.DetermineMissingRequiredMetaData(originalMetaData)}"); 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(); Console.ResetColor();
} }
@ -283,7 +417,7 @@ namespace Roadie.Library.Inspect
if (!pluginResult.IsSuccess) if (!pluginResult.IsSuccess)
{ {
Console.ForegroundColor = ConsoleColor.Red; Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"📛 Plugin Failed: Error [{JsonConvert.SerializeObject(pluginResult)}]"); Console.WriteLine($"📛 Plugin Failed: Error [{JsonSerializer.Serialize(pluginResult)}]");
Console.ResetColor(); Console.ResetColor();
return; return;
} }
@ -421,7 +555,11 @@ namespace Roadie.Library.Inspect
{ {
if (fileInfo.FullName != newPath) if (fileInfo.FullName != newPath)
{ {
if (File.Exists(newPath)) File.Delete(newPath); if (File.Exists(newPath))
{
File.Delete(newPath);
}
fileInfo.MoveTo(newPath); fileInfo.MoveTo(newPath);
} }
} }
@ -453,12 +591,14 @@ namespace Roadie.Library.Inspect
var pluginResult = plugin.Process(directoryInfo); var pluginResult = plugin.Process(directoryInfo);
if (!pluginResult.IsSuccess) if (!pluginResult.IsSuccess)
{ {
Console.WriteLine($"📛 Plugin Failed: Error [{JsonConvert.SerializeObject(pluginResult)}]"); Console.WriteLine($"📛 Plugin Failed: Error [{JsonSerializer.Serialize(pluginResult)}]");
return; return;
} }
if (!string.IsNullOrEmpty(pluginResult.Data)) if (!string.IsNullOrEmpty(pluginResult.Data))
{
Console.WriteLine($"╠╣ Directory Plugin Message: {pluginResult.Data}"); Console.WriteLine($"╠╣ Directory Plugin Message: {pluginResult.Data}");
}
} }
} }
@ -469,7 +609,7 @@ namespace Roadie.Library.Inspect
catch (Exception ex) catch (Exception ex)
{ {
Logger.LogError(ex); Logger.LogError(ex);
Console.WriteLine("📛 Exception: " + ex); Console.WriteLine($"📛 Exception: {ex}");
} }
if (!dontDeleteEmptyFolders) 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; numbers += BitConverter.ToInt32(bytes, i * 4);
Console.WriteLine($"╟ ■ InspectImage: Image Not Found [{image.FullName}]"); }
Console.ResetColor(); if (numbers < 0)
return; {
numbers *= -1;
} }
Console.WriteLine($"╟─ Inspecting Image [{image.FullName}]"); return hashids.Encode(numbers);
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;
} }
} }

View file

@ -36,7 +36,11 @@ namespace Roadie.Library.Inspect.Plugins.Directory
{ {
if (fileExtensionsToDelete.Any(x => x.Equals(file.Extension, StringComparison.OrdinalIgnoreCase))) 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); deletedFiles.Add(file.Name);
Console.WriteLine($" X Deleted File [{file}], Was found in in FileExtensionsToDelete"); Console.WriteLine($" X Deleted File [{file}], Was found in in FileExtensionsToDelete");
} }

View file

@ -36,10 +36,13 @@ namespace Roadie.Library.Inspect.Plugins.Directory
var firstMetaData = metaDatasForFilesInFolder.OrderBy(x => x.Filename ?? string.Empty) var firstMetaData = metaDatasForFilesInFolder.OrderBy(x => x.Filename ?? string.Empty)
.ThenBy(x => SafeParser.ToNumber<short>(x.TrackNumber)).FirstOrDefault(); .ThenBy(x => SafeParser.ToNumber<short>(x.TrackNumber)).FirstOrDefault();
if (firstMetaData == null) if (firstMetaData == null)
{
return new OperationResult<string>("Error Getting First MetaData") return new OperationResult<string>("Error Getting First MetaData")
{ {
Data = $"Unable to read Metadatas for Directory [{directory.FullName}]" Data = $"Unable to read Metadatas for Directory [{directory.FullName}]"
}; };
}
var artist = firstMetaData.Artist; var artist = firstMetaData.Artist;
foreach (var metaData in metaDatasForFilesInFolder.Where(x => x.Artist != artist)) foreach (var metaData in metaDatasForFilesInFolder.Where(x => x.Artist != artist))
{ {
@ -47,7 +50,10 @@ namespace Roadie.Library.Inspect.Plugins.Directory
Console.WriteLine( Console.WriteLine(
$"╟ Setting Artist to [{artist}], was [{metaData.Artist}] on file [{metaData.FileInfo.Name}"); $"╟ Setting Artist to [{artist}], was [{metaData.Artist}] on file [{metaData.FileInfo.Name}");
metaData.Artist = artist; 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"; data = $"Found [{found}] files, Modified [{modified}] files";

View file

@ -39,7 +39,10 @@ namespace Roadie.Library.Inspect.Plugins.Directory
Console.WriteLine( Console.WriteLine(
$"╟ Setting Release to [{release}], was [{metaData.Release}] on file [{metaData.FileInfo.Name}"); $"╟ Setting Release to [{release}], was [{metaData.Release}] on file [{metaData.FileInfo.Name}");
metaData.Release = release; 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"; data = $"Found [{found}] files, Modified [{modified}] files";

View file

@ -38,7 +38,10 @@ namespace Roadie.Library.Inspect.Plugins.Directory
Console.WriteLine( Console.WriteLine(
$"╟ Setting Year to [{year}], was [{metaData.Year}] on file [{metaData.FileInfo.Name}"); $"╟ Setting Year to [{year}], was [{metaData.Year}] on file [{metaData.FileInfo.Name}");
metaData.Year = year; 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"; data = $"Found [{found}] files, Modified [{modified}] files";

View file

@ -5,8 +5,11 @@ namespace Roadie.Library.Inspect.Plugins.Directory
public interface IInspectorDirectoryPlugin public interface IInspectorDirectoryPlugin
{ {
string Description { get; } string Description { get; }
bool IsEnabled { get; } bool IsEnabled { get; }
bool IsPostProcessingPlugin { get; } bool IsPostProcessingPlugin { get; }
int Order { get; } int Order { get; }
OperationResult<string> Process(DirectoryInfo directory); OperationResult<string> Process(DirectoryInfo directory);

View file

@ -1,4 +1,5 @@
using Microsoft.Extensions.Logging; using MetadataExtractor;
using Microsoft.Extensions.Logging;
using Roadie.Library.Caching; using Roadie.Library.Caching;
using Roadie.Library.Configuration; using Roadie.Library.Configuration;
using Roadie.Library.Extensions; using Roadie.Library.Extensions;
@ -14,16 +15,15 @@ namespace Roadie.Library.Inspect.Plugins.File
public override int Order => 5; public override int Order => 5;
public CleanUpArtists(IRoadieSettings configuration, ICacheManager cacheManager, ILogger logger, public CleanUpArtists(IRoadieSettings configuration, ICacheManager cacheManager, ILogger logger, IID3TagsHelper tagsHelper)
IID3TagsHelper tagsHelper)
: base(configuration, cacheManager, logger, tagsHelper) : base(configuration, cacheManager, logger, tagsHelper)
{ {
} }
public string CleanArtist(string artist, string trackArtist = null) public string CleanArtist(string artist, string trackArtist = null)
{ {
artist = artist ?? string.Empty; artist ??= string.Empty;
trackArtist = trackArtist ?? string.Empty; trackArtist ??= string.Empty;
var splitCharacter = AudioMetaData.ArtistSplitCharacter.ToString(); var splitCharacter = AudioMetaData.ArtistSplitCharacter.ToString();
// Replace seperators with proper split character // Replace seperators with proper split character
@ -39,12 +39,21 @@ namespace Roadie.Library.Inspect.Plugins.File
} }
if (!string.IsNullOrEmpty(result) && !string.IsNullOrEmpty(trackArtist)) if (!string.IsNullOrEmpty(result) && !string.IsNullOrEmpty(trackArtist))
{ {
result = result.Replace(splitCharacter + trackArtist + splitCharacter, "", StringComparison.OrdinalIgnoreCase); result = result.Replace(splitCharacter + trackArtist + splitCharacter, string.Empty, StringComparison.OrdinalIgnoreCase);
result = result.Replace(trackArtist + splitCharacter, "", StringComparison.OrdinalIgnoreCase); result = result.Replace(trackArtist + splitCharacter, string.Empty, StringComparison.OrdinalIgnoreCase);
result = result.Replace(splitCharacter + trackArtist, "", StringComparison.OrdinalIgnoreCase); result = result.Replace(splitCharacter + trackArtist, string.Empty, StringComparison.OrdinalIgnoreCase);
result = result.Replace(trackArtist, "", 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; return string.IsNullOrEmpty(result) ? null : result;
} }

View file

@ -22,8 +22,13 @@ namespace Roadie.Library.Inspect.Plugins.File
{ {
var result = new OperationResult<AudioMetaData>(); var result = new OperationResult<AudioMetaData>();
if (Configuration.Processing.DoAudioCleanup) if (Configuration.Processing.DoAudioCleanup)
{
if (Configuration.Processing.DoClearComments) if (Configuration.Processing.DoClearComments)
{
metaData.Comments = null; metaData.Comments = null;
}
}
result.Data = metaData; result.Data = metaData;
result.IsSuccess = true; result.IsSuccess = true;
return result; return result;

View file

@ -27,9 +27,21 @@ namespace Roadie.Library.Inspect.Plugins.File
var originalRelease = metaData.Release; var originalRelease = metaData.Release;
metaData.Release = metaData.Release metaData.Release = metaData.Release
?.CleanString(Configuration, Configuration.Processing.ReleaseRemoveStringsRegex).ToTitleCase(false); ?.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.Data = metaData;
result.IsSuccess = true; result.IsSuccess = true;
return result; return result;

View file

@ -27,9 +27,21 @@ namespace Roadie.Library.Inspect.Plugins.File
var originalTitle = metaData.Title; var originalTitle = metaData.Title;
metaData.Title = metaData.Title metaData.Title = metaData.Title
?.CleanString(Configuration, Configuration.Processing.TrackRemoveStringsRegex).ToTitleCase(false); ?.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.Data = metaData;
result.IsSuccess = true; result.IsSuccess = true;
return result; return result;

View file

@ -20,25 +20,27 @@ namespace Roadie.Library.Inspect.Plugins.File
{ {
} }
private static FileAttributes RemoveAttribute(FileAttributes attributes, FileAttributes attributesToRemove)
{
return attributes & ~attributesToRemove;
}
public override OperationResult<AudioMetaData> Process(AudioMetaData metaData) public override OperationResult<AudioMetaData> Process(AudioMetaData metaData)
{ {
var result = new OperationResult<AudioMetaData>(); var result = new OperationResult<AudioMetaData>();
if (Configuration.Processing.DoAudioCleanup) if (Configuration.Processing.DoAudioCleanup)
{
if (metaData.FileInfo.IsReadOnly) if (metaData.FileInfo.IsReadOnly)
{ {
metaData.FileInfo.Attributes = metaData.FileInfo.Attributes =
RemoveAttribute(metaData.FileInfo.Attributes, FileAttributes.ReadOnly); RemoveAttribute(metaData.FileInfo.Attributes, FileAttributes.ReadOnly);
Console.WriteLine($"╟ Removed read only attribute on file file [{metaData.FileInfo.Name}"); Console.WriteLine($"╟ Removed read only attribute on file file [{metaData.FileInfo.Name}");
} }
}
result.Data = metaData; result.Data = metaData;
result.IsSuccess = true; result.IsSuccess = true;
return result; return result;
} }
private static FileAttributes RemoveAttribute(FileAttributes attributes, FileAttributes attributesToRemove)
{
return attributes & ~attributesToRemove;
}
} }
} }

View file

@ -5,7 +5,9 @@ namespace Roadie.Library.Inspect.Plugins.File
public interface IInspectorFilePlugin public interface IInspectorFilePlugin
{ {
string Description { get; } string Description { get; }
bool IsEnabled { get; } bool IsEnabled { get; }
int Order { get; } int Order { get; }
OperationResult<AudioMetaData> Process(AudioMetaData metaData); OperationResult<AudioMetaData> Process(AudioMetaData metaData);

View file

@ -12,22 +12,21 @@ namespace Roadie.Library.Inspect.Plugins
{ {
public abstract class PluginBase public abstract class PluginBase
{ {
public abstract string Description { get; } private Dictionary<string, IEnumerable<AudioMetaData>> CachedAudioDatas { get; }
public abstract int Order { get; }
protected ICacheManager CacheManager { get; } protected ICacheManager CacheManager { get; }
protected IRoadieSettings Configuration { get; } protected IRoadieSettings Configuration { get; }
protected IEnumerable<string> ListReplacements { get; } = new List<string> protected IEnumerable<string> ListReplacements { get; } = new List<string> { " ; ", " ;", "; ", ";", ";", "\\" };
{" ; ", " ;", "; ", ";", ";", "\\"};
protected ILogger Logger { get; } protected ILogger Logger { get; }
protected IID3TagsHelper TagsHelper { get; } protected IID3TagsHelper TagsHelper { get; }
private Dictionary<string, IEnumerable<AudioMetaData>> CachedAudioDatas { get; } public abstract string Description { get; }
public abstract int Order { get; }
public PluginBase(IRoadieSettings configuration, ICacheManager cacheManager, ILogger logger, public PluginBase(IRoadieSettings configuration, ICacheManager cacheManager, ILogger logger,
IID3TagsHelper tagsHelper) IID3TagsHelper tagsHelper)

View file

@ -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.Playlists;
using Roadie.Library.Models.Releases; using Roadie.Library.Models.Releases;
using Roadie.Library.Models.Statistics; using Roadie.Library.Models.Statistics;
@ -8,6 +7,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Text.Json.Serialization;
namespace Roadie.Library.Models namespace Roadie.Library.Models
{ {
@ -48,7 +48,7 @@ namespace Roadie.Library.Models
[IgnoreDataMember] [IgnoreDataMember]
public string ISNI { get; set; } public string ISNI { get; set; }
[JsonProperty("isniList")] [JsonPropertyName("isniList")]
public IEnumerable<string> ISNIList public IEnumerable<string> ISNIList
{ {
get get

View file

@ -1,9 +1,9 @@
using Newtonsoft.Json; using Roadie.Library.Enums;
using Roadie.Library.Enums;
using Roadie.Library.Models.Collections; using Roadie.Library.Models.Collections;
using Roadie.Library.Models.Playlists; using Roadie.Library.Models.Playlists;
using Roadie.Library.Models.Releases; using Roadie.Library.Models.Releases;
using System; using System;
using System.Text.Json.Serialization;
namespace Roadie.Library.Models namespace Roadie.Library.Models
{ {

View file

@ -1,5 +1,5 @@
using Newtonsoft.Json; using System;
using System; using System.Text.Json.Serialization;
namespace Roadie.Library.Models.Collections namespace Roadie.Library.Models.Collections
{ {
@ -7,7 +7,9 @@ namespace Roadie.Library.Models.Collections
internal class CollectionReleaseList : EntityInfoModelBase internal class CollectionReleaseList : EntityInfoModelBase
{ {
private string _listNumber; private string _listNumber;
public DataToken Artist { get; set; } public DataToken Artist { get; set; }
public string ArtistThumbnailUrl { get; set; } public string ArtistThumbnailUrl { get; set; }
public string ListNumber public string ListNumber
@ -16,10 +18,16 @@ namespace Roadie.Library.Models.Collections
set => _listNumber = value; set => _listNumber = value;
} }
[JsonIgnore] public int ListNumberValue { get; set; } [JsonIgnore]
public int ListNumberValue { get; set; }
public DataToken Release { 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 short? ReleaseRating { get; set; }
public string ReleaseThumbnailUrl { get; set; } public string ReleaseThumbnailUrl { get; set; }
public string ReleaseYear => ReleaseDateDateTime.HasValue public string ReleaseYear => ReleaseDateDateTime.HasValue

View file

@ -1,7 +1,7 @@
using Mapster; using Mapster;
using Newtonsoft.Json;
using Roadie.Library.Utility; using Roadie.Library.Utility;
using System; using System;
using System.Text.Json.Serialization;
namespace Roadie.Library.Models namespace Roadie.Library.Models
{ {

View file

@ -1,8 +1,8 @@
using Mapster; using Mapster;
using Newtonsoft.Json;
using Roadie.Library.Utility; using Roadie.Library.Utility;
using System; using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace Roadie.Library.Models namespace Roadie.Library.Models
{ {
@ -26,9 +26,6 @@ namespace Roadie.Library.Models
public DateTime? LastUpdated { get; set; } public DateTime? LastUpdated { get; set; }
[MaxLength(250)]
public virtual string SortName { get; set; }
/// <summary> /// <summary>
/// Random int to sort when Random Request /// Random int to sort when Random Request
/// </summary> /// </summary>
@ -36,6 +33,9 @@ namespace Roadie.Library.Models
[JsonIgnore] [JsonIgnore]
public int RandomSortId { get; set; } public int RandomSortId { get; set; }
[MaxLength(250)]
public virtual string SortName { get; set; }
public EntityInfoModelBase() public EntityInfoModelBase()
{ {
RandomSortId = StaticRandom.Instance.Next(); RandomSortId = StaticRandom.Instance.Next();

View file

@ -1,5 +1,4 @@
using Mapster; using Mapster;
using Newtonsoft.Json;
using Roadie.Library.Enums; using Roadie.Library.Enums;
using Roadie.Library.Utility; using Roadie.Library.Utility;
using System; using System;
@ -7,6 +6,7 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Text.Json.Serialization;
namespace Roadie.Library.Models namespace Roadie.Library.Models
{ {

View file

@ -1,5 +1,4 @@
using Newtonsoft.Json; using System;
using System;
namespace Roadie.Library.Models namespace Roadie.Library.Models
{ {
@ -7,9 +6,13 @@ namespace Roadie.Library.Models
public sealed class LabelList : EntityInfoModelBase public sealed class LabelList : EntityInfoModelBase
{ {
public int? ArtistCount { get; set; } public int? ArtistCount { get; set; }
public DataToken Label { get; set; } public DataToken Label { get; set; }
public int? ReleaseCount { get; set; } public int? ReleaseCount { get; set; }
public Image Thumbnail { get; set; } public Image Thumbnail { get; set; }
public int? TrackCount { get; set; } public int? TrackCount { get; set; }
public static LabelList FromDataLabel(Data.Label label, Image labelThumbnail) public static LabelList FromDataLabel(Data.Label label, Image labelThumbnail)

View file

@ -1,35 +1,52 @@
using Newtonsoft.Json; using Roadie.Library.Models.Users;
using Roadie.Library.Models.Users;
using System; using System;
using System.Text.Json.Serialization;
namespace Roadie.Library.Models namespace Roadie.Library.Models
{ {
[Serializable] [Serializable]
public class PlayActivityList public class PlayActivityList
{ {
public DataToken Artist { get; set; } public DataToken Artist { get; set; }
public Image ArtistThumbnail { get; set; } public Image ArtistThumbnail { get; set; }
public bool IsNowPlaying { get; set; } public bool IsNowPlaying { get; set; }
public string PlayedDate => PlayedDateDateTime.HasValue ? PlayedDateDateTime.Value.ToString("s") : null; 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 string PlayedDay => PlayedDateDateTime.HasValue ? PlayedDateDateTime.Value.ToString("MM/dd/yyyy") : null;
public int? Rating { get; set; } public int? Rating { get; set; }
public DataToken Release { get; set; } public DataToken Release { get; set; }
public string ReleasePlayUrl { get; set; } public string ReleasePlayUrl { get; set; }
public Image ReleaseThumbnail { get; set; } public Image ReleaseThumbnail { get; set; }
public TrackList Track { get; set; } public TrackList Track { get; set; }
public DataToken TrackArtist { get; set; } public DataToken TrackArtist { get; set; }
public string TrackPlayUrl { get; set; } public string TrackPlayUrl { get; set; }
public DataToken User { get; set; } public DataToken User { get; set; }
public int? UserRating { get; set; } public int? UserRating { get; set; }
public Image UserThumbnail { get; set; } public Image UserThumbnail { get; set; }
public UserTrack UserTrack { get; set; } public UserTrack UserTrack { get; set; }
public override string ToString() public override string ToString()
{ {
return $"User [{User}], Artist [{Artist}], Release [{Release}], Track [{Track}]"; return $"User [{User}], Artist [{Artist}], Release [{Release}], Track [{Track}]";
} }
} }
} }

View file

@ -1,8 +1,8 @@
using Newtonsoft.Json; using Roadie.Library.Configuration;
using Roadie.Library.Configuration;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.Json;
namespace Roadie.Library.Models.Player namespace Roadie.Library.Models.Player
{ {
@ -11,14 +11,11 @@ namespace Roadie.Library.Models.Player
{ {
private string _rawTracks; 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 SiteName { get; set; }
public string TotalTrackTime public string TotalTrackTime => TimeSpan.FromMilliseconds((double)Tracks.Sum(x => x.Duration)).ToString(@"hh\:mm\:ss");
{
get { return TimeSpan.FromMilliseconds((double)Tracks.Sum(x => x.Duration)).ToString(@"hh\:mm\:ss"); }
}
public string TrackCount => Tracks.Count().ToString("D3"); public string TrackCount => Tracks.Count().ToString("D3");

View file

@ -1,5 +1,5 @@
using Newtonsoft.Json; using System;
using System; using System.Text.Json.Serialization;
namespace Roadie.Library.Models.Releases namespace Roadie.Library.Models.Releases
{ {
@ -7,10 +7,17 @@ namespace Roadie.Library.Models.Releases
public sealed class ReleaseLabelList : EntityInfoModelBase public sealed class ReleaseLabelList : EntityInfoModelBase
{ {
public string BeginDate => BeginDatedDateTime.HasValue ? BeginDatedDateTime.Value.ToString("s") : null; 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 CatalogNumber { get; set; }
public string EndDate => EndDatedDateTime.HasValue ? EndDatedDateTime.Value.ToString("s") : null; 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; } public DataToken Label { get; set; }
} }
} }

View file

@ -1,11 +1,11 @@
using Mapster; using Mapster;
using Newtonsoft.Json;
using Roadie.Library.Enums; using Roadie.Library.Enums;
using Roadie.Library.Models.Users; using Roadie.Library.Models.Users;
using Roadie.Library.Utility; using Roadie.Library.Utility;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Text.Json.Serialization;
namespace Roadie.Library.Models.Releases namespace Roadie.Library.Models.Releases
{ {
@ -14,14 +14,20 @@ namespace Roadie.Library.Models.Releases
public sealed class ReleaseList : EntityInfoModelBase public sealed class ReleaseList : EntityInfoModelBase
{ {
public DataToken Artist { get; set; } public DataToken Artist { get; set; }
public Image ArtistThumbnail { get; set; } public Image ArtistThumbnail { get; set; }
public decimal? Duration { get; set; } public decimal? Duration { get; set; }
public string DurationTime public string DurationTime
{ {
get get
{ {
if (!Duration.HasValue) return "--:--"; if (!Duration.HasValue)
{
return "--:--";
}
return new TimeInfo(Duration.Value).ToFullFormattedString(); return new TimeInfo(Duration.Value).ToFullFormattedString();
} }
} }
@ -41,6 +47,7 @@ namespace Roadie.Library.Models.Releases
} }
public DateTime? LastPlayed { get; set; } public DateTime? LastPlayed { get; set; }
public LibraryStatus? LibraryStatus { get; set; } public LibraryStatus? LibraryStatus { get; set; }
/// <summary> /// <summary>
@ -49,17 +56,26 @@ namespace Roadie.Library.Models.Releases
public int? ListNumber { get; set; } public int? ListNumber { get; set; }
public IEnumerable<ReleaseMediaList> Media { get; set; } public IEnumerable<ReleaseMediaList> Media { get; set; }
public int? MediaCount { get; set; } public int? MediaCount { get; set; }
public double? Rank { get; set; } public double? Rank { get; set; }
public short? Rating { get; set; } public short? Rating { get; set; }
public DataToken Release { get; set; } public DataToken Release { get; set; }
public string ReleaseDate => ReleaseDateDateTime.HasValue public string ReleaseDate => ReleaseDateDateTime.HasValue
? ReleaseDateDateTime.Value.ToUniversalTime().ToString("yyyy-MM-dd") ? ReleaseDateDateTime.Value.ToUniversalTime().ToString("yyyy-MM-dd")
: null; : null;
[JsonIgnore] public DateTime? ReleaseDateDateTime { get; set; } [JsonIgnore]
[JsonIgnore] [AdaptIgnore] public string ReleaseName => Release?.Text; public DateTime? ReleaseDateDateTime { get; set; }
[JsonIgnore]
[AdaptIgnore]
public string ReleaseName => Release?.Text;
public string ReleasePlayUrl { get; set; } public string ReleasePlayUrl { get; set; }
public string ReleaseYear => ReleaseDateDateTime.HasValue public string ReleaseYear => ReleaseDateDateTime.HasValue
@ -67,10 +83,15 @@ namespace Roadie.Library.Models.Releases
: null; : null;
public Statuses? Status { get; set; } public Statuses? Status { get; set; }
public string StatusVerbose => (Status ?? Statuses.Missing).ToString(); public string StatusVerbose => (Status ?? Statuses.Missing).ToString();
public Image Thumbnail { get; set; } public Image Thumbnail { get; set; }
public int? TrackCount { get; set; } public int? TrackCount { get; set; }
public int? TrackPlayedCount { get; set; } public int? TrackPlayedCount { get; set; }
public UserRelease UserRating { get; set; } public UserRelease UserRating { get; set; }
public static ReleaseList FromDataRelease(Data.Release release, Data.Artist artist, string baseUrl, public static ReleaseList FromDataRelease(Data.Release release, Data.Artist artist, string baseUrl,

View file

@ -1,8 +1,8 @@
using Newtonsoft.Json; using Roadie.Library.Extensions;
using Roadie.Library.Extensions;
using Roadie.Library.Models.Pagination; using Roadie.Library.Models.Pagination;
using Roadie.Library.Utility; using Roadie.Library.Utility;
using System; using System;
using System.Text.Json.Serialization;
namespace Roadie.Library.Models.ThirdPartyApi.Subsonic namespace Roadie.Library.Models.ThirdPartyApi.Subsonic
{ {
@ -20,8 +20,16 @@ namespace Roadie.Library.Models.ThirdPartyApi.Subsonic
{ {
get get
{ {
if (string.IsNullOrEmpty(id)) return null; if (string.IsNullOrEmpty(id))
if (id.StartsWith(ArtistIdIdentifier)) return SafeParser.ToGuid(id); {
return null;
}
if (id.StartsWith(ArtistIdIdentifier))
{
return SafeParser.ToGuid(id);
}
return null; return null;
} }
} }
@ -40,8 +48,16 @@ namespace Roadie.Library.Models.ThirdPartyApi.Subsonic
{ {
get get
{ {
if (string.IsNullOrEmpty(id)) return null; if (string.IsNullOrEmpty(id))
if (id.StartsWith(CollectionIdentifier)) return SafeParser.ToGuid(id); {
return null;
}
if (id.StartsWith(CollectionIdentifier))
{
return SafeParser.ToGuid(id);
}
return null; return null;
} }
} }
@ -73,8 +89,12 @@ namespace Roadie.Library.Models.ThirdPartyApi.Subsonic
// Default should be false (XML) // Default should be false (XML)
get get
{ {
if (string.IsNullOrEmpty(f)) return false; if (string.IsNullOrEmpty(f))
return f.ToLower().StartsWith("j"); {
return false;
}
return f.StartsWith("j", StringComparison.OrdinalIgnoreCase);
} }
} }
@ -90,8 +110,16 @@ namespace Roadie.Library.Models.ThirdPartyApi.Subsonic
{ {
get get
{ {
if (string.IsNullOrEmpty(p)) return null; if (string.IsNullOrEmpty(p))
if (p.StartsWith("enc:")) return p.ToLower().Replace("enc:", "").FromHexString(); {
return null;
}
if (p.StartsWith("enc:"))
{
return p.ToLower().Replace("enc:", string.Empty).FromHexString();
}
return p; return p;
} }
} }
@ -100,8 +128,16 @@ namespace Roadie.Library.Models.ThirdPartyApi.Subsonic
{ {
get get
{ {
if (string.IsNullOrEmpty(id)) return null; if (string.IsNullOrEmpty(id))
if (id.StartsWith(PlaylistdIdentifier)) return SafeParser.ToGuid(id); {
return null;
}
if (id.StartsWith(PlaylistdIdentifier))
{
return SafeParser.ToGuid(id);
}
return null; return null;
} }
} }
@ -115,8 +151,16 @@ namespace Roadie.Library.Models.ThirdPartyApi.Subsonic
{ {
get get
{ {
if (string.IsNullOrEmpty(id)) return null; if (string.IsNullOrEmpty(id))
if (id.StartsWith(ReleaseIdIdentifier)) return SafeParser.ToGuid(id); {
return null;
}
if (id.StartsWith(ReleaseIdIdentifier))
{
return SafeParser.ToGuid(id);
}
return null; return null;
} }
} }
@ -145,8 +189,16 @@ namespace Roadie.Library.Models.ThirdPartyApi.Subsonic
{ {
get get
{ {
if (string.IsNullOrEmpty(id)) return null; if (string.IsNullOrEmpty(id))
if (id.StartsWith(TrackIdIdentifier)) return SafeParser.ToGuid(id); {
return null;
}
if (id.StartsWith(TrackIdIdentifier))
{
return SafeParser.ToGuid(id);
}
return null; return null;
} }
} }

View file

@ -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.Statistics;
using Roadie.Library.Models.Users; using Roadie.Library.Models.Users;
using Roadie.Library.Utility; using Roadie.Library.Utility;
@ -7,6 +6,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Text.Json.Serialization;
namespace Roadie.Library.Models namespace Roadie.Library.Models
{ {
@ -17,33 +17,47 @@ namespace Roadie.Library.Models
private IEnumerable<string> _partTitles; private IEnumerable<string> _partTitles;
[MaxLength(50)] public string AmgId { get; set; } [MaxLength(50)]
public string AmgId { get; set; }
public ArtistList Artist { get; set; } public ArtistList Artist { get; set; }
public IEnumerable<CreditList> Credits { get; set; }
public Image ArtistThumbnail { get; set; } public Image ArtistThumbnail { get; set; }
public IEnumerable<Comment> Comments { get; set; } public IEnumerable<Comment> Comments { get; set; }
public IEnumerable<CreditList> Credits { get; set; }
public int Duration { get; set; } public int Duration { get; set; }
public string DurationTime public string DurationTime
{ {
get get
{ {
if (Duration < 1) return "--:--"; if (Duration < 1)
{
return "--:--";
}
return new TimeInfo(Duration).ToFullFormattedString(); return new TimeInfo(Duration).ToFullFormattedString();
} }
} }
public long FileSize { get; set; } 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 DateTime? LastPlayed { get; set; }
public Image MediumThumbnail { get; set; } public Image MediumThumbnail { get; set; }
[MaxLength(100)] public string MusicBrainzId { 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 // 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 (_partTitles == null)
{ {
if (string.IsNullOrEmpty(PartTitles)) return null; if (string.IsNullOrEmpty(PartTitles))
{
return null;
}
return PartTitles.Replace("|", "\n").Split("\n"); return PartTitles.Replace("|", "\n").Split("\n");
} }
@ -70,7 +88,9 @@ namespace Roadie.Library.Models
} }
public int PlayedCount { get; set; } public int PlayedCount { get; set; }
public short Rating { get; set; } public short Rating { get; set; }
public ReleaseList Release { get; set; } public ReleaseList Release { get; set; }
public string ReleaseMediaId { get; set; } public string ReleaseMediaId { get; set; }
@ -82,7 +102,10 @@ namespace Roadie.Library.Models
public TrackStatistics Statistics { get; set; } public TrackStatistics Statistics { get; set; }
public Image Thumbnail { get; set; } public Image Thumbnail { get; set; }
[MaxLength(250)] [Required] public string Title { get; set; }
[MaxLength(250)]
[Required]
public string Title { get; set; }
/// <summary> /// <summary>
/// Track Artist, not release artist. If this is present then the track has an artist different than the release. /// 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 ArtistList TrackArtist { get; set; }
public Image TrackArtistThumbnail { get; set; } public Image TrackArtistThumbnail { get; set; }
public DataToken TrackArtistToken { get; set; } public DataToken TrackArtistToken { get; set; }
[Required] public short TrackNumber { get; set; } [Required] public short TrackNumber { get; set; }
public string TrackPlayUrl { get; set; } public string TrackPlayUrl { get; set; }
public UserTrack UserRating { get; set; } public UserTrack UserRating { get; set; }
public override string ToString() public override string ToString() => $"Id [{ Id }], Title [{ Title }]";
{
return $"Id [{ Id }], Title [{ Title }]";
}
} }
} }

View file

@ -1,15 +1,15 @@
using Newtonsoft.Json; using Roadie.Library.Enums;
using Roadie.Library.Models.Releases; using Roadie.Library.Models.Releases;
using Roadie.Library.Models.Users; using Roadie.Library.Models.Users;
using Roadie.Library.Utility; using Roadie.Library.Utility;
using System; using System;
using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using Release = Roadie.Library.Data.Release; using Release = Roadie.Library.Data.Release;
using Roadie.Library.Enums;
namespace Roadie.Library.Models namespace Roadie.Library.Models
{ {
@ -18,6 +18,7 @@ namespace Roadie.Library.Models
public sealed class TrackList : EntityInfoModelBase public sealed class TrackList : EntityInfoModelBase
{ {
public ArtistList Artist { get; set; } public ArtistList Artist { get; set; }
public int? Duration { get; set; } public int? Duration { get; set; }
public string DurationTime => Duration.HasValue ? new TimeInfo(Duration.Value).ToFullFormattedString() : "--:--"; 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 string DurationTimeShort => Duration.HasValue ? new TimeInfo(Duration.Value).ToShortFormattedString() : "--:--";
public int? FavoriteCount { get; set; } public int? FavoriteCount { get; set; }
public int? FileSize { get; set; } public int? FileSize { get; set; }
public DateTime? LastPlayed { get; set; } public DateTime? LastPlayed { get; set; }
public int? MediaNumber { get; set; } public int? MediaNumber { get; set; }
[MaxLength(65535)] [MaxLength(65535)]
@ -38,100 +42,84 @@ namespace Roadie.Library.Models
{ {
get get
{ {
if (string.IsNullOrEmpty(PartTitles)) return null; if (string.IsNullOrEmpty(PartTitles))
{
return null;
}
return PartTitles.Split('\n'); return PartTitles.Split('\n');
} }
} }
public int? PlayedCount { get; set; } 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; public short? Rating { get; set; }
[JsonIgnore] public string ReleaseName => Release?.Release?.Text;
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 Statuses? Status { get; set; }
public string StatusVerbose => (Status ?? Statuses.Missing).ToString(); 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 int? TrackNumber { get; set; }
public string TrackPlayUrl { get; set; } public string TrackPlayUrl { get; set; }
public UserTrack UserRating { get; set; } public UserTrack UserRating { get; set; }
public int? Year public int? Year
{ {
get get
{ {
if (ReleaseDate.HasValue) return ReleaseDate.Value.Year; if (ReleaseDate.HasValue)
{
return ReleaseDate.Value.Year;
}
return null; 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<int> AllIndexesOfArtist(IEnumerable<TrackList> tracks, string artistId) public static IEnumerable<int> AllIndexesOfArtist(IEnumerable<TrackList> tracks, string artistId)
{ {
if(tracks == null || !tracks.Any()) if (tracks?.Any() != true)
{ {
return new int[0]; 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<int> AllIndexesOfRelease(IEnumerable<TrackList> tracks, string releaseId) public static IEnumerable<int> AllIndexesOfRelease(IEnumerable<TrackList> tracks, string releaseId)
{ {
if (tracks == null || !tracks.Any()) if (tracks?.Any() != true)
{ {
return new int[0]; return new int[0];
} }
return tracks.Select((b, i) => b.Release?.Release?.Value == releaseId ? i : -1).Where(i => i != -1).ToArray(); return tracks.Select((b, i) => b.Release?.Release?.Value == releaseId ? i : -1).Where(i => i != -1).ToArray();
} }
/// <summary>
/// Ensure that the given list is sorted so that Artist and Release don't repeat in sequence.
/// </summary>
public static IEnumerable<TrackList> Shuffle(IEnumerable<TrackList> tracks)
{
var shuffledTracks = new List<TrackList>();
var skippedTracks = new List<TrackList>();
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<TrackList>(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, public static TrackList FromDataTrack(string trackPlayUrl,
Data.Track track, Data.Track track,
int releaseMediaNumber, 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;
/// <summary>
/// Ensure that the given list is sorted so that Artist and Release don't repeat in sequence.
/// </summary>
public static IEnumerable<TrackList> Shuffle(IEnumerable<TrackList> tracks)
{
var shuffledTracks = new List<TrackList>();
var skippedTracks = new List<TrackList>();
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<TrackList>(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;
}
} }
} }

View file

@ -3,6 +3,7 @@ using Roadie.Library.Models.Statistics;
using System; using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
namespace Roadie.Library.Models.Users namespace Roadie.Library.Models.Users
{ {
@ -11,7 +12,9 @@ namespace Roadie.Library.Models.Users
{ {
public const string ActionKeyUserRated = "__userrated__"; public const string ActionKeyUserRated = "__userrated__";
public const string DefaultIncludes = "stats"; public const string DefaultIncludes = "stats";
[MaxLength(100)] public string ApiToken { get; set; }
[MaxLength(100)]
public string ApiToken { get; set; }
public Image Avatar { get; set; } public Image Avatar { get; set; }
@ -20,7 +23,12 @@ namespace Roadie.Library.Models.Users
/// </summary> /// </summary>
public string AvatarData { get; set; } 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; } public bool DoUseHtmlPlayer { get; set; }
[Required] [Required]
@ -28,18 +36,34 @@ namespace Roadie.Library.Models.Users
[DataType(DataType.EmailAddress)] [DataType(DataType.EmailAddress)]
public string Email { get; set; } 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 new int? Id { get; set; }
public bool IsAdmin { get; set; } public bool IsAdmin { get; set; }
public bool IsEditor { get; set; } public bool IsEditor { get; set; }
public bool IsPrivate { get; set; } public bool IsPrivate { get; set; }
public DateTime LastApiAccess { get; set; }
public DateTime LastLogin { get; set; }
public Image MediumThumbnail { get; set; }
/// <summary> /// <summary>
/// Posted password only used when changing password from profile edits /// Posted password only used when changing password from profile edits
/// </summary> /// </summary>
@ -55,23 +79,34 @@ namespace Roadie.Library.Models.Users
public string PasswordConfirmation { get; set; } public string PasswordConfirmation { get; set; }
public short? PlayerTrackLimit { 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? RandomReleaseLimit { get; set; }
public short? RecentlyPlayedLimit { get; set; } public short? RecentlyPlayedLimit { get; set; }
public bool RemoveTrackFromQueAfterPlayed { 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; } [MaxLength(50)]
public Image MediumThumbnail { get; set; } [Required]
public string Timezone { get; set; }
public DateTime LastLogin { get; set; } [AdaptMember("RoadieId")]
public DateTime LastApiAccess { get; set; } public Guid UserId { get; set; }
public short? DefaultRowsPerPage { get; set; }
[Required]
[MaxLength(20)]
public string UserName { get; set; }
public override string ToString() => $"Id [{Id}], RoadieId [{UserId}], UserName [{UserName}]"; public override string ToString() => $"Id [{Id}], RoadieId [{UserId}], UserName [{UserName}]";
} }

View file

@ -1,7 +1,7 @@
using Newtonsoft.Json; using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.Json.Serialization;
using System.Xml.Serialization; using System.Xml.Serialization;
namespace Roadie.Library namespace Roadie.Library
@ -10,6 +10,7 @@ namespace Roadie.Library
public class OperationResult<T> public class OperationResult<T>
{ {
private List<Exception> _errors; private List<Exception> _errors;
private List<string> _messages; private List<string> _messages;
[XmlIgnore] [XmlIgnore]
@ -22,12 +23,15 @@ namespace Roadie.Library
/// <summary> /// <summary>
/// Client friendly exceptions /// Client friendly exceptions
/// </summary> /// </summary>
[JsonProperty("errors")] [JsonPropertyName("errors")]
public IEnumerable<AppException> AppExceptions public IEnumerable<AppException> AppExceptions
{ {
get get
{ {
if (Errors == null || !Errors.Any()) return null; if (Errors?.Any() != true)
{
return null;
}
return Errors.Select(x => new AppException(x.Message)); return Errors.Select(x => new AppException(x.Message));
} }
@ -41,7 +45,8 @@ namespace Roadie.Library
[JsonIgnore] [JsonIgnore]
public IEnumerable<Exception> Errors { get; set; } public IEnumerable<Exception> Errors { get; set; }
[JsonIgnore] public bool IsAccessDeniedResult { get; set; } [JsonIgnore]
public bool IsAccessDeniedResult { get; set; }
[JsonIgnore] public bool IsNotFoundResult { get; set; } [JsonIgnore] public bool IsNotFoundResult { get; set; }
@ -57,29 +62,13 @@ namespace Roadie.Library
public OperationResult(IEnumerable<string> messages = null) public OperationResult(IEnumerable<string> messages = null)
{ {
if (messages != null && messages.Any()) if (messages?.Any() == true)
{ {
AdditionalData = new Dictionary<string, object>(); AdditionalData = new Dictionary<string, object>();
messages.ToList().ForEach(AddMessage); messages.ToList().ForEach(AddMessage);
} }
} }
public OperationResult(bool isNotFoundResult, IEnumerable<string> messages = null)
{
IsNotFoundResult = isNotFoundResult;
if (messages != null && messages.Any())
{
AdditionalData = new Dictionary<string, object>();
messages.ToList().ForEach(AddMessage);
}
}
public OperationResult(bool isNotFoundResult, string message)
{
IsNotFoundResult = isNotFoundResult;
AddMessage(message);
}
public OperationResult(string message = null) public OperationResult(string message = null)
{ {
AdditionalData = new Dictionary<string, object>(); AdditionalData = new Dictionary<string, object>();
@ -91,6 +80,22 @@ namespace Roadie.Library
AddError(error); AddError(error);
} }
public OperationResult(bool isNotFoundResult, IEnumerable<string> messages = null)
{
IsNotFoundResult = isNotFoundResult;
if (messages?.Any() == true)
{
AdditionalData = new Dictionary<string, object>();
messages.ToList().ForEach(AddMessage);
}
}
public OperationResult(bool isNotFoundResult, string message)
{
IsNotFoundResult = isNotFoundResult;
AddMessage(message);
}
public OperationResult(string message = null, Exception error = null) public OperationResult(string message = null, Exception error = null)
{ {
AddMessage(message); AddMessage(message);
@ -101,9 +106,7 @@ namespace Roadie.Library
{ {
if (exception != null) if (exception != null)
{ {
if (_errors == null) _errors = new List<Exception>(); (_errors ?? (_errors = new List<Exception>())).Add(exception);
_errors.Add(exception);
} }
} }
@ -111,9 +114,7 @@ namespace Roadie.Library
{ {
if (!string.IsNullOrEmpty(message)) if (!string.IsNullOrEmpty(message))
{ {
if (_messages == null) _messages = new List<string>(); (_messages ?? (_messages = new List<string>())).Add(message);
_messages.Add(message);
} }
} }
} }

View file

@ -18,17 +18,19 @@
<PackageReference Include="IdSharp.Tagging" Version="1.0.0-rc3" /> <PackageReference Include="IdSharp.Tagging" Version="1.0.0-rc3" />
<PackageReference Include="Inflatable.Lastfm" Version="1.1.0.339" /> <PackageReference Include="Inflatable.Lastfm" Version="1.1.0.339" />
<PackageReference Include="LiteDB" Version="5.0.8" /> <PackageReference Include="LiteDB" Version="5.0.8" />
<PackageReference Include="Magick.NET-Q16-x64" Version="7.20.0" />
<PackageReference Include="Mapster" Version="5.3.2" /> <PackageReference Include="Mapster" Version="5.3.2" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.4" /> <PackageReference Include="MetadataExtractor" Version="2.4.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.4" /> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Caching.Redis" Version="2.2.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Redis" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.4" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.5" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="1.3.4" /> <PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="1.3.4" />
<PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.8" /> <PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.8" />
<PackageReference Include="Microsoft.PowerShell.SDK" Version="7.0.1" /> <PackageReference Include="Microsoft.PowerShell.SDK" Version="7.0.2" />
<PackageReference Include="MimeMapping" Version="1.0.1.26" /> <PackageReference Include="MimeMapping" Version="1.0.1.30" />
<PackageReference Include="NodaTime" Version="3.0.0" /> <PackageReference Include="NodaTime" Version="3.0.0" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.1" /> <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.1" />
<PackageReference Include="RestSharp" Version="106.11.4" /> <PackageReference Include="RestSharp" Version="106.11.4" />
@ -39,6 +41,7 @@
<PackageReference Include="System.Drawing.Common" Version="4.7.0" /> <PackageReference Include="System.Drawing.Common" Version="4.7.0" />
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="4.7.0" /> <PackageReference Include="System.IO.FileSystem.AccessControl" Version="4.7.0" />
<PackageReference Include="System.Runtime.Caching" Version="4.7.0" /> <PackageReference Include="System.Runtime.Caching" Version="4.7.0" />
<PackageReference Include="Utf8Json" Version="1.3.7" />
<PackageReference Include="z440.atl.core" Version="3.5.0" /> <PackageReference Include="z440.atl.core" Version="3.5.0" />
<PackageReference Include="zlib.net-mutliplatform" Version="1.0.4" /> <PackageReference Include="zlib.net-mutliplatform" Version="1.0.4" />
</ItemGroup> </ItemGroup>

View file

@ -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)
{
}
}
}

View file

@ -1,10 +1,10 @@
using Newtonsoft.Json; using Roadie.Library.Extensions;
using Roadie.Library.Extensions;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.Json.Serialization;
namespace Roadie.Library.MetaData.Audio namespace Roadie.Library.MetaData.Audio
{ {
@ -14,10 +14,11 @@ namespace Roadie.Library.MetaData.Audio
public sealed class AudioMetaData : IAudioMetaData public sealed class AudioMetaData : IAudioMetaData
{ {
public const char ArtistSplitCharacter = '/'; public const char ArtistSplitCharacter = '/';
public const int MinimumYearValue = 1900; public const int MinimumYearValue = 1900;
public const string SoundTrackArtist = "Sound Tracks"; public const string SoundTrackArtist = "Sound Tracks";
private string _artist; private string _artist;
private bool _doModifyArtistNameOnGet = true; private bool _doModifyArtistNameOnGet = true;
private FileInfo _fileInfo; private FileInfo _fileInfo;
@ -43,15 +44,20 @@ namespace Roadie.Library.MetaData.Audio
{ {
get get
{ {
if (_doModifyArtistNameOnGet) if (_doModifyArtistNameOnGet && !string.IsNullOrEmpty(_artist) && _artist.Contains(ArtistSplitCharacter.ToString()))
if (!string.IsNullOrEmpty(_artist) && _artist.Contains(ArtistSplitCharacter.ToString())) {
return _artist.Split(ArtistSplitCharacter).First(); return _artist.Split(ArtistSplitCharacter)[0];
}
return _artist; return _artist;
} }
set set
{ {
_artist = value; _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 get
{ {
var result = AudioMetaDataWeights.None; var result = AudioMetaDataWeights.None;
if (!string.IsNullOrEmpty(Artist)) result |= AudioMetaDataWeights.Artist; if (!string.IsNullOrEmpty(Artist))
if (!string.IsNullOrEmpty(Title)) result |= AudioMetaDataWeights.Time; {
if ((Year ?? 0) > 1) result |= AudioMetaDataWeights.Year; result |= AudioMetaDataWeights.Artist;
if ((TrackNumber ?? 0) > 1) result |= AudioMetaDataWeights.TrackNumber; }
if ((TotalTrackNumbers ?? 0) > 1) result |= AudioMetaDataWeights.TrackTotalNumber;
if (TotalSeconds > 1) result |= AudioMetaDataWeights.Time; 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; return result;
} }
} }
@ -90,7 +120,11 @@ namespace Roadie.Library.MetaData.Audio
{ {
get get
{ {
if (string.IsNullOrEmpty(Filename)) return null; if (string.IsNullOrEmpty(Filename))
{
return null;
}
return Path.GetDirectoryName(Filename); return Path.GetDirectoryName(Filename);
} }
} }
@ -105,7 +139,8 @@ namespace Roadie.Library.MetaData.Audio
/// </summary> /// </summary>
public string DiscSubTitle { get; set; } public string DiscSubTitle { get; set; }
[JsonIgnore] public FileInfo FileInfo => _fileInfo ?? (_fileInfo = new FileInfo(Filename)); [JsonIgnore]
public FileInfo FileInfo => _fileInfo ?? (_fileInfo = new FileInfo(Filename));
/// <summary> /// <summary>
/// Full filename to the file used to get this AudioMetaData /// Full filename to the file used to get this AudioMetaData
@ -114,7 +149,8 @@ namespace Roadie.Library.MetaData.Audio
public ICollection<string> Genres { get; set; } public ICollection<string> Genres { get; set; }
[JsonIgnore] public IEnumerable<AudioMetaDataImage> Images { get; set; } [JsonIgnore]
public IEnumerable<AudioMetaDataImage> Images { get; set; }
public string ISRC { get; internal set; } public string ISRC { get; internal set; }
@ -122,10 +158,13 @@ namespace Roadie.Library.MetaData.Audio
{ {
get get
{ {
if (Genres != null && Genres.Any()) if (Genres?.Any() == true)
{ {
var soundtrackGenres = new List<string> { "24", "soundtrack" }; var soundtrackGenres = new List<string> { "24", "soundtrack" };
if (Genres.Intersect(soundtrackGenres, StringComparer.OrdinalIgnoreCase).Any()) return true; if (Genres.Intersect(soundtrackGenres, StringComparer.OrdinalIgnoreCase).Any())
{
return true;
}
} }
return false; return false;
@ -142,7 +181,7 @@ namespace Roadie.Library.MetaData.Audio
Release = Release == null ? null : Release.Equals("Unknown Release") ? null : Release; Release = Release == null ? null : Release.Equals("Unknown Release") ? null : Release;
if (!string.IsNullOrEmpty(Title)) if (!string.IsNullOrEmpty(Title))
{ {
var trackNumberTitle = string.Format("Track {0}", TrackNumber); var trackNumberTitle = $"Track {TrackNumber}";
Title = Title == trackNumberTitle ? null : Title; Title = Title == trackNumberTitle ? null : Title;
} }
@ -193,7 +232,10 @@ namespace Roadie.Library.MetaData.Audio
set set
{ {
_title = value; _title = value;
if (IsSoundTrack) Artist = SoundTrackArtist; if (IsSoundTrack)
{
Artist = SoundTrackArtist;
}
} }
} }
@ -206,7 +248,11 @@ namespace Roadie.Library.MetaData.Audio
{ {
get get
{ {
if (Time == null) return 0; if (Time == null)
{
return 0;
}
return Time.Value.TotalSeconds; return Time.Value.TotalSeconds;
} }
} }
@ -225,9 +271,11 @@ namespace Roadie.Library.MetaData.Audio
{ {
string result = null; string result = null;
if (!string.IsNullOrEmpty(_trackArtist)) if (!string.IsNullOrEmpty(_trackArtist))
result = _trackArtist.Split(ArtistSplitCharacter).First().ToTitleCase(); {
result = !_artist?.Equals(result, StringComparison.OrdinalIgnoreCase) ?? false ? result : null; result = _trackArtist.Split(ArtistSplitCharacter)[0].ToTitleCase();
return result; }
return !String.Equals(_artist, result, StringComparison.OrdinalIgnoreCase) ? result : null;
} }
set => _trackArtist = value; set => _trackArtist = value;
} }
@ -242,17 +290,33 @@ namespace Roadie.Library.MetaData.Audio
{ {
get get
{ {
if (string.IsNullOrEmpty(_trackArtist)) return new string[0]; if (string.IsNullOrEmpty(_trackArtist))
{
return new string[0];
}
if (!_trackArtist.Contains(ArtistSplitCharacter.ToString())) 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 }; return new string[1] { TrackArtist };
} }
if (!string.IsNullOrEmpty(_artist) || !string.IsNullOrEmpty(_trackArtist)) if (!string.IsNullOrEmpty(_artist) || !string.IsNullOrEmpty(_trackArtist))
if (!_artist.Equals(_trackArtist, StringComparison.OrdinalIgnoreCase)) {
return _trackArtist.Split(ArtistSplitCharacter).Where(x => !string.IsNullOrEmpty(x)) if (!String.Equals(_artist, _trackArtist, StringComparison.OrdinalIgnoreCase))
.Select(x => x.ToTitleCase()).OrderBy(x => x).ToArray(); {
return _trackArtist.Split(ArtistSplitCharacter)
.Where(x => !string.IsNullOrEmpty(x))
.Select(x => x.ToTitleCase())
.OrderBy(x => x)
.ToArray();
}
}
return new string[0]; return new string[0];
} }
} }
@ -280,8 +344,11 @@ namespace Roadie.Library.MetaData.Audio
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
var item = obj as AudioMetaData; if (!(obj is AudioMetaData item))
if (item == null) return false; {
return false;
}
return item.GetHashCode() == GetHashCode(); return item.GetHashCode() == GetHashCode();
} }
@ -290,12 +357,12 @@ namespace Roadie.Library.MetaData.Audio
unchecked unchecked
{ {
var hash = 17; var hash = 17;
hash = hash * 23 + Artist.GetHashCode(); hash = (hash * 23) + Artist.GetHashCode();
hash = hash * 23 + Release.GetHashCode(); hash = (hash * 23) + Release.GetHashCode();
hash = hash * 23 + Title.GetHashCode(); hash = (hash * 23) + Title.GetHashCode();
hash = hash * 23 + TrackNumber.GetHashCode(); hash = (hash * 23) + TrackNumber.GetHashCode();
hash = hash * 23 + AudioBitrate.GetHashCode(); hash = (hash * 23) + AudioBitrate.GetHashCode();
hash = hash * 23 + AudioSampleRate.GetHashCode(); hash = (hash * 23) + AudioSampleRate.GetHashCode();
return hash; return hash;
} }
} }
@ -314,9 +381,17 @@ namespace Roadie.Library.MetaData.Audio
{ {
var result = var result =
$"IsValid: {IsValid}{(IsSoundTrack ? " [SoundTrack ]" : string.Empty)}, ValidWeight {ValidWeight}, Artist: {Artist}"; $"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}"; 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())}"; result += $", Title: {Title}, Year: {Year}, Duration: {(Time == null ? "-" : Time.Value.ToString())}";
return result; return result;
} }

View file

@ -13,7 +13,6 @@
public string comment { get; set; } public string comment { get; set; }
public int edit { get; set; } public int edit { get; set; }
public bool front { get; set; } public bool front { get; set; }
public string id { get; set; }
public string image { get; set; } public string image { get; set; }
public Thumbnails thumbnails { get; set; } public Thumbnails thumbnails { get; set; }
public string[] types { get; set; } public string[] types { get; set; }

View file

@ -1,7 +1,7 @@
using Newtonsoft.Json; using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Text.Json.Serialization;
namespace Roadie.Library.MetaData.MusicBrainz namespace Roadie.Library.MetaData.MusicBrainz
{ {
@ -13,51 +13,59 @@ namespace Roadie.Library.MetaData.MusicBrainz
public class CoverArtArchive public class CoverArtArchive
{ {
public bool artwork { get; set; } public bool artwork { get; set; }
public bool back { get; set; } public bool back { get; set; }
public int? count { get; set; } public int? count { get; set; }
public bool darkened { get; set; } public bool darkened { get; set; }
public bool front { get; set; } public bool front { get; set; }
} }
public class Label public class Label
{ {
public List<Alias> aliases { get; set; } public List<Alias> aliases { get; set; }
public string disambiguation { get; set; } public string disambiguation { get; set; }
public NameAndCount[] genres { get; set; }
public string id { get; set; } public string id { get; set; }
[JsonProperty(PropertyName = "label-code")] [JsonPropertyName("label-code")]
public int? labelcode { get; set; } public int? labelcode { get; set; }
public string name { get; set; } public string name { get; set; }
[JsonProperty(PropertyName = "sort-name")] [JsonPropertyName("sort-name")]
public string sortname { get; set; } public string sortname { get; set; }
public NameAndCount[] tags { get; set; } public NameAndCount[] tags { get; set; }
public NameAndCount[] genres { get; set; }
} }
[Serializable] [Serializable]
public class LabelInfo public class LabelInfo
{ {
[JsonProperty(PropertyName = "catalog-number")] [JsonPropertyName("catalog-number")]
public string catalognumber { get; set; } public string catalognumber { get; set; }
public Label label { get; set; } public Label label { get; set; }
} }
[Serializable] [Serializable]
public class Medium public class Medium
{ {
public object format { get; set; } public object format { get; set; }
public int? position { get; set; } public int? position { get; set; }
public string title { get; set; } public string title { get; set; }
[JsonProperty(PropertyName = "track-count")] [JsonPropertyName("track-count")]
public short? trackcount { get; set; } public short? trackcount { get; set; }
[JsonProperty(PropertyName = "track-offset")]
[JsonPropertyName("track-offset")]
public int? trackoffset { get; set; } public int? trackoffset { get; set; }
public List<Track> tracks { get; set; } public List<Track> tracks { get; set; }
@ -67,37 +75,57 @@ namespace Roadie.Library.MetaData.MusicBrainz
public class Recording public class Recording
{ {
public List<Alias> aliases { get; set; } public List<Alias> aliases { get; set; }
public string disambiguation { 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 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] [Serializable]
public class Relation public class Relation
{ {
public List<object> attributes { get; set; } public List<object> attributes { get; set; }
public AttributeValues attributevalues { get; set; } public AttributeValues attributevalues { get; set; }
public object begin { get; set; } public object begin { get; set; }
public string direction { get; set; } public string direction { get; set; }
public object end { get; set; } public object end { get; set; }
public bool ended { get; set; }
public bool? ended { get; set; }
public string sourcecredit { get; set; } public string sourcecredit { get; set; }
public string targetcredit { get; set; } public string targetcredit { get; set; }
public string targettype { get; set; } public string targettype { get; set; }
public string type { get; set; } public string type { get; set; }
public string typeid { get; set; } public string typeid { get; set; }
public MbUrl url { get; set; } public MbUrl url { get; set; }
} }
[Serializable] [Serializable]
public class RepositoryRelease public class RepositoryRelease
{ {
public int Id { get; set; }
public string ArtistMbId { get; set; } public string ArtistMbId { get; set; }
public int Id { get; set; }
public Release Release { get; set; } public Release Release { get; set; }
} }
@ -106,46 +134,61 @@ namespace Roadie.Library.MetaData.MusicBrainz
public class Release public class Release
{ {
public List<object> aliases { get; set; } public List<object> aliases { get; set; }
[JsonPropertyName("artist-credits")]
public Artist[] artistcredits { get; set; }
public string asin { get; set; } public string asin { get; set; }
public string barcode { get; set; } public string barcode { get; set; }
public string country { get; set; } public string country { get; set; }
[JsonProperty(PropertyName = "cover-art-archive")] [JsonPropertyName("cover-art-archive")]
public CoverArtArchive coverartarchive { get; set; } public CoverArtArchive coverartarchive { get; set; }
public string coverThumbnailUrl { get; set; } public string coverThumbnailUrl { get; set; }
public string date { get; set; } public string date { get; set; }
public string disambiguation { get; set; } public string disambiguation { get; set; }
public string id { get; set; } public string id { get; set; }
public List<string> imageUrls { get; set; } public List<string> imageUrls { get; set; }
[JsonProperty(PropertyName = "label-info")] [JsonPropertyName("label-info")]
public List<LabelInfo> labelinfo { get; set; } public List<LabelInfo> labelinfo { get; set; }
public List<Medium> media { get; set; } public List<Medium> media { get; set; }
public string packaging { get; set; } public string packaging { get; set; }
public string quality { get; set; } public string quality { get; set; }
public List<Relation> relations { get; set; } public List<Relation> relations { get; set; }
[JsonProperty(PropertyName = "release-events")] [JsonPropertyName("release-events")]
public List<ReleaseEvents> releaseevents { get; set; } public List<ReleaseEvents> releaseevents { get; set; }
[JsonProperty(PropertyName = "release-group")]
[JsonPropertyName("release-group")]
public ReleaseGroup releasegroup { get; set; } public ReleaseGroup releasegroup { get; set; }
public string status { get; set; } public string status { get; set; }
[JsonProperty(PropertyName = "text-representation")]
[JsonPropertyName("text-representation")]
public TextRepresentation textrepresentation { get; set; } public TextRepresentation textrepresentation { get; set; }
public string title { get; set; } public string title { get; set; }
[JsonProperty("artist-credits")]
public Artist[] artistcredits { get; set; }
} }
[Serializable] [Serializable]
public class ReleaseBrowseResult public class ReleaseBrowseResult
{ {
[JsonProperty(PropertyName = "release-count")] [JsonPropertyName("release-count")]
public int? releasecount { get; set; } public int? releasecount { get; set; }
[JsonProperty(PropertyName = "release-offset")] [JsonPropertyName("release-offset")]
public int? releaseoffset { get; set; } public int? releaseoffset { get; set; }
public List<Release> releases { get; set; } public List<Release> releases { get; set; }
@ -163,22 +206,31 @@ namespace Roadie.Library.MetaData.MusicBrainz
public class ReleaseGroup public class ReleaseGroup
{ {
public List<object> aliases { get; set; } public List<object> aliases { get; set; }
public string disambiguation { get; set; } public string disambiguation { get; set; }
[JsonProperty("first-release-date")]
[JsonPropertyName("first-release-date")]
public string firstreleasedate { get; set; } public string firstreleasedate { get; set; }
public string id { get; set; }
[JsonProperty("primary-type")]
public string primarytype { get; set; }
public List<object> secondarytypes { get; set; }
public string title { get; set; }
public NameAndCount[] tags { get; set; }
public NameAndCount[] genres { get; set; } public NameAndCount[] genres { get; set; }
public string id { get; set; }
[JsonPropertyName("primary-type")]
public string primarytype { get; set; }
public List<object> secondarytypes { get; set; }
public NameAndCount[] tags { get; set; }
public string title { get; set; }
} }
[Serializable] [Serializable]
public class TextRepresentation public class TextRepresentation
{ {
public string language { get; set; } public string language { get; set; }
public string script { get; set; } public string script { get; set; }
} }
@ -186,20 +238,23 @@ namespace Roadie.Library.MetaData.MusicBrainz
public class Track public class Track
{ {
public string id { get; set; } public string id { get; set; }
public int? length { get; set; } public int? length { get; set; }
public string number { get; set; } public string number { get; set; }
public string position { get; set; }
public int? position { get; set; }
public Recording recording { get; set; } public Recording recording { get; set; }
public string title { get; set; } public string title { get; set; }
} }
[Serializable] [Serializable]
public class MbUrl public class MbUrl
{ {
public string id { get; set; } public string id { get; set; }
public string resource { get; set; } public string resource { get; set; }
} }
} }

View file

@ -88,7 +88,7 @@ namespace Roadie.Library.MetaData.MusicBrainz
Release = release.title, Release = release.title,
Title = track.title, Title = track.title,
Time = track.length.HasValue ? (TimeSpan?)TimeSpan.FromMilliseconds(track.length.Value) : null, Time = track.length.HasValue ? (TimeSpan?)TimeSpan.FromMilliseconds(track.length.Value) : null,
TrackNumber = SafeParser.ToNumber<short?>(track.position ?? track.number) ?? 0, TrackNumber = SafeParser.ToNumber<short?>(track.position) ?? SafeParser.ToNumber<short?>(track.number) ?? 0,
Disc = media.position, Disc = media.position,
Year = date > 0 ? (int?)date : null, Year = date > 0 ? (int?)date : null,
TotalTrackNumbers = media.trackcount TotalTrackNumbers = media.trackcount

View file

@ -1,8 +1,8 @@
using Newtonsoft.Json; using Roadie.Library.Utility;
using Roadie.Library.Utility;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Net; using System.Net;
using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -48,6 +48,7 @@ namespace Roadie.Library.MetaData.MusicBrainz
{ {
var tryCount = 0; var tryCount = 0;
var result = default(T); var result = default(T);
string downloadedString = null;
while (result == null && tryCount < MaxRetries) while (result == null && tryCount < MaxRetries)
{ {
try try
@ -55,7 +56,11 @@ namespace Roadie.Library.MetaData.MusicBrainz
using (var webClient = new WebClient()) using (var webClient = new WebClient())
{ {
webClient.Headers.Add("user-agent", WebHelper.UserAgent); webClient.Headers.Add("user-agent", WebHelper.UserAgent);
result = JsonConvert.DeserializeObject<T>(await webClient.DownloadStringTaskAsync(new Uri(url)).ConfigureAwait(false)); downloadedString = await webClient.DownloadStringTaskAsync(new Uri(url)).ConfigureAwait(false);
if (!string.IsNullOrWhiteSpace(downloadedString))
{
result = JsonSerializer.Deserialize<T>(downloadedString);
}
} }
} }
catch (WebException ex) catch (WebException ex)
@ -69,7 +74,7 @@ namespace Roadie.Library.MetaData.MusicBrainz
} }
catch (Exception ex) catch (Exception ex)
{ {
Trace.WriteLine($"GetAsync: [{ ex.ToString() }]", "Warning"); Trace.WriteLine($"GetAsync: DownloadedString [{ downloadedString }], Exception: [{ ex }]", "Warning");
Thread.Sleep(100); Thread.Sleep(100);
} }
finally finally

View file

@ -1,140 +1,194 @@
using Newtonsoft.Json; using System;
using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Text; using System.Text.Json.Serialization;
namespace Roadie.Library.MetaData.MusicBrainz namespace Roadie.Library.MetaData.MusicBrainz
{ {
[Serializable] [Serializable]
public class ArtistResult public class ArtistResult
{ {
public DateTime created { get; set; }
public int count { get; set; }
public int offset { get; set; }
public Artist[] artists { get; set; } public Artist[] artists { get; set; }
public int count { get; set; }
public DateTime created { get; set; }
public int offset { get; set; }
} }
[Serializable] [Serializable]
public class RepositoryArtist public class RepositoryArtist
{ {
public int Id { get; set; }
public string ArtistName { get; set; }
public string ArtistMbId { get; set; }
public Artist Artist { 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}")] [DebuggerDisplay("name: {name}")]
[Serializable] [Serializable]
public class Artist 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 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 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 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] [Serializable]
public class Relations public class Relations
{ {
[JsonProperty("attribute-ids")] [JsonPropertyName("attribute-ids")]
public string[] attributeids { get; set; } public string[] attributeids { get; set; }
public string[] attributes { 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; } public string[] attributevalues { get; set; }
[JsonProperty("source-credit")]
public string sourcecredit { get; set; }
public object end { get; set; }
public object begin { 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] [Serializable]
public class Url public class Url
{ {
public string resource { get; set; }
public string id { get; set; } public string id { get; set; }
}
public string resource { get; set; }
}
[Serializable] [Serializable]
public class Area public class Area
{ {
public string id { get; set; } public string id { get; set; }
public string type { get; set; }
[JsonProperty("type-id")] [JsonPropertyName("iso-3166-1-codes")]
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")]
public string[] iso31661codes { get; set; } 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] [Serializable]
public class LifeSpan public class LifeSpan
{ {
public string end { get; set; }
public string ended { get; set; }
public string begin { get; set; } public string begin { get; set; }
public string end { get; set; }
public bool? ended { get; set; }
} }
[Serializable] [Serializable]
public class BeginAndEndArea public class BeginAndEndArea
{ {
public string id { get; set; } public string id { get; set; }
public string type { get; set; }
public string typeid { get; set; } [JsonPropertyName("life-span")]
public string name { get; set; }
[JsonProperty("sort-name")]
public string sortname { get; set; }
[JsonProperty("life-span")]
public LifeSpan lifespan { get; set; } 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] [Serializable]
public class Alias 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 begin { get; set; }
public string end { 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] [Serializable]
@ -147,7 +201,7 @@ namespace Roadie.Library.MetaData.MusicBrainz
public class NameAndCount public class NameAndCount
{ {
public int? count { get; set; } public int? count { get; set; }
public string name { get; set; } public string name { get; set; }
} }
} }

View file

@ -7,10 +7,10 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Hashids.net" Version="1.3.0" /> <PackageReference Include="Hashids.net" Version="1.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="3.1.4" /> <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="3.1.5" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.6.0" /> <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.6.0" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.1.2" /> <PackageReference Include="System.Linq.Dynamic.Core" Version="1.1.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -102,78 +102,56 @@ namespace Roadie.Api.Services
return await GetArtist(artistByName.RoadieId).ConfigureAwait(false); return await GetArtist(artistByName.RoadieId).ConfigureAwait(false);
} }
protected async Task<data.Artist> GetArtist(Guid id) protected Task<data.Artist> GetArtist(Guid id)
{ {
if (id == Guid.Empty) return CacheManager.GetAsync(data.Artist.CacheUrn(id), async () =>
{
return null;
}
return await CacheManager.GetAsync(data.Artist.CacheUrn(id), async () =>
{ {
return await DbContext.Artists return await DbContext.Artists
.Include(x => x.Genres) .Include(x => x.Genres)
.Include("Genres.Genre") .Include("Genres.Genre")
.FirstOrDefaultAsync(x => x.RoadieId == id).ConfigureAwait(false); .FirstOrDefaultAsync(x => x.RoadieId == id)
}, data.Artist.CacheRegionUrn(id)).ConfigureAwait(false); .ConfigureAwait(false);
}, data.Artist.CacheRegionUrn(id));
} }
protected async Task<data.Collection> GetCollection(Guid id) protected Task<data.Collection> GetCollection(Guid id)
{ {
if (id == Guid.Empty) return CacheManager.GetAsync(data.Collection.CacheUrn(id), async () =>
{
return null;
}
return await CacheManager.GetAsync(data.Collection.CacheUrn(id), async () =>
{ {
return await DbContext.Collections.FirstOrDefaultAsync(x => x.RoadieId == id).ConfigureAwait(false); return await DbContext.Collections.FirstOrDefaultAsync(x => x.RoadieId == id).ConfigureAwait(false);
}, data.Collection.CacheRegionUrn(id)).ConfigureAwait(false); }, data.Collection.CacheRegionUrn(id));
} }
protected async Task<data.Genre> GetGenre(Guid id) protected Task<data.Genre> GetGenre(Guid id)
{ {
if (id == Guid.Empty) return CacheManager.GetAsync(data.Genre.CacheUrn(id), async () =>
{
return null;
}
return await CacheManager.GetAsync(data.Genre.CacheUrn(id), async () =>
{ {
return await DbContext.Genres.FirstOrDefaultAsync(x => x.RoadieId == id).ConfigureAwait(false); return await DbContext.Genres.FirstOrDefaultAsync(x => x.RoadieId == id).ConfigureAwait(false);
}, data.Genre.CacheRegionUrn(id)).ConfigureAwait(false); }, data.Genre.CacheRegionUrn(id));
} }
protected async Task<data.Label> GetLabel(Guid id) protected Task<data.Label> GetLabel(Guid id)
{ {
if (id == Guid.Empty) return CacheManager.GetAsync(data.Label.CacheUrn(id), async () =>
{
return null;
}
return await CacheManager.GetAsync(data.Label.CacheUrn(id), async () =>
{ {
return await DbContext.Labels.FirstOrDefaultAsync(x => x.RoadieId == id).ConfigureAwait(false); return await DbContext.Labels.FirstOrDefaultAsync(x => x.RoadieId == id).ConfigureAwait(false);
}, data.Label.CacheRegionUrn(id)).ConfigureAwait(false); }, data.Label.CacheRegionUrn(id));
} }
protected async Task<data.Playlist> GetPlaylist(Guid id) protected Task<data.Playlist> GetPlaylist(Guid id)
{ {
if (id == Guid.Empty) return CacheManager.GetAsync(data.Playlist.CacheUrn(id), async () =>
{
return null;
}
return await CacheManager.GetAsync(data.Playlist.CacheUrn(id), async () =>
{ {
return await DbContext.Playlists return await DbContext.Playlists
.Include(x => x.User) .Include(x => x.User)
.FirstOrDefaultAsync(x => x.RoadieId == id).ConfigureAwait(false); .FirstOrDefaultAsync(x => x.RoadieId == id)
}, data.Playlist.CacheRegionUrn(id)).ConfigureAwait(false); .ConfigureAwait(false);
}, data.Playlist.CacheRegionUrn(id));
} }
protected async Task<data.Release> GetRelease(Guid id) protected Task<data.Release> GetRelease(Guid id)
{ {
if (id == Guid.Empty) return CacheManager.GetAsync(data.Release.CacheUrn(id), async () =>
{
return null;
}
return await CacheManager.GetAsync(data.Release.CacheUrn(id), async () =>
{ {
return await DbContext.Releases return await DbContext.Releases
.Include(x => x.Artist) .Include(x => x.Artist)
@ -183,7 +161,7 @@ namespace Roadie.Api.Services
.Include("Medias.Tracks") .Include("Medias.Tracks")
.Include("Medias.Tracks.TrackArtist") .Include("Medias.Tracks.TrackArtist")
.FirstOrDefaultAsync(x => x.RoadieId == id).ConfigureAwait(false); .FirstOrDefaultAsync(x => x.RoadieId == id).ConfigureAwait(false);
}, data.Release.CacheRegionUrn(id)).ConfigureAwait(false); }, data.Release.CacheRegionUrn(id));
} }
/// <summary> /// <summary>
@ -199,13 +177,9 @@ namespace Roadie.Api.Services
} }
// Only read operations // Only read operations
protected async Task<data.Track> GetTrack(Guid id) protected Task<data.Track> GetTrack(Guid id)
{ {
if(id == Guid.Empty) return CacheManager.GetAsync(data.Track.CacheUrn(id), async () =>
{
return null;
}
return await CacheManager.GetAsync(data.Track.CacheUrn(id), async () =>
{ {
return await DbContext.Tracks return await DbContext.Tracks
.Include(x => x.ReleaseMedia) .Include(x => x.ReleaseMedia)
@ -213,7 +187,7 @@ namespace Roadie.Api.Services
.Include(x => x.ReleaseMedia.Release.Artist) .Include(x => x.ReleaseMedia.Release.Artist)
.Include(x => x.TrackArtist) .Include(x => x.TrackArtist)
.FirstOrDefaultAsync(x => x.RoadieId == id).ConfigureAwait(false); .FirstOrDefaultAsync(x => x.RoadieId == id).ConfigureAwait(false);
}, data.Track.CacheRegionUrn(id)).ConfigureAwait(false); }, data.Track.CacheRegionUrn(id));
} }
protected async Task<User> GetUser(string username) protected async Task<User> GetUser(string username)
@ -229,13 +203,9 @@ namespace Roadie.Api.Services
return await GetUser(userByUsername?.RoadieId).ConfigureAwait(false); return await GetUser(userByUsername?.RoadieId).ConfigureAwait(false);
} }
protected async Task<User> GetUser(Guid? id) protected Task<User> GetUser(Guid? id)
{ {
if (!id.HasValue) return CacheManager.GetAsync(User.CacheUrn(id.Value), async () =>
{
return null;
}
return await CacheManager.GetAsync(User.CacheUrn(id.Value), async () =>
{ {
return await DbContext.Users return await DbContext.Users
.Include(x => x.UserRoles) .Include(x => x.UserRoles)
@ -245,7 +215,7 @@ namespace Roadie.Api.Services
.Include(x => x.UserQues) .Include(x => x.UserQues)
.Include("UserQues.Track") .Include("UserQues.Track")
.FirstOrDefaultAsync(x => x.RoadieId == id).ConfigureAwait(false); .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}"); protected string MakeLastFmUrl(string artistName, string releaseTitle) => "http://www.last.fm/music/" + HttpEncoder.UrlEncode($"{artistName}/{releaseTitle}");

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,6 @@
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Roadie.Library; using Roadie.Library;
using Roadie.Library.Caching; using Roadie.Library.Caching;
using Roadie.Library.Configuration; using Roadie.Library.Configuration;
@ -24,6 +23,7 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Linq.Dynamic.Core; using System.Linq.Dynamic.Core;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using data = Roadie.Library.Data; using data = Roadie.Library.Data;
@ -791,7 +791,7 @@ namespace Roadie.Api.Services
} }
catch (Exception ex) 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<TrackList> return new Library.Models.Pagination.PagedResult<TrackList>
{ {
Message = "An Error has occured" Message = "An Error has occured"

View file

@ -2,7 +2,6 @@
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Roadie.Library; using Roadie.Library;
using Roadie.Library.Caching; using Roadie.Library.Caching;
using Roadie.Library.Configuration; using Roadie.Library.Configuration;
@ -23,6 +22,7 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Linq.Dynamic.Core; using System.Linq.Dynamic.Core;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using data = Roadie.Library.Data; using data = Roadie.Library.Data;
using models = Roadie.Library.Models; using models = Roadie.Library.Models;
@ -593,7 +593,7 @@ namespace Roadie.Api.Services
result.AdditionalData.Add("Timing", sw.ElapsedMilliseconds); result.AdditionalData.Add("Timing", sw.ElapsedMilliseconds);
Logger.LogTrace( 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; return result;
} }

View file

@ -2,7 +2,6 @@
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Roadie.Api.Services; using Roadie.Api.Services;
using Roadie.Library.Caching; using Roadie.Library.Caching;
using Roadie.Library.Configuration; using Roadie.Library.Configuration;
@ -13,6 +12,7 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using models = Roadie.Library.Models.Users; using models = Roadie.Library.Models.Users;
@ -146,7 +146,7 @@ namespace Roadie.Api.Controllers
}; };
await playActivityService.NowPlayingAsync(user, scrobble).ConfigureAwait(false); await playActivityService.NowPlayingAsync(user, scrobble).ConfigureAwait(false);
sw.Stop(); 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(); return new EmptyResult();
} }

View file

@ -2,7 +2,6 @@
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Roadie.Api.ModelBinding; using Roadie.Api.ModelBinding;
using Roadie.Api.Services; using Roadie.Api.Services;
using Roadie.Library.Caching; using Roadie.Library.Caching;
@ -15,6 +14,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using User = Roadie.Library.Models.Users.User; using User = Roadie.Library.Models.Users.User;
@ -364,7 +364,7 @@ namespace Roadie.Api.Controllers
if (!result.IsSuccess) if (!result.IsSuccess)
{ {
Logger.LogWarning($"GetCoverArt Failed For [{JsonConvert.SerializeObject(request)}]"); Logger.LogWarning($"GetCoverArt Failed For [{ JsonSerializer.Serialize(request)}]");
return StatusCode((int)HttpStatusCode.InternalServerError); return StatusCode((int)HttpStatusCode.InternalServerError);
} }
@ -901,7 +901,7 @@ namespace Roadie.Api.Controllers
} }
else 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)) if ((request?.f ?? string.Empty).Equals("jsonp", StringComparison.OrdinalIgnoreCase))

View file

@ -21,9 +21,7 @@ namespace Roadie.Api.Controllers
[Authorize] [Authorize]
public class UserController : EntityControllerBase public class UserController : EntityControllerBase
{ {
private IHttpContext RoadieHttpContext { get; } private IHttpContext RoadieHttpContext { get; }
private IUserService UserService { get; } private IUserService UserService { get; }
private readonly ITokenService TokenService; private readonly ITokenService TokenService;
@ -65,8 +63,14 @@ namespace Roadie.Api.Controllers
public async Task<IActionResult> Get(Guid id, string inc = null) public async Task<IActionResult> Get(Guid id, string inc = null)
{ {
var user = await CurrentUserModel().ConfigureAwait(false); var user = await CurrentUserModel().ConfigureAwait(false);
var result = await CacheManager.GetAsync($"urn:user_model_by_id:{id}", var result = await CacheManager.GetAsync($"urn:user_model_by_id:{id}", async () =>
async () => await UserService.ByIdAsync(user, id, (inc ?? Library.Models.Users.User.DefaultIncludes).ToLower().Split(",")).ConfigureAwait(false), ControllerCacheRegionUrn).ConfigureAwait(false); {
return await UserService.ByIdAsync(user, id, (inc ?? Library.Models.Users.User.DefaultIncludes)
.ToLower()
.Split(","))
.ConfigureAwait(false);
},
ControllerCacheRegionUrn).ConfigureAwait(false);
if (result?.IsNotFoundResult != false) if (result?.IsNotFoundResult != false)
{ {
return NotFound(); return NotFound();
@ -386,11 +390,5 @@ namespace Roadie.Api.Controllers
DefaultRowsPerPage = modelUser.DefaultRowsPerPage ?? RoadieSettings.DefaultRowsPerPage DefaultRowsPerPage = modelUser.DefaultRowsPerPage ?? RoadieSettings.DefaultRowsPerPage
}); });
} }
public class PagingParams
{
public int Limit { get; set; } = 5;
public int Page { get; set; } = 1;
}
} }
} }

View file

@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Pastel;
using Serilog; using Serilog;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
@ -31,19 +32,19 @@ namespace Roadie.Api
#if DEBUG #if DEBUG
// Logging Output tests // Logging Output tests
Log.Verbose(":: Log Test: Verbose (Trace,None)"); // Microsoft.Extensions.Logging.LogLevel.Trace and Microsoft.Extensions.Logging.LogLevel.None 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"); // Microsoft.Extensions.Logging.LogLevel.Debug Log.Debug(":: Log Test: Debug, Test 'value', Test: Value"); // Microsoft.Extensions.Logging.LogLevel.Debug
Log.Information(":: Log Test: Information"); // Microsoft.Extensions.Logging.LogLevel.Information Log.Information(":: Log Test: Information, Test 'value', Test: Value"); // Microsoft.Extensions.Logging.LogLevel.Information
Log.Warning(":: Log Test: Warning"); // Microsoft.Extensions.Logging.LogLevel.Warning 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"); // Microsoft.Extensions.Logging.LogLevel.Error 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)"); // Microsoft.Extensions.Logging.LogLevel.Critical Log.Fatal(":: Log Test: Fatal (Critial), Test 'value', Test: Value"); // Microsoft.Extensions.Logging.LogLevel.Critical
Trace.WriteLine(":: Log Test: Trace WriteLine()"); Trace.WriteLine(":: Log Test: Trace WriteLine(), Test 'value', Test: Value");
#endif #endif
Console.WriteLine(""); Console.WriteLine("");
Console.WriteLine(@" ____ __ __ ____ __ ____ __ ____ __ "); Console.WriteLine(@" ____ __ __ ____ __ ____ __ ____ __ ".Pastel("#FEFF0E"));
Console.WriteLine(@"( _ \ / \ / _\ ( \( )( __) / _\ ( _ \( ) "); Console.WriteLine(@"( _ \ / \ / _\ ( \( )( __) / _\ ( _ \( ) ".Pastel("#F49014"));
Console.WriteLine(@" ) /( O )/ \ ) D ( )( ) _) / \ ) __/ )( "); Console.WriteLine(@" ) /( O )/ \ ) D ( )( ) _) / \ ) __/ )( ".Pastel("#E30014"));
Console.WriteLine(@"(__\_) \__/ \_/\_/(____/(__)(____) \_/\_/(__) (__) "); Console.WriteLine(@"(__\_) \__/ \_/\_/(____/(__)(____) \_/\_/(__) (__) ".Pastel("#DB0083"));
Console.WriteLine(""); Console.WriteLine("");
CreateHostBuilder(args).Build().Run(); CreateHostBuilder(args).Build().Run();

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<VersionPrefix>1.1.2</VersionPrefix> <VersionPrefix>1.1.3</VersionPrefix>
<VersionSuffix></VersionSuffix> <VersionSuffix></VersionSuffix>
</PropertyGroup> </PropertyGroup>
@ -31,15 +31,16 @@
<PackageReference Include="BCrypt-Core" Version="2.0.0" /> <PackageReference Include="BCrypt-Core" Version="2.0.0" />
<PackageReference Include="Mapster" Version="5.3.2" /> <PackageReference Include="Mapster" Version="5.3.2" />
<PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.14.0" /> <PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.14.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.4" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.5" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.4" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.5" />
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="3.6.0" /> <PackageReference Include="Microsoft.CodeAnalysis.Common" Version="3.6.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.6.0" /> <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.6.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.6.0" /> <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.6.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.4" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.5" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.3" /> <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.3" />
<PackageReference Include="Pastel" Version="1.3.2" />
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" /> <PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.1.3" /> <PackageReference Include="Serilog.Enrichers.Environment" Version="2.1.3" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" /> <PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
@ -49,7 +50,7 @@
<PackageReference Include="Serilog.Sinks.LiteDB.NetStandard" Version="1.0.14" /> <PackageReference Include="Serilog.Sinks.LiteDB.NetStandard" Version="1.0.14" />
<PackageReference Include="Serilog.Sinks.RollingFileAlternate" Version="2.0.9" /> <PackageReference Include="Serilog.Sinks.RollingFileAlternate" Version="2.0.9" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.6.0" /> <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.6.0" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.1.2" /> <PackageReference Include="System.Linq.Dynamic.Core" Version="1.1.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -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, string>
{
[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",
}
);
}
}

View file

@ -16,7 +16,6 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using Roadie.Api.Hubs; using Roadie.Api.Hubs;
using Roadie.Api.ModelBinding; using Roadie.Api.ModelBinding;
using Roadie.Api.Services; using Roadie.Api.Services;
@ -113,10 +112,17 @@ namespace Roadie.Api
services.AddSingleton<IHttpEncoder, HttpEncoder>(); services.AddSingleton<IHttpEncoder, HttpEncoder>();
services.AddSingleton<IEmailSender, EmailSenderService>(); services.AddSingleton<IEmailSender, EmailSenderService>();
services.AddSingleton<ICacheSerializer>(options =>
{
var logger = options.GetService<ILogger<Utf8JsonCacheSerializer>>();
return new Utf8JsonCacheSerializer(logger);
});
services.AddSingleton<ICacheManager>(options => services.AddSingleton<ICacheManager>(options =>
{ {
var logger = options.GetService<ILogger<MemoryCacheManager>>(); var logger = options.GetService<ILogger<MemoryCacheManager>>();
return new MemoryCacheManager(logger, new CachePolicy(TimeSpan.FromHours(4))); var serializer = options.GetService<ICacheSerializer>();
return new MemoryCacheManager(logger, serializer, new CachePolicy(TimeSpan.FromHours(4)));
}); });
var dbFolder = new DirectoryInfo(settings.FileDatabaseOptions.DatabaseFolder); var dbFolder = new DirectoryInfo(settings.FileDatabaseOptions.DatabaseFolder);
@ -326,11 +332,7 @@ namespace Roadie.Api
options.RespectBrowserAcceptHeader = true; // false by default options.RespectBrowserAcceptHeader = true; // false by default
options.ModelBinderProviders.Insert(0, new SubsonicRequestBinderProvider()); options.ModelBinderProviders.Insert(0, new SubsonicRequestBinderProvider());
}) })
.AddNewtonsoftJson(options => .AddJsonOptions(options => options.JsonSerializerOptions.IgnoreNullValues = true)
{
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
})
.AddXmlSerializerFormatters(); .AddXmlSerializerFormatters();
services.Configure<IdentityOptions>(options => services.Configure<IdentityOptions>(options =>

View file

@ -20,8 +20,8 @@
{ {
"Name": "Console", "Name": "Console",
"Args": { "Args": {
"theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console", "theme": "Roadie.Api.RoadieSerilogThemes::RoadieRainbow, Roadie.Api",
"restrictedToMinimumLevel": "Information" "restrictedToMinimumLevel": "Verbose"
} }
}, },
{ {

View file

@ -1,5 +1,4 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Roadie.Dlna.Server.Metadata; using Roadie.Dlna.Server.Metadata;
using Roadie.Dlna.Utility; using Roadie.Dlna.Utility;
using System; using System;
@ -13,23 +12,16 @@ namespace Roadie.Dlna.Server
internal partial class MediaMount internal partial class MediaMount
{ {
private const string NS_DC = "http://purl.org/dc/elements/1.1/"; 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_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_DLNA = "urn:schemas-dlna-org:metadata-1-0/";
private const string NS_SEC = "http://www.sec.co.kr/"; 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_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/"; private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/";
private static readonly IDictionary<string, AttributeCollection> soapCache = new LeastRecentlyUsedDictionary<string, AttributeCollection>(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 string featureList = Encoding.UTF8.GetString(ResourceHelper.GetResourceData("x_featurelist.xml") ?? new byte[0]);
private static readonly IDictionary<string, AttributeCollection> soapCache = new LeastRecentlyUsedDictionary<string, AttributeCollection>(200);
private static void AddBookmarkInfo(IMediaResource resource, XmlElement item) private static void AddBookmarkInfo(IMediaResource resource, XmlElement item)
{ {
var bookmarkable = resource as IBookmarkable; var bookmarkable = resource as IBookmarkable;
@ -79,10 +71,7 @@ namespace Roadie.Dlna.Server
var res = result.CreateElement(string.Empty, "res", NS_DIDL); var res = result.CreateElement(string.Empty, "res", NS_DIDL);
res.InnerText = curl; res.InnerText = curl;
res.SetAttribute("protocolInfo", string.Format( 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}");
"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
));
var width = c.MetaWidth; var width = c.MetaWidth;
var height = c.MetaHeight; var height = c.MetaHeight;
if (width.HasValue && height.HasValue) if (width.HasValue && height.HasValue)
@ -270,10 +259,7 @@ namespace Roadie.Dlna.Server
res.SetAttribute("duration", prop); res.SetAttribute("duration", prop);
} }
res.SetAttribute("protocolInfo", string.Format( 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}");
"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
));
item.AppendChild(res); item.AppendChild(res);
AddCover(request, resource, item); 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 }]"); 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); var body = soap.SelectSingleNode("//soap:Body", namespaceMgr);
if (body == null) if (body == null)
{ {
@ -625,5 +611,7 @@ namespace Roadie.Dlna.Server
rv.Headers.Add("EXT", string.Empty); rv.Headers.Add("EXT", string.Empty);
return rv; return rv;
} }
private static readonly XmlNamespaceManager namespaceMgr = CreateNamespaceManager();
} }
} }

BIN
libraries/libwebp_x64.dll Normal file

Binary file not shown.