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>
<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>

View file

@ -47,7 +47,7 @@ namespace Roadie.Library.Tests
IConfiguration configuration = configurationBuilder.Build();
configuration.GetSection("RoadieSettings").Bind(settings);
Configuration = settings;
CacheManager = new DictionaryCacheManager(Logger, new CachePolicy(TimeSpan.FromHours(4)));
CacheManager = new DictionaryCacheManager(Logger, new NewtonsoftCacheSerializer(Logger), new CachePolicy(TimeSpan.FromHours(4)));
HttpEncoder = new Encoding.DummyHttpEncoder();
}

View file

@ -290,5 +290,22 @@ namespace Roadie.Library.Tests
var d = input.ToSecondsFromMilliseconds();
Assert.Equal(shouldBe, d);
}
[Theory]
[InlineData("Song (ft. Joe)")]
[InlineData("Song (featuring Joe)")]
[InlineData("Song (feat. Joe)")]
public void StringHaveFeatureFragments(string input)
{
Assert.True(input.HasFeaturingFragments());
}
[Theory]
[InlineData("Future Feature")]
[InlineData("Feature Song")]
public void StringShouldNotHaveFeatureFragments(string input)
{
Assert.False(input.HasFeaturingFragments());
}
}
}

View file

@ -42,7 +42,7 @@ namespace Roadie.Library.Tests
configuration.GetSection("RoadieSettings").Bind(settings);
settings.ConnectionString = configuration.GetConnectionString("RoadieDatabaseConnection");
Configuration = settings;
CacheManager = new DictionaryCacheManager(Logger, new CachePolicy(TimeSpan.FromHours(4)));
CacheManager = new DictionaryCacheManager(Logger, new NewtonsoftCacheSerializer(Logger), new CachePolicy(TimeSpan.FromHours(4)));
var tagHelperLooper = new EventMessageLogger<ID3TagsHelper>();
tagHelperLooper.Messages += MessageLoggerMessages;
TagsHelper = new ID3TagsHelper(Configuration, CacheManager, tagHelperLooper);

View file

@ -45,7 +45,7 @@ namespace Roadie.Library.Tests
configuration.GetSection("RoadieSettings").Bind(settings);
settings.ConnectionString = configuration.GetConnectionString("RoadieDatabaseConnection");
Configuration = settings;
CacheManager = new DictionaryCacheManager(Logger, new CachePolicy(TimeSpan.FromHours(4)));
CacheManager = new DictionaryCacheManager(Logger, new NewtonsoftCacheSerializer(Logger), new CachePolicy(TimeSpan.FromHours(4)));
var tagHelperLooper = new EventMessageLogger<ID3TagsHelper>();
tagHelperLooper.Messages += MessageLoggerMessages;
TagsHelper = new ID3TagsHelper(Configuration, CacheManager, tagHelperLooper);

View file

@ -23,7 +23,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</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="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.2">

View file

@ -35,7 +35,7 @@ namespace Roadie.Library.Tests
IConfiguration configuration = configurationBuilder.Build();
configuration.GetSection("RoadieSettings").Bind(settings);
Configuration = settings;
CacheManager = new DictionaryCacheManager(Logger, new CachePolicy(TimeSpan.FromHours(4)));
CacheManager = new DictionaryCacheManager(Logger, new NewtonsoftCacheSerializer(Logger), new CachePolicy(TimeSpan.FromHours(4)));
HttpEncoder = new Encoding.DummyHttpEncoder();
}

View file

@ -1,5 +1,4 @@
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Threading.Tasks;
@ -10,19 +9,15 @@ namespace Roadie.Library.Caching
public const string SystemCacheRegionUrn = "urn:system";
protected readonly CachePolicy _defaultPolicy;
protected readonly JsonSerializerSettings _serializerSettings;
protected ILogger Logger { get; }
protected ICacheSerializer CacheSerializer { get; }
public CacheManagerBase(ILogger logger, CachePolicy defaultPolicy)
public CacheManagerBase(ILogger logger, ICacheSerializer cacheSerializer, CachePolicy defaultPolicy)
{
Logger = logger;
CacheSerializer = cacheSerializer;
_defaultPolicy = defaultPolicy;
_serializerSettings = new JsonSerializerSettings
{
ContractResolver = new IgnoreJsonAttributesResolver(),
Formatting = Formatting.Indented
};
}
public abstract bool Add<TCacheValue>(string key, TCacheValue value);
@ -55,24 +50,5 @@ namespace Roadie.Library.Caching
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; }
public DictionaryCacheManager(ILogger logger, CachePolicy defaultPolicy)
: base(logger, defaultPolicy)
public DictionaryCacheManager(ILogger logger, ICacheSerializer cacheSerializer, CachePolicy defaultPolicy)
: base(logger, cacheSerializer, defaultPolicy)
{
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;
public MemoryCacheManager(ILogger logger, CachePolicy defaultPolicy)
: base(logger, defaultPolicy)
public MemoryCacheManager(ILogger logger, ICacheSerializer cacheSerializer, CachePolicy defaultPolicy)
: base(logger, cacheSerializer, defaultPolicy)
{
_cache = new MemoryCache(new MemoryCacheOptions());
}

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());
public RedisCacheManager(ILogger logger, CachePolicy defaultPolicy)
: base(logger, defaultPolicy)
public RedisCacheManager(ILogger logger, ICacheSerializer cacheSerializer, CachePolicy defaultPolicy)
: base(logger, cacheSerializer, defaultPolicy)
{
}
@ -49,7 +49,7 @@ namespace Roadie.Library.Caching
public override bool Add<TCacheValue>(string key, TCacheValue value, string region, CachePolicy policy)
{
if (_doTraceLogging) Logger.LogTrace("Added [{0}], Region [{1}]", key, region);
return Redis.StringSet(key, Serialize(value));
return Redis.StringSet(key, CacheSerializer.Serialize(value));
}
public override void Clear()
@ -135,7 +135,7 @@ namespace Roadie.Library.Caching
private TOut Get<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 (_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 bool DoDetectFeatureFragments { get; set; }
public Processing()
{
DoAudioCleanup = true;
@ -55,6 +57,7 @@ namespace Roadie.Library.Configuration
DoParseFromLastFM = true;
DoParseFromMusicBrainz = true;
DoSaveEditsToTags = true;
DoDetectFeatureFragments = true;
MaximumArtistImagesToAdd = 12;
MaximumReleaseImagesToAdd = 12;

View file

@ -1,7 +1,5 @@
using CsvHelper;
using Newtonsoft.Json;
using Roadie.Library.Configuration;
using Roadie.Library.Enums;
using Roadie.Library.Extensions;
using Roadie.Library.Utility;
using System;
@ -14,6 +12,10 @@ namespace Roadie.Library.Data
{
public partial class Collection
{
public const string ArtistPosition = "artist";
public const string PositionPosition = "position";
public const string ReleasePosition = "release";
/// <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"
/// </summary>
@ -35,7 +37,10 @@ namespace Roadie.Library.Data
foreach (var pos in ListInCSVFormat.Split(','))
{
looper++;
if (pos.ToLower().Equals("artist")) _artistColumn = looper;
if (String.Equals(pos, ArtistPosition, StringComparison.OrdinalIgnoreCase))
{
_artistColumn = looper;
}
}
}
@ -70,7 +75,10 @@ namespace Roadie.Library.Data
foreach (var pos in ListInCSVFormat.Split(','))
{
looper++;
if (pos.ToLower().Equals("position")) _positionColumn = looper;
if (String.Equals(pos, PositionPosition, StringComparison.OrdinalIgnoreCase))
{
_positionColumn = looper;
}
}
}
@ -88,7 +96,10 @@ namespace Roadie.Library.Data
foreach (var pos in ListInCSVFormat.Split(','))
{
looper++;
if (pos.ToLower().Equals("release")) _releaseColumn = looper;
if (String.Equals(pos, ReleasePosition, StringComparison.OrdinalIgnoreCase))
{
_releaseColumn = looper;
}
}
}
@ -129,10 +140,7 @@ namespace Roadie.Library.Data
MissingFieldFound = null,
HasHeaderRecord = false
};
configuration.BadDataFound = context =>
{
Trace.WriteLine($"PositionArtistReleases: Bad data found on row '{context.RawRow}'", "Warning");
};
configuration.BadDataFound = context => Trace.WriteLine($"PositionArtistReleases: Bad data found on row '{context.RawRow}'", "Warning");
using (var csv = new CsvReader(sr, configuration))
{
while (csv.Read())
@ -160,6 +168,4 @@ namespace Roadie.Library.Data
return $"Id [{Id}], Name [{Name}], RoadieId [{RoadieId}]";
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,7 +6,11 @@ namespace Roadie.Library.Extensions
{
public static int? Or(this int? value, int? alternative)
{
if (!value.HasValue && !alternative.HasValue) return null;
if (!value.HasValue && !alternative.HasValue)
{
return null;
}
return value.HasValue ? value : alternative;
}

View file

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

View file

@ -6,7 +6,11 @@ namespace Roadie.Library.Extensions
{
public static string ToFileSize(this long? l)
{
if (!l.HasValue) return "0";
if (!l.HasValue)
{
return "0";
}
return string.Format(new FileSizeFormatProvider(), "{0:fs}", l);
}
}

View file

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

View file

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

View file

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

View file

@ -1,4 +1,5 @@
using Roadie.Library.Configuration;
using ImageMagick;
using Roadie.Library.Configuration;
using Roadie.Library.Enums;
using Roadie.Library.Extensions;
using Roadie.Library.MetaData.Audio;
@ -20,121 +21,123 @@ namespace Roadie.Library.Imaging
public static class ImageHelper
{
public static string ArtistImageFilename = "artist.jpg";
public static string ArtistSecondaryImageFilename = "artist {0}.jpg"; // Replace with counter of image
public static string ReleaseCoverFilename = "cover.jpg";
public static string ReleaseSecondaryImageFilename = "release {0}.jpg"; // Replace with counter of image
public static byte[] ConvertToJpegFormat(byte[] imageBytes)
{
if(imageBytes?.Any() != true)
{
return null;
}
try
{
using(var outStream = new MemoryStream())
{
IImageFormat imageFormat = null;
using(var image = SixLabors.ImageSharp.Image.Load(imageBytes, out imageFormat))
{
image.SaveAsJpeg(outStream);
}
return outStream.ToArray();
}
} catch(Exception ex)
{
Trace.WriteLine($"Error Converting Image to Jpg [{ ex.Message }]", "Warning");
}
return null;
}
/// <summary>
/// Only user avatars are GIF to allow for animation.
/// </summary>
public static byte[] ConvertToGifFormat(byte[] imageBytes)
{
if(imageBytes?.Any() != true)
if (imageBytes?.Any() != true)
{
return null;
}
try
{
using(var outStream = new MemoryStream())
using (var outStream = new MemoryStream())
{
IImageFormat imageFormat = null;
using(var image = SixLabors.ImageSharp.Image.Load(imageBytes, out imageFormat))
using (var image = SixLabors.ImageSharp.Image.Load(imageBytes, out imageFormat))
{
image.SaveAsGif(outStream);
}
return outStream.ToArray();
}
} catch(Exception ex)
}
catch (Exception ex)
{
Trace.WriteLine($"Error Converting Image to Gif [{ ex.Message }]", "Warning");
}
return null;
}
public static byte[] ConvertToJpegFormat(byte[] imageBytes)
{
if (imageBytes?.Any() != true)
{
return null;
}
try
{
return ConvertToJpegFormatViaSixLabors(imageBytes);
}
catch (Exception ex)
{
Trace.WriteLine($"Error Converting Image to Jpg via SixLabors [{ ex }]", "Warning");
}
try
{
return ConvertToJpegFormatViaMagick(imageBytes);
}
catch (Exception ex)
{
Trace.WriteLine($"Error Converting Image to Jpg via Magick [{ ex }]", "Warning");
}
return null;
}
public static byte[] ConvertToJpegFormatViaSixLabors(byte[] imageBytes)
{
using (var outStream = new MemoryStream())
{
IImageFormat imageFormat = null;
using (var image = SixLabors.ImageSharp.Image.Load(imageBytes, out imageFormat))
{
image.SaveAsJpeg(outStream);
}
return outStream.ToArray();
}
}
public static byte[] ConvertToJpegFormatViaMagick(byte[] imageBytes)
{
using (var image = new MagickImage(imageBytes))
{
image.Format = MagickFormat.Jpeg;
return image.ToByteArray();
}
}
public static IEnumerable<FileInfo> FindImagesByName(DirectoryInfo directory,
string name,
SearchOption folderSearchOptions = SearchOption.AllDirectories)
{
var result = new List<FileInfo>();
if(directory?.Exists != true || string.IsNullOrEmpty(name))
if (directory?.Exists != true || string.IsNullOrEmpty(name))
{
return result;
}
var imageFilesInFolder = ImageFilesInFolder(directory.FullName, folderSearchOptions);
if(imageFilesInFolder?.Any() != true)
if (imageFilesInFolder?.Any() != true)
{
return result;
if(imageFilesInFolder.Length > 0)
}
if (imageFilesInFolder.Length > 0)
{
name = name.ToAlphanumericName();
foreach(var imageFileInFolder in imageFilesInFolder)
foreach (var imageFileInFolder in imageFilesInFolder)
{
var image = new FileInfo(imageFileInFolder);
var filenameWithoutExtension = Path.GetFileName(imageFileInFolder).ToAlphanumericName();
var imageName = image.Name.ToAlphanumericName();
if(imageName.Equals(name) || filenameWithoutExtension.Equals(name))
if (imageName.Equals(name) || filenameWithoutExtension.Equals(name))
{
result.Add(image);
}
}
}
return result;
}
public static bool IsImageBetterQuality(string image1, string compareToImage)
{
if(string.IsNullOrEmpty(compareToImage))
{
return true;
}
try
{
if(string.IsNullOrEmpty(image1) || !File.Exists(image1))
{
return File.Exists(compareToImage);
}
using(var imageComparing = SixLabors.ImageSharp.Image.Load(image1))
{
using(var imageToCompare = SixLabors.ImageSharp.Image.Load(compareToImage))
{
// Generally a larger image is the preferred image
var isBigger = imageToCompare.Height > imageComparing.Height &&
imageToCompare.Width > imageComparing.Width;
if(isBigger)
{
return true;
}
}
}
} catch(Exception ex)
{
Trace.WriteLine($"Error IsImageBetterQuality Image Comparing [{ image1 }] to [{ compareToImage }], Error [{ ex }]",
"Warning");
}
return false;
}
public static IEnumerable<FileInfo> FindImageTypeInDirectory(DirectoryInfo directory,
ImageType type,
SearchOption folderSearchOptions = SearchOption.AllDirectories)
@ -149,10 +152,10 @@ namespace Roadie.Library.Imaging
{
return result;
}
foreach(var imageFile in imageFilesInFolder)
foreach (var imageFile in imageFilesInFolder)
{
var image = new FileInfo(imageFile);
switch(type)
switch (type)
{
case ImageType.Artist:
if (IsArtistImage(image))
@ -197,174 +200,21 @@ namespace Roadie.Library.Imaging
string[] patterns = null,
SearchOption options = SearchOption.TopDirectoryOnly)
{
if(!Directory.Exists(path))
if (!Directory.Exists(path))
{
return new string[0];
}
if(patterns == null || patterns.Length == 0)
if (patterns == null || patterns.Length == 0)
{
return Directory.GetFiles(path, "*", options);
}
if(patterns.Length == 1)
if (patterns.Length == 1)
{
return Directory.GetFiles(path, patterns[0], options);
}
return patterns.SelectMany(pattern => Directory.GetFiles(path, pattern, options)).Distinct().ToArray();
}
public static byte[] ImageDataFromUrl(string imageUrl)
{
if(!string.IsNullOrEmpty(imageUrl) && !imageUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
var dataString = imageUrl.Trim()
.Replace('-', '+')
.Replace("data:image/jpeg;base64,", "")
.Replace("data:image/bmp;base64,", "")
.Replace("data:image/gif;base64,", "")
.Replace("data:image/png;base64,", "");
return Convert.FromBase64String(dataString);
}
return null;
}
public static string[] ImageExtensions()
{ return new string[6] { "*.bmp", "*.jpeg", "*.jpe", "*.jpg", "*.png", "*.gif" }; }
public static string[] ImageFilesInFolder(string folder, SearchOption searchOption)
{ return GetFiles(folder, ImageExtensions(), searchOption); }
public static string[] ImageMimeTypes()
{ return new string[4] { "image/bmp", "image/jpeg", "image/png", "image/gif" }; }
public static ImageSearchResult ImageSearchResultForImageUrl(string imageUrl)
{
if(!WebHelper.IsStringUrl(imageUrl))
return null;
var result = new ImageSearchResult();
var imageBytes = WebHelper.BytesForImageUrl(imageUrl);
IImageFormat imageFormat = null;
using(var image = SixLabors.ImageSharp.Image.Load(imageBytes, out imageFormat))
{
result.Height = image.Height.ToString();
result.Width = image.Width.ToString();
result.MediaUrl = imageUrl;
}
return result;
}
public static bool IsArtistImage(FileInfo fileinfo)
{
if(fileinfo == null)
return false;
return Regex.IsMatch(fileinfo.Name,
@"(band|artist|group|photo)\.(jpg|jpeg|png|bmp|gif)",
RegexOptions.IgnoreCase);
}
public static bool IsArtistSecondaryImage(FileInfo fileinfo)
{
if(fileinfo == null)
return false;
return Regex.IsMatch(fileinfo.Name,
@"(artist_logo|logo|photo[-_\s]*[0-9]+|(artist[\s_-]+[0-9]+)|(band[\s_-]+[0-9]+))\.(jpg|jpeg|png|bmp|gif)",
RegexOptions.IgnoreCase);
}
public static bool IsLabelImage(FileInfo fileinfo)
{
if(fileinfo == null)
return false;
return Regex.IsMatch(fileinfo.Name,
@"(label|recordlabel|record_label)\.(jpg|jpeg|png|bmp|gif)",
RegexOptions.IgnoreCase);
}
public static bool IsReleaseImage(FileInfo fileinfo, string releaseName = null)
{
if(fileinfo == null)
return false;
return Regex.IsMatch(fileinfo.Name,
@"((f[-_\s]*[0-9]*)|00|art|big[art]*|cover|cvr|folder|release|front[-_\s]*)\.(jpg|jpeg|png|bmp|gif)",
RegexOptions.IgnoreCase);
}
public static bool IsReleaseSecondaryImage(FileInfo fileinfo)
{
if(fileinfo == null)
return false;
return Regex.IsMatch(fileinfo.Name,
@"((img[\s-_]*[0-9]*[\s-_]*[0-9]*)|(book[let]*[#-_\s(]*[0-9]*-*[0-9]*(\))*)|(encartes[-_\s]*[(]*[0-9]*[)]*)|sc[an]*(.)?[0-9]*|matrix(.)?[0-9]*|(cover[\s_-]*[0-9]+)|back|traycard|jewel case|disc|(.*)[in]*side(.*)|in([side|lay|let|site])*[0-9]*|digipack.?\[?\(?([0-9]*)\]?\)?|cd.?\[?\(?([0-9]*)\]?\)?|(release[\s_-]+[0-9]+))\.(jpg|jpeg|png|bmp|gif)",
RegexOptions.IgnoreCase);
}
public static byte[] ResizeImage(byte[] imageBytes, int width, int height) => ImageHelper.ResizeImage(imageBytes,
width,
height,
false)
.Item2;
/// <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>
/// Get image data from all sources for either fileanme or MetaData
/// </summary>
@ -380,6 +230,29 @@ namespace Roadie.Library.Imaging
return ImageForFilename(configuration, filename);
}
public static byte[] ImageDataFromUrl(string imageUrl)
{
if (!string.IsNullOrEmpty(imageUrl) && !imageUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
var dataString = imageUrl.Trim()
.Replace('-', '+')
.Replace("data:image/jpeg;base64,", string.Empty)
.Replace("data:image/bmp;base64,", string.Empty)
.Replace("data:image/gif;base64,", string.Empty)
.Replace("data:image/png;base64,", string.Empty)
.Replace("data:image/webp;base64,", string.Empty);
return Convert.FromBase64String(dataString);
}
return null;
}
public static string[] ImageExtensions()
{ return new string[7] { "*.bmp", "*.jpeg", "*.jpe", "*.jpg", "*.png", "*.gif", "*.webp" }; }
public static string[] ImageFilesInFolder(string folder, SearchOption searchOption)
{ return GetFiles(folder, ImageExtensions(), searchOption); }
/// <summary>
/// Does image exist with the same filename
/// </summary>
@ -389,7 +262,7 @@ namespace Roadie.Library.Imaging
{
AudioMetaDataImage imageMetaData = null;
if(string.IsNullOrEmpty(filename))
if (string.IsNullOrEmpty(filename))
{
return imageMetaData;
}
@ -397,9 +270,9 @@ namespace Roadie.Library.Imaging
{
var fileInfo = new FileInfo(filename);
var ReleaseCover = Path.ChangeExtension(filename, "jpg");
if(File.Exists(ReleaseCover))
if (File.Exists(ReleaseCover))
{
using(var processor = new ImageProcessor(configuration))
using (var processor = new ImageProcessor(configuration))
{
imageMetaData = new AudioMetaDataImage
{
@ -408,23 +281,31 @@ namespace Roadie.Library.Imaging
MimeType = Library.Processors.FileProcessor.DetermineFileType(fileInfo)
};
}
} else
}
else
{
// Is there a picture in filename folder (for the Release)
var pictures = fileInfo.Directory.GetFiles("*.jpg");
var tagImages = new List<AudioMetaDataImage>();
if(pictures?.Any() == true)
if (pictures?.Any() == true)
{
// See if there is a "cover" or "front" jpg file if so use it
FileInfo picture = Array.Find(pictures,
x => x.Name.Equals("cover", StringComparison.OrdinalIgnoreCase));
if(picture == null)
if (picture == null)
{
picture = Array.Find(pictures,
x => x.Name.Equals("front", StringComparison.OrdinalIgnoreCase));
if(picture == null)
x => x.Name.Equals("front", StringComparison.OrdinalIgnoreCase));
}
if (picture == null)
{
picture = pictures[0];
if(picture != null)
using(var processor = new ImageProcessor(configuration))
}
if (picture != null)
{
using (var processor = new ImageProcessor(configuration))
{
imageMetaData = new AudioMetaDataImage
{
@ -433,11 +314,14 @@ namespace Roadie.Library.Imaging
MimeType = Library.Processors.FileProcessor.DetermineFileType(picture)
};
}
}
}
}
} catch(FileNotFoundException)
}
catch (FileNotFoundException)
{
} catch(Exception ex)
}
catch (Exception ex)
{
Trace.WriteLine(ex, ex.Serialize());
}
@ -445,58 +329,132 @@ namespace Roadie.Library.Imaging
return imageMetaData;
}
public static models.Image MakeThumbnailImage(IRoadieSettings configuration,
IHttpContext httpContext,
Guid id,
string type,
int? width = null,
int? height = null,
bool includeCachebuster = false)
public static string[] ImageMimeTypes()
{ return new string[4] { "image/bmp", "image/jpeg", "image/png", "image/gif" }; }
public static ImageSearchResult ImageSearchResultForImageUrl(string imageUrl)
{
return MakeImage(configuration,
httpContext,
id,
type,
width ?? configuration.ThumbnailImageSize.Width,
height ?? configuration.ThumbnailImageSize.Height,
null,
includeCachebuster);
if (!WebHelper.IsStringUrl(imageUrl))
{
return null;
}
var result = new ImageSearchResult();
var imageBytes = WebHelper.BytesForImageUrl(imageUrl);
IImageFormat imageFormat = null;
using (var image = SixLabors.ImageSharp.Image.Load(imageBytes, out imageFormat))
{
result.Height = image.Height.ToString();
result.Width = image.Width.ToString();
result.MediaUrl = imageUrl;
}
return result;
}
public static models.Image MakeNewImage(IHttpContext httpContext, string type)
{ return new models.Image($"{httpContext.ImageBaseUrl}/{type}.jpg", null, null); }
public static bool IsArtistImage(FileInfo fileinfo)
{
if (fileinfo == null)
{
return false;
}
public static models.Image MakePlaylistThumbnailImage(IRoadieSettings configuration,
IHttpContext httpContext,
Guid id)
{ return MakeThumbnailImage(configuration, httpContext, id, "playlist"); }
return Regex.IsMatch(fileinfo.Name,
@"(band|artist|group|photo)\.(jpg|jpeg|png|bmp|gif)",
RegexOptions.IgnoreCase);
}
public static models.Image MakeReleaseThumbnailImage(IRoadieSettings configuration,
IHttpContext httpContext,
Guid id)
{ return MakeThumbnailImage(configuration, httpContext, id, "release"); }
public static bool IsArtistSecondaryImage(FileInfo fileinfo)
{
if (fileinfo == null)
{
return false;
}
public static models.Image MakeTrackThumbnailImage(IRoadieSettings configuration,
IHttpContext httpContext,
Guid id)
{ return MakeThumbnailImage(configuration, httpContext, id, "track"); }
return Regex.IsMatch(fileinfo.Name,
@"(artist_logo|logo|photo[-_\s]*[0-9]+|(artist[\s_-]+[0-9]+)|(band[\s_-]+[0-9]+))\.(jpg|jpeg|png|bmp|gif)",
RegexOptions.IgnoreCase);
}
public static models.Image MakeUserThumbnailImage(IRoadieSettings configuration,
IHttpContext httpContext,
Guid id)
{ return MakeThumbnailImage(configuration, httpContext, id, "user"); }
public static bool IsImageBetterQuality(string image1, string compareToImage)
{
if (string.IsNullOrEmpty(compareToImage))
{
return true;
}
try
{
if (string.IsNullOrEmpty(image1) || !File.Exists(image1))
{
return File.Exists(compareToImage);
}
using (var imageComparing = SixLabors.ImageSharp.Image.Load(image1))
{
using (var imageToCompare = SixLabors.ImageSharp.Image.Load(compareToImage))
{
// Generally a larger image is the preferred image
var isBigger = imageToCompare.Height > imageComparing.Height &&
imageToCompare.Width > imageComparing.Width;
if (isBigger)
{
return true;
}
}
}
}
catch (Exception ex)
{
Trace.WriteLine($"Error IsImageBetterQuality Image Comparing [{ image1 }] to [{ compareToImage }], Error [{ ex }]",
"Warning");
}
return false;
}
public static models.Image MakeLabelThumbnailImage(IRoadieSettings configuration,
IHttpContext httpContext,
Guid id)
{ return MakeThumbnailImage(configuration, httpContext, id, "label"); }
public static bool IsLabelImage(FileInfo fileinfo)
{
if (fileinfo == null)
{
return false;
}
return Regex.IsMatch(fileinfo.Name,
@"(label|recordlabel|record_label)\.(jpg|jpeg|png|bmp|gif)",
RegexOptions.IgnoreCase);
}
public static bool IsReleaseImage(FileInfo fileinfo, string releaseName = null)
{
if (fileinfo == null)
{
return false;
}
return Regex.IsMatch(fileinfo.Name,
@"((f[-_\s]*[0-9]*)|00|art|big[art]*|cover|cvr|folder|release|front[-_\s]*)\.(jpg|jpeg|png|bmp|gif)",
RegexOptions.IgnoreCase);
}
public static bool IsReleaseSecondaryImage(FileInfo fileinfo)
{
if (fileinfo == null)
{
return false;
}
return Regex.IsMatch(fileinfo.Name,
@"((img[\s-_]*[0-9]*[\s-_]*[0-9]*)|(book[let]*[#-_\s(]*[0-9]*-*[0-9]*(\))*)|(encartes[-_\s]*[(]*[0-9]*[)]*)|sc[an]*(.)?[0-9]*|matrix(.)?[0-9]*|(cover[\s_-]*[0-9]+)|back|traycard|jewel case|disc|(.*)[in]*side(.*)|in([side|lay|let|site])*[0-9]*|digipack.?\[?\(?([0-9]*)\]?\)?|cd.?\[?\(?([0-9]*)\]?\)?|(release[\s_-]+[0-9]+))\.(jpg|jpeg|png|bmp|gif)",
RegexOptions.IgnoreCase);
}
public static models.Image MakeArtistThumbnailImage(IRoadieSettings configuration,
IHttpContext httpContext,
Guid? id)
{
if(!id.HasValue)
if (!id.HasValue)
{
return null;
}
return MakeThumbnailImage(configuration, httpContext, id.Value, "artist");
}
@ -522,7 +480,7 @@ namespace Roadie.Library.Imaging
int imageId,
string caption = null)
{
if(type == ImageType.ArtistSecondary)
if (type == ImageType.ArtistSecondary)
{
return new models.Image($"{httpContext.ImageBaseUrl}/artist-secondary/{id}/{imageId}",
caption,
@ -538,8 +496,12 @@ namespace Roadie.Library.Imaging
Guid id)
{ return MakeThumbnailImage(configuration, httpContext, id, "genre"); }
public static models.Image MakeUnknownImage(IHttpContext httpContext, string unknownType = "unknown")
{ return new models.Image($"{httpContext.ImageBaseUrl}/{ unknownType }.jpg"); }
public static models.Image MakeImage(IRoadieSettings configuration,
IHttpContext httpContext,
Guid id,
string type,
IImageSize imageSize)
{ return MakeImage(configuration, httpContext, id, type, imageSize.Width, imageSize.Height); }
public static models.Image MakeImage(IRoadieSettings configuration,
IHttpContext httpContext,
@ -554,13 +516,6 @@ namespace Roadie.Library.Imaging
$"{httpContext.ImageBaseUrl}/{id}/{configuration.SmallImageSize.Width}/{configuration.SmallImageSize.Height}");
}
public static models.Image MakeImage(IRoadieSettings configuration,
IHttpContext httpContext,
Guid id,
string type,
IImageSize imageSize)
{ return MakeImage(configuration, httpContext, id, type, imageSize.Width, imageSize.Height); }
public static models.Image MakeImage(IRoadieSettings configuration,
IHttpContext httpContext,
Guid id,
@ -570,14 +525,135 @@ namespace Roadie.Library.Imaging
string caption = null,
bool includeCachebuster = false)
{
if(width.HasValue &&
if (width.HasValue &&
height.HasValue &&
(width.Value != configuration.ThumbnailImageSize.Width ||
height.Value != configuration.ThumbnailImageSize.Height))
{
return new models.Image($"{httpContext.ImageBaseUrl}/{type}/{id}/{width}/{height}/{(includeCachebuster ? DateTime.UtcNow.Ticks.ToString() : string.Empty)}",
caption,
$"{httpContext.ImageBaseUrl}/{type}/{id}/{configuration.ThumbnailImageSize.Width}/{configuration.ThumbnailImageSize.Height}");
caption,
$"{httpContext.ImageBaseUrl}/{type}/{id}/{configuration.ThumbnailImageSize.Width}/{configuration.ThumbnailImageSize.Height}");
}
return new models.Image($"{httpContext.ImageBaseUrl}/{type}/{id}", caption, null);
}
public static models.Image MakeLabelThumbnailImage(IRoadieSettings configuration,
IHttpContext httpContext,
Guid id)
{ return MakeThumbnailImage(configuration, httpContext, id, "label"); }
public static models.Image MakeNewImage(IHttpContext httpContext, string type)
{ return new models.Image($"{httpContext.ImageBaseUrl}/{type}.jpg", null, null); }
public static models.Image MakePlaylistThumbnailImage(IRoadieSettings configuration,
IHttpContext httpContext,
Guid id)
{ return MakeThumbnailImage(configuration, httpContext, id, "playlist"); }
public static models.Image MakeReleaseThumbnailImage(IRoadieSettings configuration,
IHttpContext httpContext,
Guid id)
{ return MakeThumbnailImage(configuration, httpContext, id, "release"); }
public static models.Image MakeThumbnailImage(IRoadieSettings configuration,
IHttpContext httpContext,
Guid id,
string type,
int? width = null,
int? height = null,
bool includeCachebuster = false)
{
return MakeImage(configuration,
httpContext,
id,
type,
width ?? configuration.ThumbnailImageSize.Width,
height ?? configuration.ThumbnailImageSize.Height,
null,
includeCachebuster);
}
public static models.Image MakeTrackThumbnailImage(IRoadieSettings configuration,
IHttpContext httpContext,
Guid id)
{ return MakeThumbnailImage(configuration, httpContext, id, "track"); }
public static models.Image MakeUnknownImage(IHttpContext httpContext, string unknownType = "unknown")
{ return new models.Image($"{httpContext.ImageBaseUrl}/{ unknownType }.jpg"); }
public static models.Image MakeUserThumbnailImage(IRoadieSettings configuration,
IHttpContext httpContext,
Guid id)
{ return MakeThumbnailImage(configuration, httpContext, id, "user"); }
public static byte[] ResizeImage(byte[] imageBytes, int width, int height) => ImageHelper.ResizeImage(imageBytes,
width,
height,
false)
.Item2;
/// <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 Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Roadie.Library.Caching;
using Roadie.Library.Configuration;
using Roadie.Library.Enums;
@ -21,6 +20,7 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Text.Json;
namespace Roadie.Library.Inspect
{
@ -28,7 +28,13 @@ namespace Roadie.Library.Inspect
{
private const string Salt = "6856F2EE-5965-4345-884B-2CCA457AAF59";
private IRoadieSettings Configuration { get; }
private ILogger Logger => MessageLogger as ILogger;
private IEventMessageLogger MessageLogger { get; }
private ID3TagsHelper TagsHelper { get; }
private IEnumerable<IInspectorDirectoryPlugin> _directoryPlugins;
private IEnumerable<IInspectorFilePlugin> _filePlugins;
public DictionaryCacheManager CacheManager { get; }
@ -47,6 +53,7 @@ namespace Roadie.Library.Inspect
.SelectMany(s => s.GetTypes())
.Where(p => type.IsAssignableFrom(p));
foreach (var t in types)
{
if (t.GetInterface("IInspectorDirectoryPlugin") != null && !t.IsAbstract && !t.IsInterface)
{
var plugin = Activator.CreateInstance(t, Configuration, CacheManager, Logger, TagsHelper) as IInspectorDirectoryPlugin;
@ -59,6 +66,7 @@ namespace Roadie.Library.Inspect
Console.WriteLine($"╠╣ Not Loading Disabled Plugin [{plugin.Description}]");
}
}
}
}
catch (Exception ex)
{
@ -113,18 +121,8 @@ namespace Roadie.Library.Inspect
}
}
private IRoadieSettings Configuration { get; }
private ILogger Logger => MessageLogger as ILogger;
private IEventMessageLogger MessageLogger { get; }
private ID3TagsHelper TagsHelper { get; }
public Inspector()
{
Console.WriteLine("Roadie Media Inspector");
MessageLogger = new EventMessageLogger<Inspector>();
MessageLogger.Messages += MessageLogger_Messages;
@ -135,31 +133,141 @@ namespace Roadie.Library.Inspect
configuration.GetSection("RoadieSettings").Bind(settings);
settings.ConnectionString = configuration.GetConnectionString("RoadieDatabaseConnection");
Configuration = settings;
CacheManager = new DictionaryCacheManager(Logger, new CachePolicy(TimeSpan.FromHours(4)));
CacheManager = new DictionaryCacheManager(Logger, new NewtonsoftCacheSerializer(Logger), new CachePolicy(TimeSpan.FromHours(4)));
var tagHelperLooper = new EventMessageLogger<ID3TagsHelper>();
tagHelperLooper.Messages += MessageLogger_Messages;
TagsHelper = new ID3TagsHelper(Configuration, CacheManager, tagHelperLooper);
}
public static string ArtistInspectorToken(AudioMetaData metaData) => ToToken(metaData.Artist);
public static string ReleaseInspectorToken(AudioMetaData metaData) => ToToken(metaData.Artist + metaData.Release);
public static string ToToken(string input)
private void InspectImage(bool isReadOnly, bool doCopy, string dest, string subdirectory, FileInfo image)
{
var hashids = new Hashids(Salt);
var numbers = 0;
var bytes = System.Text.Encoding.ASCII.GetBytes(input);
var looper = bytes.Length / 4;
for (var i = 0; i < looper; i++)
if (!image.Exists)
{
numbers += BitConverter.ToInt32(bytes, i * 4);
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"╟ ■ InspectImage: Image Not Found [{image.FullName}]");
Console.ResetColor();
return;
}
if (numbers < 0) numbers *= -1;
return hashids.Encode(numbers);
Console.WriteLine($"╟─ Inspecting Image [{image.FullName}]");
var newImageFolder = new DirectoryInfo(Path.Combine(dest, subdirectory));
if (!newImageFolder.Exists)
{
newImageFolder.Create();
}
var newImagePath = Path.Combine(dest, subdirectory, image.Name);
if (image.FullName != newImagePath)
{
var looper = 0;
while (File.Exists(newImagePath))
{
looper++;
newImagePath = Path.Combine(dest, subdirectory, looper.ToString("00"), image.Name);
}
if (isReadOnly)
{
Console.WriteLine($"╟ 🔒 Read Only Mode: Would be [{(doCopy ? "Copied" : "Moved")}] to [{newImagePath}]");
}
else
{
try
{
if (!doCopy)
{
image.MoveTo(newImagePath);
}
else
{
image.CopyTo(newImagePath, true);
}
Console.ForegroundColor = ConsoleColor.DarkYellow;
Console.WriteLine($"╠═ 🚛 {(doCopy ? "Copied" : "Moved")} Image File to [{newImagePath}]");
}
catch (Exception ex)
{
Logger.LogError(ex);
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"📛 Error file [{image.FullName}], newImagePath [{newImagePath}], Exception: [{ex}]");
}
}
}
Console.ResetColor();
}
private void MessageLogger_Messages(object sender, EventMessage e)
{
Console.WriteLine($"Log Level [{e.Level}] Log Message [{e.Message}] ");
var message = e.Message;
switch (e.Level)
{
case LogLevel.Trace:
Logger.LogTrace(message);
break;
case LogLevel.Debug:
Logger.LogDebug(message);
break;
case LogLevel.Information:
Logger.LogInformation(message);
break;
case LogLevel.Warning:
Logger.LogWarning(message);
break;
case LogLevel.Critical:
Logger.LogCritical(message);
break;
}
}
private string RunScript(string scriptFilename, bool doCopy, bool isReadOnly, string directoryToInspect, string dest)
{
if (string.IsNullOrEmpty(scriptFilename))
{
return null;
}
try
{
if (!File.Exists(scriptFilename))
{
Console.WriteLine($"Script Not Found: [{ scriptFilename }]");
return null;
}
Console.WriteLine($"Running Script: [{ scriptFilename }]");
var script = File.ReadAllText(scriptFilename);
using (var ps = PowerShell.Create())
{
var r = string.Empty;
var results = ps.AddScript(script)
.AddParameter("DoCopy", doCopy)
.AddParameter("IsReadOnly", isReadOnly)
.AddParameter("DirectoryToInspect", directoryToInspect)
.AddParameter("Dest", dest)
.Invoke();
foreach (var result in results)
{
r += result + Environment.NewLine;
}
return r;
}
}
catch (Exception ex)
{
Console.WriteLine($"📛 Error with Script File [{scriptFilename}], Error [{ex}] ");
}
return null;
}
public static string ArtistInspectorToken(AudioMetaData metaData) => ToToken(metaData.Artist);
public void Inspect(bool doCopy, bool isReadOnly, string directoryToInspect, string destination, bool dontAppendSubFolder, bool dontDeleteEmptyFolders, bool dontRunPreScripts)
{
Configuration.Inspector.IsInReadOnlyMode = isReadOnly;
@ -171,13 +279,32 @@ namespace Roadie.Library.Inspect
Trace.Listeners.Add(new LoggingTraceListener());
Console.ForegroundColor = ConsoleColor.Blue;
Console.WriteLine("");
Console.WriteLine(" ▄▄▄ ▄▄▄· ·▄▄▄▄ ▪ ▄▄▄ . • ▌ ▄ ·. ▄▄▄ .·▄▄▄▄ ▪ ▄▄▄· ▪ ▐ ▄ .▄▄ · ▄▄▄·▄▄▄ . ▄▄· ▄▄▄▄▄ ▄▄▄ ");
Console.WriteLine(" ▀▄ █·▪ ▐█ ▀█ ██▪ ██ ██ ▀▄.▀· ·██ ▐███▪▀▄.▀·██▪ ██ ██ ▐█ ▀█ ██ •█▌▐█▐█ ▀. ▐█ ▄█▀▄.▀·▐█ ▌▪•██ ▪ ▀▄ █·");
Console.WriteLine(" ▐▀▀▄ ▄█▀▄ ▄█▀▀█ ▐█· ▐█▌▐█·▐▀▀▪▄ ▐█ ▌▐▌▐█·▐▀▀▪▄▐█· ▐█▌▐█·▄█▀▀█ ▐█·▐█▐▐▌▄▀▀▀█▄ ██▀·▐▀▀▪▄██ ▄▄ ▐█.▪ ▄█▀▄ ▐▀▀▄ ");
Console.WriteLine(" ▐█•█▌▐█▌.▐▌▐█ ▪▐▌██. ██ ▐█▌▐█▄▄▌ ██ ██▌▐█▌▐█▄▄▌██. ██ ▐█▌▐█ ▪▐▌ ▐█▌██▐█▌▐█▄▪▐█▐█▪·•▐█▄▄▌▐███▌ ▐█▌·▐█▌.▐▌▐█•█▌");
Console.WriteLine(" .▀ ▀ ▀█▄▀▪ ▀ ▀ ▀▀▀▀▀• ▀▀▀ ▀▀▀ ▀▀ █▪▀▀▀ ▀▀▀ ▀▀▀▀▀• ▀▀▀ ▀ ▀ ▀▀▀▀▀ █▪ ▀▀▀▀ .▀ ▀▀▀ ·▀▀▀ ▀▀▀ ▀█▄▀▪.▀ ▀");
Console.WriteLine("");
Console.ResetColor();
Console.BackgroundColor = ConsoleColor.White;
Console.ForegroundColor = ConsoleColor.Blue;
Console.WriteLine($"✨ Inspector Start, UTC [{DateTime.UtcNow.ToString("s")}]");
Console.ResetColor();
if (!Directory.Exists(directoryToInspect))
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"📛 Folder To Inspect [{ directoryToInspect }] is not found.");
Console.ResetColor();
return;
}
string scriptResult = null;
// Run PreInspect script
dontRunPreScripts = File.Exists(Configuration.Processing.PreInspectScript) && dontRunPreScripts;
if (dontRunPreScripts)
{
Console.BackgroundColor = ConsoleColor.Blue;
@ -198,7 +325,10 @@ namespace Roadie.Library.Inspect
}
// Create a new destination subfolder for each Inspector run by Current timestamp
var dest = Path.Combine(destination, DateTime.UtcNow.ToString("yyyyMMddHHmm"));
if (isReadOnly || dontAppendSubFolder) dest = destination;
if (isReadOnly || dontAppendSubFolder)
{
dest = destination;
}
// Get all the directorys in the directory
var directoryDirectories = Directory.GetDirectories(directoryToInspect, "*.*", SearchOption.AllDirectories);
var directories = new List<string>
@ -239,7 +369,7 @@ namespace Roadie.Library.Inspect
var pluginResult = plugin.Process(directoryInfo);
if (!pluginResult.IsSuccess)
{
Console.WriteLine($"📛 Plugin Failed: Error [{JsonConvert.SerializeObject(pluginResult)}]");
Console.WriteLine($"📛 Plugin Failed: Error [{JsonSerializer.Serialize(pluginResult)}]");
return;
}
if (!string.IsNullOrEmpty(pluginResult.Data))
@ -261,7 +391,11 @@ namespace Roadie.Library.Inspect
Console.WriteLine($"╟─ 🎵 Inspecting [{fileInfo.FullName}]");
var tagLib = TagsHelper.MetaDataForFile(fileInfo.FullName, true);
Console.ForegroundColor = ConsoleColor.Cyan;
if (!tagLib?.IsSuccess ?? false) Console.ForegroundColor = ConsoleColor.DarkYellow;
if (!tagLib?.IsSuccess ?? false)
{
Console.ForegroundColor = ConsoleColor.DarkYellow;
}
Console.WriteLine($"╟ (Pre ) : {tagLib.Data}");
Console.ResetColor();
tagLib.Data.Filename = fileInfo.FullName;
@ -270,7 +404,7 @@ namespace Roadie.Library.Inspect
{
Console.ForegroundColor = ConsoleColor.DarkYellow;
Console.WriteLine($"╟ ❗ INVALID: Missing: {ID3TagsHelper.DetermineMissingRequiredMetaData(originalMetaData)}");
Console.WriteLine($"╟ [{JsonConvert.SerializeObject(tagLib, Newtonsoft.Json.Formatting.Indented)}]");
Console.WriteLine($"╟ [{JsonSerializer.Serialize(tagLib, new JsonSerializerOptions { WriteIndented = true })}]");
Console.ResetColor();
}
@ -283,7 +417,7 @@ namespace Roadie.Library.Inspect
if (!pluginResult.IsSuccess)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"📛 Plugin Failed: Error [{JsonConvert.SerializeObject(pluginResult)}]");
Console.WriteLine($"📛 Plugin Failed: Error [{JsonSerializer.Serialize(pluginResult)}]");
Console.ResetColor();
return;
}
@ -421,7 +555,11 @@ namespace Roadie.Library.Inspect
{
if (fileInfo.FullName != newPath)
{
if (File.Exists(newPath)) File.Delete(newPath);
if (File.Exists(newPath))
{
File.Delete(newPath);
}
fileInfo.MoveTo(newPath);
}
}
@ -453,12 +591,14 @@ namespace Roadie.Library.Inspect
var pluginResult = plugin.Process(directoryInfo);
if (!pluginResult.IsSuccess)
{
Console.WriteLine($"📛 Plugin Failed: Error [{JsonConvert.SerializeObject(pluginResult)}]");
Console.WriteLine($"📛 Plugin Failed: Error [{JsonSerializer.Serialize(pluginResult)}]");
return;
}
if (!string.IsNullOrEmpty(pluginResult.Data))
{
Console.WriteLine($"╠╣ Directory Plugin Message: {pluginResult.Data}");
}
}
}
@ -469,7 +609,7 @@ namespace Roadie.Library.Inspect
catch (Exception ex)
{
Logger.LogError(ex);
Console.WriteLine("📛 Exception: " + ex);
Console.WriteLine($"📛 Exception: {ex}");
}
if (!dontDeleteEmptyFolders)
@ -496,120 +636,24 @@ namespace Roadie.Library.Inspect
}
}
private void InspectImage(bool isReadOnly, bool doCopy, string dest, string subdirectory, FileInfo image)
public static string ReleaseInspectorToken(AudioMetaData metaData) => ToToken(metaData.Artist + metaData.Release);
public static string ToToken(string input)
{
if (!image.Exists)
var hashids = new Hashids(Salt);
var numbers = 0;
var bytes = System.Text.Encoding.ASCII.GetBytes(input);
var looper = bytes.Length / 4;
for (var i = 0; i < looper; i++)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"╟ ■ InspectImage: Image Not Found [{image.FullName}]");
Console.ResetColor();
return;
numbers += BitConverter.ToInt32(bytes, i * 4);
}
if (numbers < 0)
{
numbers *= -1;
}
Console.WriteLine($"╟─ Inspecting Image [{image.FullName}]");
var newImageFolder = new DirectoryInfo(Path.Combine(dest, subdirectory));
if (!newImageFolder.Exists) newImageFolder.Create();
var newImagePath = Path.Combine(dest, subdirectory, image.Name);
if (image.FullName != newImagePath)
{
var looper = 0;
while (File.Exists(newImagePath))
{
looper++;
newImagePath = Path.Combine(dest, subdirectory, looper.ToString("00"), image.Name);
}
if (isReadOnly)
{
Console.WriteLine($"╟ 🔒 Read Only Mode: Would be [{(doCopy ? "Copied" : "Moved")}] to [{newImagePath}]");
}
else
{
try
{
if (!doCopy)
{
image.MoveTo(newImagePath);
}
else
{
image.CopyTo(newImagePath, true);
}
Console.ForegroundColor = ConsoleColor.DarkYellow;
Console.WriteLine($"╠═ 🚛 {(doCopy ? "Copied" : "Moved")} Image File to [{newImagePath}]");
}
catch (Exception ex)
{
Logger.LogError(ex);
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"📛 Error file [{image.FullName}], newImagePath [{newImagePath}], Exception: [{ex}]");
}
}
}
Console.ResetColor();
}
private void MessageLogger_Messages(object sender, EventMessage e)
{
Console.WriteLine($"Log Level [{e.Level}] Log Message [{e.Message}] ");
var message = e.Message;
switch (e.Level)
{
case LogLevel.Trace:
Logger.LogTrace(message);
break;
case LogLevel.Debug:
Logger.LogDebug(message);
break;
case LogLevel.Information:
Logger.LogInformation(message);
break;
case LogLevel.Warning:
Logger.LogWarning(message);
break;
case LogLevel.Critical:
Logger.LogCritical(message);
break;
}
}
private string RunScript(string scriptFilename, bool doCopy, bool isReadOnly, string directoryToInspect, string dest)
{
if (string.IsNullOrEmpty(scriptFilename))
{
return null;
}
try
{
Console.WriteLine($"Running Script: [{ scriptFilename }]");
var script = File.ReadAllText(scriptFilename);
using (var ps = PowerShell.Create())
{
var r = string.Empty;
var results = ps.AddScript(script)
.AddParameter("DoCopy", doCopy)
.AddParameter("IsReadOnly", isReadOnly)
.AddParameter("DirectoryToInspect", directoryToInspect)
.AddParameter("Dest", dest)
.Invoke();
foreach (var result in results)
{
r += result + Environment.NewLine;
}
return r;
}
}
catch (Exception ex)
{
Console.WriteLine($"📛 Error with Script File [{scriptFilename}], Error [{ex}] ");
}
return null;
return hashids.Encode(numbers);
}
}

View file

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

View file

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

View file

@ -39,7 +39,10 @@ namespace Roadie.Library.Inspect.Plugins.Directory
Console.WriteLine(
$"╟ Setting Release to [{release}], was [{metaData.Release}] on file [{metaData.FileInfo.Name}");
metaData.Release = release;
if (!Configuration.Inspector.IsInReadOnlyMode) TagsHelper.WriteTags(metaData, metaData.Filename);
if (!Configuration.Inspector.IsInReadOnlyMode)
{
TagsHelper.WriteTags(metaData, metaData.Filename);
}
}
data = $"Found [{found}] files, Modified [{modified}] files";

View file

@ -38,7 +38,10 @@ namespace Roadie.Library.Inspect.Plugins.Directory
Console.WriteLine(
$"╟ Setting Year to [{year}], was [{metaData.Year}] on file [{metaData.FileInfo.Name}");
metaData.Year = year;
if (!Configuration.Inspector.IsInReadOnlyMode) TagsHelper.WriteTags(metaData, metaData.Filename);
if (!Configuration.Inspector.IsInReadOnlyMode)
{
TagsHelper.WriteTags(metaData, metaData.Filename);
}
}
data = $"Found [{found}] files, Modified [{modified}] files";

View file

@ -5,8 +5,11 @@ namespace Roadie.Library.Inspect.Plugins.Directory
public interface IInspectorDirectoryPlugin
{
string Description { get; }
bool IsEnabled { get; }
bool IsPostProcessingPlugin { get; }
int Order { get; }
OperationResult<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.Configuration;
using Roadie.Library.Extensions;
@ -14,16 +15,15 @@ namespace Roadie.Library.Inspect.Plugins.File
public override int Order => 5;
public CleanUpArtists(IRoadieSettings configuration, ICacheManager cacheManager, ILogger logger,
IID3TagsHelper tagsHelper)
public CleanUpArtists(IRoadieSettings configuration, ICacheManager cacheManager, ILogger logger, IID3TagsHelper tagsHelper)
: base(configuration, cacheManager, logger, tagsHelper)
{
}
public string CleanArtist(string artist, string trackArtist = null)
{
artist = artist ?? string.Empty;
trackArtist = trackArtist ?? string.Empty;
artist ??= string.Empty;
trackArtist ??= string.Empty;
var splitCharacter = AudioMetaData.ArtistSplitCharacter.ToString();
// Replace seperators with proper split character
@ -39,12 +39,21 @@ namespace Roadie.Library.Inspect.Plugins.File
}
if (!string.IsNullOrEmpty(result) && !string.IsNullOrEmpty(trackArtist))
{
result = result.Replace(splitCharacter + trackArtist + splitCharacter, "", StringComparison.OrdinalIgnoreCase);
result = result.Replace(trackArtist + splitCharacter, "", StringComparison.OrdinalIgnoreCase);
result = result.Replace(splitCharacter + trackArtist, "", StringComparison.OrdinalIgnoreCase);
result = result.Replace(trackArtist, "", StringComparison.OrdinalIgnoreCase);
result = result.Replace(splitCharacter + trackArtist + splitCharacter, string.Empty, StringComparison.OrdinalIgnoreCase);
result = result.Replace(trackArtist + splitCharacter, string.Empty, StringComparison.OrdinalIgnoreCase);
result = result.Replace(splitCharacter + trackArtist, string.Empty, StringComparison.OrdinalIgnoreCase);
result = result.Replace(trackArtist, string.Empty, StringComparison.OrdinalIgnoreCase);
}
if(Configuration.Processing.DoDetectFeatureFragments)
{
if(!string.IsNullOrWhiteSpace(result))
{
if(result.HasFeaturingFragments())
{
throw new RoadieProcessingException($"Artist name [{ result }] has Feature fragments.");
}
}
}
return string.IsNullOrEmpty(result) ? null : result;
}

View file

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

View file

@ -27,9 +27,21 @@ namespace Roadie.Library.Inspect.Plugins.File
var originalRelease = metaData.Release;
metaData.Release = metaData.Release
?.CleanString(Configuration, Configuration.Processing.ReleaseRemoveStringsRegex).ToTitleCase(false);
if (string.IsNullOrEmpty(metaData.Release)) metaData.Release = originalRelease;
if (string.IsNullOrEmpty(metaData.Release))
{
metaData.Release = originalRelease;
}
}
if (Configuration.Processing.DoDetectFeatureFragments)
{
if (!string.IsNullOrWhiteSpace(metaData?.Release))
{
if (metaData.Release.HasFeaturingFragments())
{
throw new RoadieProcessingException($"Release title [{ metaData?.Release }] has Feature fragments.");
}
}
}
result.Data = metaData;
result.IsSuccess = true;
return result;

View file

@ -27,9 +27,21 @@ namespace Roadie.Library.Inspect.Plugins.File
var originalTitle = metaData.Title;
metaData.Title = metaData.Title
?.CleanString(Configuration, Configuration.Processing.TrackRemoveStringsRegex).ToTitleCase(false);
if (string.IsNullOrEmpty(metaData.Title)) metaData.Title = originalTitle;
if (string.IsNullOrEmpty(metaData.Title))
{
metaData.Title = originalTitle;
}
}
if (Configuration.Processing.DoDetectFeatureFragments)
{
if (!string.IsNullOrWhiteSpace(metaData?.Title))
{
if (metaData.Release.HasFeaturingFragments())
{
throw new RoadieProcessingException($"Track title [{ metaData?.Title }] has Feature fragments.");
}
}
}
result.Data = metaData;
result.IsSuccess = true;
return result;

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)
{
var result = new OperationResult<AudioMetaData>();
if (Configuration.Processing.DoAudioCleanup)
{
if (metaData.FileInfo.IsReadOnly)
{
metaData.FileInfo.Attributes =
RemoveAttribute(metaData.FileInfo.Attributes, FileAttributes.ReadOnly);
Console.WriteLine($"╟ Removed read only attribute on file file [{metaData.FileInfo.Name}");
}
}
result.Data = metaData;
result.IsSuccess = true;
return result;
}
private static FileAttributes RemoveAttribute(FileAttributes attributes, FileAttributes attributesToRemove)
{
return attributes & ~attributesToRemove;
}
}
}

View file

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

View file

@ -12,22 +12,21 @@ namespace Roadie.Library.Inspect.Plugins
{
public abstract class PluginBase
{
public abstract string Description { get; }
public abstract int Order { get; }
private Dictionary<string, IEnumerable<AudioMetaData>> CachedAudioDatas { get; }
protected ICacheManager CacheManager { 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 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,
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.Releases;
using Roadie.Library.Models.Statistics;
@ -8,6 +7,7 @@ using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
namespace Roadie.Library.Models
{
@ -48,7 +48,7 @@ namespace Roadie.Library.Models
[IgnoreDataMember]
public string ISNI { get; set; }
[JsonProperty("isniList")]
[JsonPropertyName("isniList")]
public IEnumerable<string> ISNIList
{
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.Playlists;
using Roadie.Library.Models.Releases;
using System;
using System.Text.Json.Serialization;
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
{
@ -7,7 +7,9 @@ namespace Roadie.Library.Models.Collections
internal class CollectionReleaseList : EntityInfoModelBase
{
private string _listNumber;
public DataToken Artist { get; set; }
public string ArtistThumbnailUrl { get; set; }
public string ListNumber
@ -16,10 +18,16 @@ namespace Roadie.Library.Models.Collections
set => _listNumber = value;
}
[JsonIgnore] public int ListNumberValue { get; set; }
[JsonIgnore]
public int ListNumberValue { get; set; }
public DataToken Release { get; set; }
[JsonIgnore] public DateTime? ReleaseDateDateTime { get; set; }
[JsonIgnore]
public DateTime? ReleaseDateDateTime { get; set; }
public short? ReleaseRating { get; set; }
public string ReleaseThumbnailUrl { get; set; }
public string ReleaseYear => ReleaseDateDateTime.HasValue

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

View file

@ -1,15 +1,15 @@
using Newtonsoft.Json;
using Roadie.Library.Enums;
using Roadie.Library.Models.Releases;
using Roadie.Library.Models.Users;
using Roadie.Library.Utility;
using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using Release = Roadie.Library.Data.Release;
using Roadie.Library.Enums;
namespace Roadie.Library.Models
{
@ -18,6 +18,7 @@ namespace Roadie.Library.Models
public sealed class TrackList : EntityInfoModelBase
{
public ArtistList Artist { get; set; }
public int? Duration { get; set; }
public string DurationTime => Duration.HasValue ? new TimeInfo(Duration.Value).ToFullFormattedString() : "--:--";
@ -25,8 +26,11 @@ namespace Roadie.Library.Models
public string DurationTimeShort => Duration.HasValue ? new TimeInfo(Duration.Value).ToShortFormattedString() : "--:--";
public int? FavoriteCount { get; set; }
public int? FileSize { get; set; }
public DateTime? LastPlayed { get; set; }
public int? MediaNumber { get; set; }
[MaxLength(65535)]
@ -38,100 +42,84 @@ namespace Roadie.Library.Models
{
get
{
if (string.IsNullOrEmpty(PartTitles)) return null;
if (string.IsNullOrEmpty(PartTitles))
{
return null;
}
return PartTitles.Split('\n');
}
}
public int? PlayedCount { get; set; }
public short? Rating { get; set; }
public ReleaseList Release { get; set; }
[JsonIgnore] public DateTime? ReleaseDate { get; set; }
public short? ReleaseRating { get; set; }
public Image Thumbnail { get; set; }
public string Title { get; set; }
public DataToken Track { get; set; }
public ArtistList TrackArtist { get; set; }
[JsonIgnore] public string TrackId => Track?.Value;
[JsonIgnore] public string TrackName => Track?.Text;
[JsonIgnore] public string ReleaseName => Release?.Release?.Text;
public short? Rating { get; set; }
public ReleaseList Release { get; set; }
[JsonIgnore]
public DateTime? ReleaseDate { get; set; }
[JsonIgnore]
public string ReleaseName => Release?.Release?.Text;
public short? ReleaseRating { get; set; }
public Statuses? Status { get; set; }
public string StatusVerbose => (Status ?? Statuses.Missing).ToString();
public Image Thumbnail { get; set; }
public string Title { get; set; }
public DataToken Track { get; set; }
public ArtistList TrackArtist { get; set; }
[JsonIgnore]
public string TrackId => Track?.Value;
[JsonIgnore]
public string TrackName => Track?.Text;
public int? TrackNumber { get; set; }
public string TrackPlayUrl { get; set; }
public UserTrack UserRating { get; set; }
public int? Year
{
get
{
if (ReleaseDate.HasValue) return ReleaseDate.Value.Year;
if (ReleaseDate.HasValue)
{
return ReleaseDate.Value.Year;
}
return null;
}
}
public int GetArtistReleaseHashCode() => this.Artist?.Artist?.Value.GetHashCode() + this.Release?.Release?.Value.GetHashCode() ?? 0;
public override int GetHashCode() => this.Artist?.Artist?.Value.GetHashCode() + this.Release?.Release?.Value.GetHashCode() + this.Track?.Value.GetHashCode() ?? 0;
public static IEnumerable<int> AllIndexesOfArtist(IEnumerable<TrackList> tracks, string artistId)
{
if(tracks == null || !tracks.Any())
if (tracks?.Any() != true)
{
return new int[0];
}
return tracks.Select((b, i) => b.Artist?.Artist?.Value == artistId ? i : -1).Where(i => i != -1).ToArray();
return tracks.Select((b, i) => b.Artist?.Artist?.Value == artistId ? i : -1).Where(i => i != -1).ToArray();
}
public static IEnumerable<int> AllIndexesOfRelease(IEnumerable<TrackList> tracks, string releaseId)
{
if (tracks == null || !tracks.Any())
if (tracks?.Any() != true)
{
return new int[0];
}
return tracks.Select((b, i) => b.Release?.Release?.Value == releaseId ? i : -1).Where(i => i != -1).ToArray();
}
/// <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,
Data.Track track,
int releaseMediaNumber,
@ -173,6 +161,44 @@ namespace Roadie.Library.Models
};
}
public int GetArtistReleaseHashCode() => this.Artist?.Artist?.Value.GetHashCode() + this.Release?.Release?.Value.GetHashCode() ?? 0;
public override int GetHashCode() => this.Artist?.Artist?.Value.GetHashCode() + this.Release?.Release?.Value.GetHashCode() + this.Track?.Value.GetHashCode() ?? 0;
/// <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.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
namespace Roadie.Library.Models.Users
{
@ -11,7 +12,9 @@ namespace Roadie.Library.Models.Users
{
public const string ActionKeyUserRated = "__userrated__";
public const string DefaultIncludes = "stats";
[MaxLength(100)] public string ApiToken { get; set; }
[MaxLength(100)]
public string ApiToken { get; set; }
public Image Avatar { get; set; }
@ -20,7 +23,12 @@ namespace Roadie.Library.Models.Users
/// </summary>
public string AvatarData { get; set; }
[Required] [MaxLength(100)] public string ConcurrencyStamp { get; set; }
[Required]
[MaxLength(100)]
public string ConcurrencyStamp { get; set; }
public short? DefaultRowsPerPage { get; set; }
public bool DoUseHtmlPlayer { get; set; }
[Required]
@ -28,18 +36,34 @@ namespace Roadie.Library.Models.Users
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
[MaxLength(500)] public string FtpDirectory { get; set; }
[MaxLength(500)]
public string FtpDirectory { get; set; }
[MaxLength(500)] public string FtpPassword { get; set; }
[MaxLength(500)]
public string FtpPassword { get; set; }
[MaxLength(250)] public string FtpUrl { get; set; }
[MaxLength(250)]
[DataType(DataType.Url)]
public string FtpUrl { get; set; }
[MaxLength(50)] public string FtpUsername { get; set; }
[MaxLength(50)]
public string FtpUsername { get; set; }
[JsonPropertyName("UserDbId")]
public new int? Id { get; set; }
public bool IsAdmin { get; set; }
public bool IsEditor { get; set; }
public bool IsPrivate { get; set; }
public DateTime LastApiAccess { get; set; }
public DateTime LastLogin { get; set; }
public Image MediumThumbnail { get; set; }
/// <summary>
/// Posted password only used when changing password from profile edits
/// </summary>
@ -55,23 +79,34 @@ namespace Roadie.Library.Models.Users
public string PasswordConfirmation { get; set; }
public short? PlayerTrackLimit { get; set; }
[MaxLength(65535)] public string Profile { get; set; }
[MaxLength(65535)]
public string Profile { get; set; }
public short? RandomReleaseLimit { get; set; }
public short? RecentlyPlayedLimit { get; set; }
public bool RemoveTrackFromQueAfterPlayed { get; set; }
[NotMapped] [AdaptIgnore] public UserStatistics Statistics { get; set; }
[StringLength(50)] [Required] public string Timeformat { get; set; }
[MaxLength(50)] [Required] public string Timezone { get; set; }
[NotMapped]
[AdaptIgnore]
public UserStatistics Statistics { get; set; }
[AdaptMember("RoadieId")] public Guid UserId { get; set; }
[StringLength(50)]
[Required]
public string Timeformat { get; set; }
[Required] [MaxLength(20)] public string UserName { get; set; }
public Image MediumThumbnail { get; set; }
[MaxLength(50)]
[Required]
public string Timezone { get; set; }
public DateTime LastLogin { get; set; }
public DateTime LastApiAccess { get; set; }
public short? DefaultRowsPerPage { get; set; }
[AdaptMember("RoadieId")]
public Guid UserId { get; set; }
[Required]
[MaxLength(20)]
public string UserName { get; set; }
public override string ToString() => $"Id [{Id}], RoadieId [{UserId}], UserName [{UserName}]";
}

View file

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

View file

@ -18,17 +18,19 @@
<PackageReference Include="IdSharp.Tagging" Version="1.0.0-rc3" />
<PackageReference Include="Inflatable.Lastfm" Version="1.1.0.339" />
<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="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.4" />
<PackageReference Include="MetadataExtractor" Version="2.4.2" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.5" />
<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.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.Net.Http.Headers" Version="2.2.8" />
<PackageReference Include="Microsoft.PowerShell.SDK" Version="7.0.1" />
<PackageReference Include="MimeMapping" Version="1.0.1.26" />
<PackageReference Include="Microsoft.PowerShell.SDK" Version="7.0.2" />
<PackageReference Include="MimeMapping" Version="1.0.1.30" />
<PackageReference Include="NodaTime" Version="3.0.0" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.1" />
<PackageReference Include="RestSharp" Version="106.11.4" />
@ -39,6 +41,7 @@
<PackageReference Include="System.Drawing.Common" 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="Utf8Json" Version="1.3.7" />
<PackageReference Include="z440.atl.core" Version="3.5.0" />
<PackageReference Include="zlib.net-mutliplatform" Version="1.0.4" />
</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.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.Json.Serialization;
namespace Roadie.Library.MetaData.Audio
{
@ -14,10 +14,11 @@ namespace Roadie.Library.MetaData.Audio
public sealed class AudioMetaData : IAudioMetaData
{
public const char ArtistSplitCharacter = '/';
public const int MinimumYearValue = 1900;
public const string SoundTrackArtist = "Sound Tracks";
private string _artist;
private bool _doModifyArtistNameOnGet = true;
private FileInfo _fileInfo;
@ -43,15 +44,20 @@ namespace Roadie.Library.MetaData.Audio
{
get
{
if (_doModifyArtistNameOnGet)
if (!string.IsNullOrEmpty(_artist) && _artist.Contains(ArtistSplitCharacter.ToString()))
return _artist.Split(ArtistSplitCharacter).First();
if (_doModifyArtistNameOnGet && !string.IsNullOrEmpty(_artist) && _artist.Contains(ArtistSplitCharacter.ToString()))
{
return _artist.Split(ArtistSplitCharacter)[0];
}
return _artist;
}
set
{
_artist = value;
if (!string.IsNullOrEmpty(_artist)) _artist = _artist.Replace(';', ArtistSplitCharacter);
if (!string.IsNullOrEmpty(_artist))
{
_artist = _artist.Replace(';', ArtistSplitCharacter);
}
}
}
@ -66,12 +72,36 @@ namespace Roadie.Library.MetaData.Audio
get
{
var result = AudioMetaDataWeights.None;
if (!string.IsNullOrEmpty(Artist)) result |= AudioMetaDataWeights.Artist;
if (!string.IsNullOrEmpty(Title)) result |= AudioMetaDataWeights.Time;
if ((Year ?? 0) > 1) result |= AudioMetaDataWeights.Year;
if ((TrackNumber ?? 0) > 1) result |= AudioMetaDataWeights.TrackNumber;
if ((TotalTrackNumbers ?? 0) > 1) result |= AudioMetaDataWeights.TrackTotalNumber;
if (TotalSeconds > 1) result |= AudioMetaDataWeights.Time;
if (!string.IsNullOrEmpty(Artist))
{
result |= AudioMetaDataWeights.Artist;
}
if (!string.IsNullOrEmpty(Title))
{
result |= AudioMetaDataWeights.Time;
}
if ((Year ?? 0) > 1)
{
result |= AudioMetaDataWeights.Year;
}
if ((TrackNumber ?? 0) > 1)
{
result |= AudioMetaDataWeights.TrackNumber;
}
if ((TotalTrackNumbers ?? 0) > 1)
{
result |= AudioMetaDataWeights.TrackTotalNumber;
}
if (TotalSeconds > 1)
{
result |= AudioMetaDataWeights.Time;
}
return result;
}
}
@ -90,7 +120,11 @@ namespace Roadie.Library.MetaData.Audio
{
get
{
if (string.IsNullOrEmpty(Filename)) return null;
if (string.IsNullOrEmpty(Filename))
{
return null;
}
return Path.GetDirectoryName(Filename);
}
}
@ -105,7 +139,8 @@ namespace Roadie.Library.MetaData.Audio
/// </summary>
public string DiscSubTitle { get; set; }
[JsonIgnore] public FileInfo FileInfo => _fileInfo ?? (_fileInfo = new FileInfo(Filename));
[JsonIgnore]
public FileInfo FileInfo => _fileInfo ?? (_fileInfo = new FileInfo(Filename));
/// <summary>
/// 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; }
[JsonIgnore] public IEnumerable<AudioMetaDataImage> Images { get; set; }
[JsonIgnore]
public IEnumerable<AudioMetaDataImage> Images { get; set; }
public string ISRC { get; internal set; }
@ -122,10 +158,13 @@ namespace Roadie.Library.MetaData.Audio
{
get
{
if (Genres != null && Genres.Any())
if (Genres?.Any() == true)
{
var soundtrackGenres = new List<string> { "24", "soundtrack" };
if (Genres.Intersect(soundtrackGenres, StringComparer.OrdinalIgnoreCase).Any()) return true;
if (Genres.Intersect(soundtrackGenres, StringComparer.OrdinalIgnoreCase).Any())
{
return true;
}
}
return false;
@ -142,7 +181,7 @@ namespace Roadie.Library.MetaData.Audio
Release = Release == null ? null : Release.Equals("Unknown Release") ? null : Release;
if (!string.IsNullOrEmpty(Title))
{
var trackNumberTitle = string.Format("Track {0}", TrackNumber);
var trackNumberTitle = $"Track {TrackNumber}";
Title = Title == trackNumberTitle ? null : Title;
}
@ -193,7 +232,10 @@ namespace Roadie.Library.MetaData.Audio
set
{
_title = value;
if (IsSoundTrack) Artist = SoundTrackArtist;
if (IsSoundTrack)
{
Artist = SoundTrackArtist;
}
}
}
@ -206,7 +248,11 @@ namespace Roadie.Library.MetaData.Audio
{
get
{
if (Time == null) return 0;
if (Time == null)
{
return 0;
}
return Time.Value.TotalSeconds;
}
}
@ -225,9 +271,11 @@ namespace Roadie.Library.MetaData.Audio
{
string result = null;
if (!string.IsNullOrEmpty(_trackArtist))
result = _trackArtist.Split(ArtistSplitCharacter).First().ToTitleCase();
result = !_artist?.Equals(result, StringComparison.OrdinalIgnoreCase) ?? false ? result : null;
return result;
{
result = _trackArtist.Split(ArtistSplitCharacter)[0].ToTitleCase();
}
return !String.Equals(_artist, result, StringComparison.OrdinalIgnoreCase) ? result : null;
}
set => _trackArtist = value;
}
@ -242,17 +290,33 @@ namespace Roadie.Library.MetaData.Audio
{
get
{
if (string.IsNullOrEmpty(_trackArtist)) return new string[0];
if (string.IsNullOrEmpty(_trackArtist))
{
return new string[0];
}
if (!_trackArtist.Contains(ArtistSplitCharacter.ToString()))
{
if (string.IsNullOrEmpty(TrackArtist)) return new string[0];
if (string.IsNullOrEmpty(TrackArtist))
{
return new string[0];
}
return new string[1] { TrackArtist };
}
if (!string.IsNullOrEmpty(_artist) || !string.IsNullOrEmpty(_trackArtist))
if (!_artist.Equals(_trackArtist, StringComparison.OrdinalIgnoreCase))
return _trackArtist.Split(ArtistSplitCharacter).Where(x => !string.IsNullOrEmpty(x))
.Select(x => x.ToTitleCase()).OrderBy(x => x).ToArray();
{
if (!String.Equals(_artist, _trackArtist, StringComparison.OrdinalIgnoreCase))
{
return _trackArtist.Split(ArtistSplitCharacter)
.Where(x => !string.IsNullOrEmpty(x))
.Select(x => x.ToTitleCase())
.OrderBy(x => x)
.ToArray();
}
}
return new string[0];
}
}
@ -280,8 +344,11 @@ namespace Roadie.Library.MetaData.Audio
public override bool Equals(object obj)
{
var item = obj as AudioMetaData;
if (item == null) return false;
if (!(obj is AudioMetaData item))
{
return false;
}
return item.GetHashCode() == GetHashCode();
}
@ -290,12 +357,12 @@ namespace Roadie.Library.MetaData.Audio
unchecked
{
var hash = 17;
hash = hash * 23 + Artist.GetHashCode();
hash = hash * 23 + Release.GetHashCode();
hash = hash * 23 + Title.GetHashCode();
hash = hash * 23 + TrackNumber.GetHashCode();
hash = hash * 23 + AudioBitrate.GetHashCode();
hash = hash * 23 + AudioSampleRate.GetHashCode();
hash = (hash * 23) + Artist.GetHashCode();
hash = (hash * 23) + Release.GetHashCode();
hash = (hash * 23) + Title.GetHashCode();
hash = (hash * 23) + TrackNumber.GetHashCode();
hash = (hash * 23) + AudioBitrate.GetHashCode();
hash = (hash * 23) + AudioSampleRate.GetHashCode();
return hash;
}
}
@ -314,9 +381,17 @@ namespace Roadie.Library.MetaData.Audio
{
var result =
$"IsValid: {IsValid}{(IsSoundTrack ? " [SoundTrack ]" : string.Empty)}, ValidWeight {ValidWeight}, Artist: {Artist}";
if (!string.IsNullOrEmpty(TrackArtist)) result += $", TrackArtist: {TrackArtist}";
if (!string.IsNullOrEmpty(TrackArtist))
{
result += $", TrackArtist: {TrackArtist}";
}
result += $", Release: {Release}, TrackNumber: {TrackNumber}, TrackTotal: {TotalTrackNumbers}";
if (TotalDiscCount > 1) result += $", Disc: {Disc}/{TotalDiscCount}";
if (TotalDiscCount > 1)
{
result += $", Disc: {Disc}/{TotalDiscCount}";
}
result += $", Title: {Title}, Year: {Year}, Duration: {(Time == null ? "-" : Time.Value.ToString())}";
return result;
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,140 +1,194 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System;
using System.Diagnostics;
using System.Text;
using System.Text.Json.Serialization;
namespace Roadie.Library.MetaData.MusicBrainz
{
[Serializable]
public class ArtistResult
{
public DateTime created { get; set; }
public int count { get; set; }
public int offset { get; set; }
public Artist[] artists { get; set; }
public int count { get; set; }
public DateTime created { get; set; }
public int offset { get; set; }
}
[Serializable]
public class RepositoryArtist
{
public int Id { get; set; }
public string ArtistName { get; set; }
public string ArtistMbId { get; set; }
public Artist Artist { get; set; }
public string ArtistMbId { get; set; }
public string ArtistName { get; set; }
public int Id { get; set; }
}
[DebuggerDisplay("name: {name}")]
[Serializable]
public class Artist
{
public string id { get; set; }
public string type { get; set; }
[JsonProperty("type-id")]
public string typeid { get; set; }
public int score { get; set; }
public string name { get; set; }
[JsonProperty("sort-name")]
public string sortname { get; set; }
public string gender { get; set; }
public string country { get; set; }
public Area area { get; set; }
[JsonProperty("begin_area")]
public BeginAndEndArea beginarea { get; set; }
[JsonProperty("end_area")]
public BeginAndEndArea endarea { get; set; }
public string[] ipis { get; set; }
[JsonProperty("life-span")]
public LifeSpan lifespan { get; set; }
public Alias[] aliases { get; set; }
public NameAndCount[] tags { get; set; }
public IsniList[] isnilist { get; set; }
public Area area { get; set; }
[JsonPropertyName("begin_area")]
public BeginAndEndArea beginarea { get; set; }
public string country { get; set; }
[JsonPropertyName("end_area")]
public BeginAndEndArea endarea { get; set; }
public string gender { get; set; }
public NameAndCount[] genres { get; set; }
public string id { get; set; }
public string[] ipis { get; set; }
public IsniList[] isnilist { get; set; }
[JsonPropertyName("life-span")]
public LifeSpan lifespan { get; set; }
public string name { get; set; }
public Relation[] relations { get; set; }
public int score { get; set; }
[JsonPropertyName("sort-name")]
public string sortname { get; set; }
public NameAndCount[] tags { get; set; }
public string type { get; set; }
[JsonPropertyName("type-id")]
public string typeid { get; set; }
}
[Serializable]
public class Relations
{
[JsonProperty("attribute-ids")]
[JsonPropertyName("attribute-ids")]
public string[] attributeids { get; set; }
public string[] attributes { get; set; }
public string direction { get; set; }
[JsonProperty("target-credit")]
public string targetcredit { get; set; }
[JsonProperty("type-id")]
public string typeid { get; set; }
[JsonProperty("target-type")]
public string targettype { get; set; }
public string type { get; set; }
public bool ended { get; set; }
public Url url { get; set; }
public string[] attributevalues { get; set; }
[JsonProperty("source-credit")]
public string sourcecredit { get; set; }
public object end { get; set; }
public object begin { get; set; }
public string direction { get; set; }
public object end { get; set; }
public bool? ended { get; set; }
[JsonPropertyName("source-credit")]
public string sourcecredit { get; set; }
[JsonPropertyName("target-credit")]
public string targetcredit { get; set; }
[JsonPropertyName("target-type")]
public string targettype { get; set; }
public string type { get; set; }
[JsonPropertyName("type-id")]
public string typeid { get; set; }
public Url url { get; set; }
}
[Serializable]
public class Url
{
public string resource { get; set; }
public string id { get; set; }
}
public string resource { get; set; }
}
[Serializable]
public class Area
{
public string id { get; set; }
public string type { get; set; }
[JsonProperty("type-id")]
public string typeid { get; set; }
public string name { get; set; }
[JsonProperty("sort-name")]
public string sortname { get; set; }
[JsonProperty("life-span")]
public LifeSpan lifespan { get; set; }
[JsonProperty("iso-3166-1-codes")]
[JsonPropertyName("iso-3166-1-codes")]
public string[] iso31661codes { get; set; }
[JsonPropertyName("life-span")]
public LifeSpan lifespan { get; set; }
public string name { get; set; }
[JsonPropertyName("sort-name")]
public string sortname { get; set; }
public string type { get; set; }
[JsonPropertyName("type-id")]
public string typeid { get; set; }
}
[Serializable]
public class LifeSpan
{
public string end { get; set; }
public string ended { get; set; }
public string begin { get; set; }
public string end { get; set; }
public bool? ended { get; set; }
}
[Serializable]
public class BeginAndEndArea
{
public string id { get; set; }
public string type { get; set; }
public string typeid { get; set; }
public string name { get; set; }
[JsonProperty("sort-name")]
public string sortname { get; set; }
[JsonProperty("life-span")]
[JsonPropertyName("life-span")]
public LifeSpan lifespan { get; set; }
public string name { get; set; }
[JsonPropertyName("sort-name")]
public string sortname { get; set; }
public string type { get; set; }
public string typeid { get; set; }
}
[Serializable]
public class Alias
{
[JsonProperty("sort-name")]
public string sortname { get; set; }
[JsonProperty("type-id")]
public string typeid { get; set; }
public string name { get; set; }
public string locale { get; set; }
public string type { get; set; }
public string primary { get; set; }
public string begin { get; set; }
public string end { get; set; }
public bool ended { get; set; }
public bool? ended { get; set; }
public string locale { get; set; }
public string name { get; set; }
public bool? primary { get; set; }
[JsonPropertyName("sort-name")]
public string sortname { get; set; }
public string type { get; set; }
[JsonPropertyName("type-id")]
public string typeid { get; set; }
}
[Serializable]
@ -147,7 +201,7 @@ namespace Roadie.Library.MetaData.MusicBrainz
public class NameAndCount
{
public int? count { get; set; }
public string name { get; set; }
}
}

View file

@ -7,10 +7,10 @@
<ItemGroup>
<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="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>

View file

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

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,6 @@
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Roadie.Library;
using Roadie.Library.Caching;
using Roadie.Library.Configuration;
@ -24,6 +23,7 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Text.Json;
using System.Threading.Tasks;
using data = Roadie.Library.Data;
@ -791,7 +791,7 @@ namespace Roadie.Api.Services
}
catch (Exception ex)
{
Logger.LogError(ex, "Error In List, Request [{0}], User [{1}]", JsonConvert.SerializeObject(request), roadieUser);
Logger.LogError(ex, "Error In List, Request [{0}], User [{1}]", JsonSerializer.Serialize(request), roadieUser);
return new Library.Models.Pagination.PagedResult<TrackList>
{
Message = "An Error has occured"

View file

@ -2,7 +2,6 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Roadie.Library;
using Roadie.Library.Caching;
using Roadie.Library.Configuration;
@ -23,6 +22,7 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Text.Json;
using System.Threading.Tasks;
using data = Roadie.Library.Data;
using models = Roadie.Library.Models;
@ -593,7 +593,7 @@ namespace Roadie.Api.Services
result.AdditionalData.Add("Timing", sw.ElapsedMilliseconds);
Logger.LogTrace(
$"User `{roadieUser}` set rating [{rating}] on TrackId [{trackId}]. Result [{JsonConvert.SerializeObject(result)}]");
$"User `{roadieUser}` set rating [{rating}] on TrackId [{trackId}]. Result [{JsonSerializer.Serialize(result)}]");
return result;
}

View file

@ -2,7 +2,6 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Roadie.Api.Services;
using Roadie.Library.Caching;
using Roadie.Library.Configuration;
@ -13,6 +12,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;
using models = Roadie.Library.Models.Users;
@ -146,7 +146,7 @@ namespace Roadie.Api.Controllers
};
await playActivityService.NowPlayingAsync(user, scrobble).ConfigureAwait(false);
sw.Stop();
Logger.LogTrace($"StreamTrack ElapsedTime [{sw.ElapsedMilliseconds}], Timings [{JsonConvert.SerializeObject(timings)}], StreamInfo `{info?.Data}`");
Logger.LogTrace($"StreamTrack ElapsedTime [{sw.ElapsedMilliseconds}], Timings [{JsonSerializer.Serialize(timings)}], StreamInfo `{info?.Data}`");
return new EmptyResult();
}

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<VersionPrefix>1.1.2</VersionPrefix>
<VersionPrefix>1.1.3</VersionPrefix>
<VersionSuffix></VersionSuffix>
</PropertyGroup>
@ -31,15 +31,16 @@
<PackageReference Include="BCrypt-Core" Version="2.0.0" />
<PackageReference Include="Mapster" Version="5.3.2" />
<PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.14.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.4" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.4" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.5" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.5" />
<PackageReference Include="Microsoft.CodeAnalysis.Common" 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.EntityFrameworkCore.Relational" Version="3.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" 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.Configuration.Json" Version="3.1.5" />
<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.Enrichers.Environment" Version="2.1.3" />
<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.RollingFileAlternate" Version="2.0.9" />
<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>

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.Logging;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using Roadie.Api.Hubs;
using Roadie.Api.ModelBinding;
using Roadie.Api.Services;
@ -113,10 +112,17 @@ namespace Roadie.Api
services.AddSingleton<IHttpEncoder, HttpEncoder>();
services.AddSingleton<IEmailSender, EmailSenderService>();
services.AddSingleton<ICacheSerializer>(options =>
{
var logger = options.GetService<ILogger<Utf8JsonCacheSerializer>>();
return new Utf8JsonCacheSerializer(logger);
});
services.AddSingleton<ICacheManager>(options =>
{
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);
@ -326,11 +332,7 @@ namespace Roadie.Api
options.RespectBrowserAcceptHeader = true; // false by default
options.ModelBinderProviders.Insert(0, new SubsonicRequestBinderProvider());
})
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
})
.AddJsonOptions(options => options.JsonSerializerOptions.IgnoreNullValues = true)
.AddXmlSerializerFormatters();
services.Configure<IdentityOptions>(options =>

View file

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

View file

@ -1,5 +1,4 @@
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Roadie.Dlna.Server.Metadata;
using Roadie.Dlna.Utility;
using System;
@ -13,23 +12,16 @@ namespace Roadie.Dlna.Server
internal partial class MediaMount
{
private const string NS_DC = "http://purl.org/dc/elements/1.1/";
private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
private const string NS_DLNA = "urn:schemas-dlna-org:metadata-1-0/";
private const string NS_SEC = "http://www.sec.co.kr/";
private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/";
private static readonly IDictionary<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 IDictionary<string, AttributeCollection> soapCache = new LeastRecentlyUsedDictionary<string, AttributeCollection>(200);
private static void AddBookmarkInfo(IMediaResource resource, XmlElement item)
{
var bookmarkable = resource as IBookmarkable;
@ -79,10 +71,7 @@ namespace Roadie.Dlna.Server
var res = result.CreateElement(string.Empty, "res", NS_DIDL);
res.InnerText = curl;
res.SetAttribute("protocolInfo", string.Format(
"http-get:*:{1}:DLNA.ORG_PN={0};DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS={2}",
c.PN, DlnaMaps.Mime[c.Type], DlnaMaps.DefaultStreaming
));
res.SetAttribute("protocolInfo", $"http-get:*:{DlnaMaps.Mime[c.Type]}:DLNA.ORG_PN={c.PN};DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS={DlnaMaps.DefaultStreaming}");
var width = c.MetaWidth;
var height = c.MetaHeight;
if (width.HasValue && height.HasValue)
@ -270,10 +259,7 @@ namespace Roadie.Dlna.Server
res.SetAttribute("duration", prop);
}
res.SetAttribute("protocolInfo", string.Format(
"http-get:*:{1}:DLNA.ORG_PN={0};DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS={2}",
resource.PN, DlnaMaps.Mime[resource.Type], DlnaMaps.DefaultStreaming
));
res.SetAttribute("protocolInfo", $"http-get:*:{DlnaMaps.Mime[resource.Type]}:DLNA.ORG_PN={resource.PN};DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS={DlnaMaps.DefaultStreaming}");
item.AppendChild(res);
AddCover(request, resource, item);
@ -509,7 +495,7 @@ namespace Roadie.Dlna.Server
{
Logger.LogError(ex, $"ProcessSoapRequest. Error Loading Request From [{ request.RemoteEndpoint.Address }], Body [{ request.Body }]");
}
var sparams = new RawHeaders();
var sparams = new RawHeaders();
var body = soap.SelectSingleNode("//soap:Body", namespaceMgr);
if (body == null)
{
@ -625,5 +611,7 @@ namespace Roadie.Dlna.Server
rv.Headers.Add("EXT", string.Empty);
return rv;
}
private static readonly XmlNamespaceManager namespaceMgr = CreateNamespaceManager();
}
}

BIN
libraries/libwebp_x64.dll Normal file

Binary file not shown.