mirror of
https://github.com/sphildreth/roadie
synced 2024-11-10 06:44:12 +00:00
CodeMaid cleanup, Warning resolve work
This commit is contained in:
parent
022920b3f3
commit
0fd9fc92d0
45 changed files with 652 additions and 190 deletions
|
@ -20,7 +20,9 @@ namespace Roadie.Library.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
private IRoadieSettings Configuration { get; }
|
private IRoadieSettings Configuration { get; }
|
||||||
|
|
||||||
public DictionaryCacheManager CacheManager { get; }
|
public DictionaryCacheManager CacheManager { get; }
|
||||||
|
|
||||||
private Encoding.IHttpEncoder HttpEncoder { get; }
|
private Encoding.IHttpEncoder HttpEncoder { get; }
|
||||||
|
|
||||||
public ArtistLookupEngineTests()
|
public ArtistLookupEngineTests()
|
||||||
|
|
111
Roadie.Api.Library.Tests/LastFmHelperTests.cs
Normal file
111
Roadie.Api.Library.Tests/LastFmHelperTests.cs
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Roadie.Library.Caching;
|
||||||
|
using Roadie.Library.Configuration;
|
||||||
|
using Roadie.Library.MetaData.LastFm;
|
||||||
|
using Roadie.Library.Processors;
|
||||||
|
using Roadie.Library.MetaData.MusicBrainz;
|
||||||
|
using Roadie.Library.SearchEngines.MetaData.Discogs;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xunit;
|
||||||
|
using Roadie.Library.Data.Context;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Roadie.Library.Tests
|
||||||
|
{
|
||||||
|
public class LastFmHelperTests : HttpClientFactoryBaseTests
|
||||||
|
{
|
||||||
|
private IEventMessageLogger MessageLogger { get; }
|
||||||
|
|
||||||
|
private IRoadieSettings Configuration { get; }
|
||||||
|
|
||||||
|
public DictionaryCacheManager CacheManager { get; }
|
||||||
|
|
||||||
|
private Encoding.IHttpEncoder HttpEncoder { get; }
|
||||||
|
|
||||||
|
private IRoadieDbContext RoadieDbContext { get; }
|
||||||
|
|
||||||
|
private ILogger Logger
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return MessageLogger as ILogger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LastFmHelperTests()
|
||||||
|
{
|
||||||
|
MessageLogger = new EventMessageLogger<SearchEngineTests>();
|
||||||
|
MessageLogger.Messages += MessageLogger_Messages;
|
||||||
|
|
||||||
|
var settings = new RoadieSettings();
|
||||||
|
IConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
|
||||||
|
configurationBuilder.AddJsonFile("appsettings.test.json");
|
||||||
|
IConfiguration configuration = configurationBuilder.Build();
|
||||||
|
configuration.GetSection("RoadieSettings").Bind(settings);
|
||||||
|
Configuration = settings;
|
||||||
|
CacheManager = new DictionaryCacheManager(Logger, new SystemTextCacheSerializer(Logger), new CachePolicy(TimeSpan.FromHours(4)));
|
||||||
|
HttpEncoder = new Encoding.DummyHttpEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task LastFMReleaseSearch()
|
||||||
|
{
|
||||||
|
if (!Configuration.Integrations.LastFmProviderEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var logger = new EventMessageLogger<LastFmHelper>();
|
||||||
|
logger.Messages += MessageLogger_Messages;
|
||||||
|
var lfmHelper = new LastFmHelper(Configuration, CacheManager, new EventMessageLogger<LastFmHelper>(), RoadieDbContext, HttpEncoder, _httpClientFactory);
|
||||||
|
|
||||||
|
var artistName = "Billy Joel";
|
||||||
|
var title = "Piano Man";
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
var result = await lfmHelper.PerformReleaseSearch(artistName, title, 1).ConfigureAwait(false);
|
||||||
|
|
||||||
|
sw.Stop();
|
||||||
|
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.NotNull(result.Data);
|
||||||
|
Assert.NotEmpty(result.Data);
|
||||||
|
var release = result.Data.FirstOrDefault();
|
||||||
|
Assert.NotNull(release);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task LastFMArtistSearch()
|
||||||
|
{
|
||||||
|
if (!Configuration.Integrations.LastFmProviderEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var logger = new EventMessageLogger<LastFmHelper>();
|
||||||
|
logger.Messages += MessageLogger_Messages;
|
||||||
|
var lfmHelper = new LastFmHelper(Configuration, CacheManager, new EventMessageLogger<LastFmHelper>(), RoadieDbContext, HttpEncoder, _httpClientFactory);
|
||||||
|
|
||||||
|
var artistName = "Billy Joel";
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
var result = await lfmHelper.PerformArtistSearchAsync(artistName, 1).ConfigureAwait(false);
|
||||||
|
|
||||||
|
sw.Stop();
|
||||||
|
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.NotNull(result.Data);
|
||||||
|
Assert.NotEmpty(result.Data);
|
||||||
|
var release = result.Data.FirstOrDefault();
|
||||||
|
Assert.NotNull(release);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MessageLogger_Messages(object sender, EventMessage e)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Log Level [{ e.Level }] Log Message [{ e.Message }] ");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNet.WebApi.Core" Version="5.2.7" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.WebApiCompatShim" Version="2.2.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="Moq" Version="4.16.1" />
|
<PackageReference Include="Moq" Version="4.16.1" />
|
||||||
|
|
51
Roadie.Api.Library/Caching/SystemTextCacheSerializer.cs
Normal file
51
Roadie.Api.Library/Caching/SystemTextCacheSerializer.cs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Roadie.Library.Caching
|
||||||
|
{
|
||||||
|
public sealed class SystemTextCacheSerializer : ICacheSerializer
|
||||||
|
{
|
||||||
|
private ILogger Logger { get; }
|
||||||
|
|
||||||
|
public SystemTextCacheSerializer(ILogger logger)
|
||||||
|
{
|
||||||
|
Logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Serialize(object o)
|
||||||
|
{
|
||||||
|
if (o == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return System.Text.Json.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 System.Text.Json.JsonSerializer.Deserialize<TOut>(s);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex);
|
||||||
|
}
|
||||||
|
return default(TOut);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ namespace Roadie.Library.Extensions
|
||||||
{
|
{
|
||||||
return JsonSerializer.Serialize(input, new JsonSerializerOptions
|
return JsonSerializer.Serialize(input, new JsonSerializerOptions
|
||||||
{
|
{
|
||||||
IgnoreNullValues = true,
|
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
|
||||||
WriteIndented = true
|
WriteIndented = true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
<PackageReference Include="EFCore.BulkExtensions" Version="6.3.0" />
|
<PackageReference Include="EFCore.BulkExtensions" Version="6.3.0" />
|
||||||
<PackageReference Include="FluentFTP" Version="36.1.0" />
|
<PackageReference Include="FluentFTP" Version="36.1.0" />
|
||||||
<PackageReference Include="Hashids.net" Version="1.4.1" />
|
<PackageReference Include="Hashids.net" Version="1.4.1" />
|
||||||
|
<PackageReference Include="Hqub.Last.fm" Version="2.1.0" />
|
||||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.40" />
|
<PackageReference Include="HtmlAgilityPack" Version="1.11.40" />
|
||||||
<PackageReference Include="IdSharp.Common" Version="1.0.1" />
|
<PackageReference Include="IdSharp.Common" Version="1.0.1" />
|
||||||
<PackageReference Include="IdSharp.Tagging" Version="1.0.0-rc3" />
|
<PackageReference Include="IdSharp.Tagging" Version="1.0.0-rc3" />
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<package >
|
<package >
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>$id$</id>
|
<id>$id$</id>
|
||||||
<version>$version$</version>
|
<version>1.0.1-pre</version>
|
||||||
<title>$title$</title>
|
<title>$title$</title>
|
||||||
<authors>$author$</authors>
|
<authors>$author$</authors>
|
||||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Roadie.Library.SearchEngines.MetaData.LastFm
|
||||||
|
{
|
||||||
|
public class Rootobject
|
||||||
|
{
|
||||||
|
public Album album { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Album
|
||||||
|
{
|
||||||
|
public string artist { get; set; }
|
||||||
|
|
||||||
|
public string mbid { get; set; }
|
||||||
|
|
||||||
|
public Tags tags { get; set; }
|
||||||
|
|
||||||
|
public string name { get; set; }
|
||||||
|
|
||||||
|
public Image[] image { get; set; }
|
||||||
|
|
||||||
|
public Tracks tracks { get; set; }
|
||||||
|
|
||||||
|
public string listeners { get; set; }
|
||||||
|
|
||||||
|
public string playcount { get; set; }
|
||||||
|
|
||||||
|
public string url { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Tags
|
||||||
|
{
|
||||||
|
public Tag[] tag { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Tag
|
||||||
|
{
|
||||||
|
public string url { get; set; }
|
||||||
|
|
||||||
|
public string name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Tracks
|
||||||
|
{
|
||||||
|
public Track[] track { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Track
|
||||||
|
{
|
||||||
|
public Streamable streamable { get; set; }
|
||||||
|
|
||||||
|
public int duration { get; set; }
|
||||||
|
|
||||||
|
public string url { get; set; }
|
||||||
|
|
||||||
|
public string name { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("@attr")]
|
||||||
|
public Attr attr { get; set; }
|
||||||
|
|
||||||
|
//public int? TrackNumber => string.IsNullOrWhiteSpace(attr) ? null : int.Parse(attr.Replace("\"@attr\":{\"rank\":", "").Replace("}", ""));
|
||||||
|
|
||||||
|
public int? TrackNumber => attr?.rank;
|
||||||
|
|
||||||
|
public Artist artist { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Streamable
|
||||||
|
{
|
||||||
|
public string fulltrack { get; set; }
|
||||||
|
|
||||||
|
public string text { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Attr
|
||||||
|
{
|
||||||
|
public int rank { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Artist
|
||||||
|
{
|
||||||
|
public string url { get; set; }
|
||||||
|
|
||||||
|
public string name { get; set; }
|
||||||
|
|
||||||
|
public string mbid { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Image
|
||||||
|
{
|
||||||
|
public string size { get; set; }
|
||||||
|
|
||||||
|
public string text { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
using IF.Lastfm.Core.Api;
|
using IF.Lastfm.Core.Api;
|
||||||
using IF.Lastfm.Core.Objects;
|
using IF.Lastfm.Core.Objects;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using RestSharp;
|
|
||||||
using Roadie.Library.Caching;
|
using Roadie.Library.Caching;
|
||||||
using Roadie.Library.Configuration;
|
using Roadie.Library.Configuration;
|
||||||
using Roadie.Library.Data.Context;
|
using Roadie.Library.Data.Context;
|
||||||
|
@ -18,20 +17,22 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using System.Xml.XPath;
|
using System.Xml.XPath;
|
||||||
using data = Roadie.Library.Data;
|
using static System.Net.Mime.MediaTypeNames;
|
||||||
|
|
||||||
namespace Roadie.Library.MetaData.LastFm
|
namespace Roadie.Library.MetaData.LastFm
|
||||||
{
|
{
|
||||||
public class LastFmHelper : MetaDataProviderBase, ILastFmHelper
|
public class LastFmHelper : MetaDataProviderBase, ILastFmHelper
|
||||||
{
|
{
|
||||||
private const string LastFmErrorCodeXPath = "/lfm/error/@code";
|
private const string LastFmErrorCodeXPath = "/lfm/error/@code";
|
||||||
|
|
||||||
private const string LastFmErrorXPath = "/lfm/error";
|
private const string LastFmErrorXPath = "/lfm/error";
|
||||||
|
|
||||||
private const string LastFmStatusOk = "ok";
|
private const string LastFmStatusOk = "ok";
|
||||||
|
|
||||||
private const string LastFmStatusXPath = "/lfm/@status";
|
private const string LastFmStatusXPath = "/lfm/@status";
|
||||||
|
|
||||||
public override bool IsEnabled =>
|
public override bool IsEnabled =>
|
||||||
|
@ -90,12 +91,17 @@ namespace Roadie.Library.MetaData.LastFm
|
||||||
{
|
{
|
||||||
{"token", token}
|
{"token", token}
|
||||||
};
|
};
|
||||||
var request = new RestRequest();
|
string responseXML = null;
|
||||||
request.Method = Method.Get;
|
var client = _httpClientFactory.CreateClient();
|
||||||
var client = new RestClient(BuildUrl("auth.getSession", parameters));
|
var request = new HttpRequestMessage(HttpMethod.Get, BuildUrl("auth.getSession", parameters));
|
||||||
var responseXML = await client.ExecuteAsync<string>(request).ConfigureAwait(false);
|
request.Headers.Add("User-Agent", WebHelper.UserAgent);
|
||||||
|
var response = await client.SendAsync(request).ConfigureAwait(false);
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
responseXML = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
var doc = new XmlDocument();
|
var doc = new XmlDocument();
|
||||||
doc.LoadXml(responseXML.Content);
|
doc.LoadXml(responseXML);
|
||||||
var sessionKey = doc.GetElementsByTagName("key")[0].InnerText;
|
var sessionKey = doc.GetElementsByTagName("key")[0].InnerText;
|
||||||
return new OperationResult<string>
|
return new OperationResult<string>
|
||||||
{
|
{
|
||||||
|
@ -117,36 +123,31 @@ namespace Roadie.Library.MetaData.LastFm
|
||||||
{
|
{
|
||||||
return new OperationResult<bool>("User does not have LastFM Integration setup");
|
return new OperationResult<bool>("User does not have LastFM Integration setup");
|
||||||
}
|
}
|
||||||
await Task.Run(() =>
|
var method = "track.updateNowPlaying";
|
||||||
|
var parameters = new RequestParameters
|
||||||
{
|
{
|
||||||
var method = "track.updateNowPlaying";
|
{"artist", scrobble.ArtistName},
|
||||||
var parameters = new RequestParameters
|
{"track", scrobble.TrackTitle},
|
||||||
{
|
{"album", scrobble.ReleaseTitle},
|
||||||
{"artist", scrobble.ArtistName},
|
{"duration", ((int) scrobble.TrackDuration.TotalSeconds).ToString()}
|
||||||
{"track", scrobble.TrackTitle},
|
};
|
||||||
{"album", scrobble.ReleaseTitle},
|
var url = "http://ws.audioscrobbler.com/2.0/";
|
||||||
{"duration", ((int) scrobble.TrackDuration.TotalSeconds).ToString()}
|
var signature = GenerateMethodSignature(method, parameters, user.LastFMSessionKey);
|
||||||
};
|
parameters.Add("api_sig", signature);
|
||||||
var url = "http://ws.audioscrobbler.com/2.0/";
|
|
||||||
var signature = GenerateMethodSignature(method, parameters, user.LastFMSessionKey);
|
|
||||||
parameters.Add("api_sig", signature);
|
|
||||||
|
|
||||||
ServicePointManager.Expect100Continue = false;
|
ServicePointManager.Expect100Continue = false;
|
||||||
var request = WebRequest.Create(url);
|
var client = _httpClientFactory.CreateClient();
|
||||||
request.Method = "POST";
|
XPathNavigator xp = null;
|
||||||
var postData = parameters.ToString();
|
var parametersJson = new StringContent(CacheManager.CacheSerializer.Serialize(parameters), System.Text.Encoding.UTF8, Application.Json);
|
||||||
var byteArray = System.Text.Encoding.UTF8.GetBytes(postData);
|
using (var httpResponseMessage = await client.PostAsync(url, parametersJson).ConfigureAwait(false))
|
||||||
request.ContentType = "application/x-www-form-urlencoded";
|
{
|
||||||
request.ContentLength = byteArray.Length;
|
if (httpResponseMessage.IsSuccessStatusCode)
|
||||||
using (var dataStream = request.GetRequestStream())
|
|
||||||
{
|
{
|
||||||
dataStream.Write(byteArray, 0, byteArray.Length);
|
xp = await GetResponseAsXml(httpResponseMessage).ConfigureAwait(false);
|
||||||
dataStream.Close();
|
result = true;
|
||||||
}
|
}
|
||||||
var xp = GetResponseAsXml(request);
|
}
|
||||||
Logger.LogTrace($"LastFmHelper: RoadieUser `{roadieUser}` NowPlaying `{scrobble}` LastFmResult [{xp.InnerXml}]");
|
Logger.LogTrace($"LastFmHelper: Success [{ result }] RoadieUser `{roadieUser}` NowPlaying `{scrobble}` LastFmResult [{xp.InnerXml}]");
|
||||||
result = true;
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -169,7 +170,7 @@ namespace Roadie.Library.MetaData.LastFm
|
||||||
{
|
{
|
||||||
Logger.LogTrace("LastFmHelper:PerformArtistSearch:{0}", query);
|
Logger.LogTrace("LastFmHelper:PerformArtistSearch:{0}", query);
|
||||||
var auth = new LastAuth(ApiKey.Key, ApiKey.KeySecret);
|
var auth = new LastAuth(ApiKey.Key, ApiKey.KeySecret);
|
||||||
var albumApi = new ArtistApi(auth);
|
var albumApi = new ArtistApi(auth, _httpClientFactory.CreateClient());
|
||||||
var response = await albumApi.GetInfoAsync(query).ConfigureAwait(false);
|
var response = await albumApi.GetInfoAsync(query).ConfigureAwait(false);
|
||||||
if (!response.Success)
|
if (!response.Success)
|
||||||
{
|
{
|
||||||
|
@ -205,19 +206,29 @@ namespace Roadie.Library.MetaData.LastFm
|
||||||
return new OperationResult<IEnumerable<ArtistSearchResult>>();
|
return new OperationResult<IEnumerable<ArtistSearchResult>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<OperationResult<IEnumerable<ReleaseSearchResult>>> PerformReleaseSearch(string artistName,string query, int resultsCount)
|
public async Task<OperationResult<IEnumerable<ReleaseSearchResult>>> PerformReleaseSearch(string artistName, string query, int resultsCount)
|
||||||
{
|
{
|
||||||
var cacheKey = $"uri:lastfm:releasesearch:{ artistName.ToAlphanumericName() }:{ query.ToAlphanumericName() }";
|
var cacheKey = $"uri:lastfm:releasesearch:{ artistName.ToAlphanumericName() }:{ query.ToAlphanumericName() }";
|
||||||
var data = await CacheManager.GetAsync<ReleaseSearchResult>(cacheKey, async () =>
|
var data = await CacheManager.GetAsync<ReleaseSearchResult>(cacheKey, async () =>
|
||||||
{
|
{
|
||||||
var request = new RestRequest();
|
Rootobject response = null;
|
||||||
request.Method = Method.Get;
|
var client = _httpClientFactory.CreateClient();
|
||||||
var client = new RestClient(string.Format("http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key={0}&artist={1}&album={2}&format=xml", ApiKey.Key, artistName, query));
|
var request = new HttpRequestMessage(HttpMethod.Get, $"http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key={ ApiKey.Key }&artist={ artistName }&album={ query }&format=json");
|
||||||
var responseData = await client.ExecuteAsync<lfm>(request).ConfigureAwait(false);
|
request.Headers.Add("User-Agent", WebHelper.UserAgent);
|
||||||
|
var sendResponse = await client.SendAsync(request).ConfigureAwait(false);
|
||||||
|
if (sendResponse.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var r = await sendResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
|
response = CacheManager.CacheSerializer.Deserialize<Rootobject>(r);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
ReleaseSearchResult result = null;
|
ReleaseSearchResult result = null;
|
||||||
|
|
||||||
var response = responseData != null && responseData.Data != null ? responseData.Data : null;
|
|
||||||
if (response != null && response.album != null)
|
if (response != null && response.album != null)
|
||||||
{
|
{
|
||||||
var lastFmAlbum = response.album;
|
var lastFmAlbum = response.album;
|
||||||
|
@ -229,15 +240,15 @@ namespace Roadie.Library.MetaData.LastFm
|
||||||
|
|
||||||
// No longer fetching/consuming images LastFm says is violation of ToS ; https://getsatisfaction.com/lastfm/topics/api-announcement-dac8oefw5vrxq
|
// No longer fetching/consuming images LastFm says is violation of ToS ; https://getsatisfaction.com/lastfm/topics/api-announcement-dac8oefw5vrxq
|
||||||
|
|
||||||
if (lastFmAlbum.tags != null) result.Tags = lastFmAlbum.tags.Select(x => x.name).ToList();
|
if (lastFmAlbum.tags != null) result.Tags = lastFmAlbum.tags.tag.Select(x => x.name).ToList();
|
||||||
if (lastFmAlbum.tracks != null)
|
if (lastFmAlbum.tracks != null)
|
||||||
{
|
{
|
||||||
var tracks = new List<TrackSearchResult>();
|
var tracks = new List<TrackSearchResult>();
|
||||||
foreach (var lastFmTrack in lastFmAlbum.tracks)
|
foreach (var lastFmTrack in lastFmAlbum.tracks.track)
|
||||||
{
|
{
|
||||||
tracks.Add(new TrackSearchResult
|
tracks.Add(new TrackSearchResult
|
||||||
{
|
{
|
||||||
TrackNumber = SafeParser.ToNumber<short?>(lastFmTrack.rank),
|
TrackNumber = SafeParser.ToNumber<short?>(lastFmTrack.TrackNumber),
|
||||||
Title = lastFmTrack.name,
|
Title = lastFmTrack.name,
|
||||||
Duration = SafeParser.ToNumber<int?>(lastFmTrack.duration),
|
Duration = SafeParser.ToNumber<int?>(lastFmTrack.duration),
|
||||||
Urls = string.IsNullOrEmpty(lastFmTrack.url) ? new[] { lastFmTrack.url } : null
|
Urls = string.IsNullOrEmpty(lastFmTrack.url) ? new[] { lastFmTrack.url } : null
|
||||||
|
@ -282,7 +293,9 @@ namespace Roadie.Library.MetaData.LastFm
|
||||||
var user = DbContext.Users.FirstOrDefault(x => x.RoadieId == roadieUser.UserId);
|
var user = DbContext.Users.FirstOrDefault(x => x.RoadieId == roadieUser.UserId);
|
||||||
|
|
||||||
if (user == null || string.IsNullOrEmpty(user.LastFMSessionKey))
|
if (user == null || string.IsNullOrEmpty(user.LastFMSessionKey))
|
||||||
|
{
|
||||||
return new OperationResult<bool>("User does not have LastFM Integration setup");
|
return new OperationResult<bool>("User does not have LastFM Integration setup");
|
||||||
|
}
|
||||||
var parameters = new RequestParameters
|
var parameters = new RequestParameters
|
||||||
{
|
{
|
||||||
{"artist", scrobble.ArtistName},
|
{"artist", scrobble.ArtistName},
|
||||||
|
@ -299,19 +312,17 @@ namespace Roadie.Library.MetaData.LastFm
|
||||||
parameters.Add("api_sig", signature);
|
parameters.Add("api_sig", signature);
|
||||||
|
|
||||||
ServicePointManager.Expect100Continue = false;
|
ServicePointManager.Expect100Continue = false;
|
||||||
var request = WebRequest.Create(url);
|
var client = _httpClientFactory.CreateClient();
|
||||||
request.Method = "POST";
|
XPathNavigator xp = null;
|
||||||
var postData = parameters.ToString();
|
var parametersJson = new StringContent(CacheManager.CacheSerializer.Serialize(parameters), System.Text.Encoding.UTF8, Application.Json);
|
||||||
var byteArray = System.Text.Encoding.UTF8.GetBytes(postData);
|
using (var httpResponseMessage = await client.PostAsync(url, parametersJson).ConfigureAwait(false))
|
||||||
request.ContentType = "application/x-www-form-urlencoded";
|
|
||||||
request.ContentLength = byteArray.Length;
|
|
||||||
using (var dataStream = request.GetRequestStream())
|
|
||||||
{
|
{
|
||||||
dataStream.Write(byteArray, 0, byteArray.Length);
|
if (httpResponseMessage.IsSuccessStatusCode)
|
||||||
dataStream.Close();
|
{
|
||||||
|
xp = await GetResponseAsXml(httpResponseMessage).ConfigureAwait(false);
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var xp = GetResponseAsXml(request);
|
|
||||||
Logger.LogTrace($"LastFmHelper: RoadieUser `{roadieUser}` Scrobble `{scrobble}` LastFmResult [{xp.InnerXml}]");
|
Logger.LogTrace($"LastFmHelper: RoadieUser `{roadieUser}` Scrobble `{scrobble}` LastFmResult [{xp.InnerXml}]");
|
||||||
result = true;
|
result = true;
|
||||||
}
|
}
|
||||||
|
@ -389,7 +400,7 @@ namespace Roadie.Library.MetaData.LastFm
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected internal virtual XPathNavigator GetResponseAsXml(WebRequest request)
|
protected internal virtual XPathNavigator GetResponseXml(HttpWebRequest request)
|
||||||
{
|
{
|
||||||
WebResponse response;
|
WebResponse response;
|
||||||
XPathNavigator navigator;
|
XPathNavigator navigator;
|
||||||
|
@ -413,6 +424,45 @@ namespace Roadie.Library.MetaData.LastFm
|
||||||
return navigator;
|
return navigator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected internal virtual async Task<XPathNavigator> GetResponseAsXml(HttpResponseMessage request)
|
||||||
|
{
|
||||||
|
XPathNavigator navigator;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
navigator = await GetXpathDocumentFromResponse(request);
|
||||||
|
CheckLastFmStatus(navigator);
|
||||||
|
}
|
||||||
|
catch (WebException exception)
|
||||||
|
{
|
||||||
|
var response = exception.Response;
|
||||||
|
|
||||||
|
XPathNavigator document;
|
||||||
|
TryGetXpathDocumentFromResponse(response, out document);
|
||||||
|
|
||||||
|
if (document != null) CheckLastFmStatus(document, exception);
|
||||||
|
throw; // throw even if Last.fm status is OK
|
||||||
|
}
|
||||||
|
|
||||||
|
return navigator;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual async Task<XPathNavigator> GetXpathDocumentFromResponse(HttpResponseMessage response)
|
||||||
|
{
|
||||||
|
using (var stream = await response.Content.ReadAsStreamAsync())
|
||||||
|
{
|
||||||
|
if (stream == null) throw new InvalidOperationException("Response Stream is null");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new XPathDocument(stream).CreateNavigator();
|
||||||
|
}
|
||||||
|
catch (XmlException exception)
|
||||||
|
{
|
||||||
|
throw new XmlException("Could not read HTTP Response as XML", exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual XPathNavigator GetXpathDocumentFromResponse(WebResponse response)
|
protected virtual XPathNavigator GetXpathDocumentFromResponse(WebResponse response)
|
||||||
{
|
{
|
||||||
using (var stream = response.GetResponseStream())
|
using (var stream = response.GetResponseStream())
|
||||||
|
|
|
@ -25,7 +25,7 @@ namespace Roadie.Library.MetaData.MusicBrainz
|
||||||
IHttpClientFactory httpClientFactory)
|
IHttpClientFactory httpClientFactory)
|
||||||
: base(configuration, cacheManager, logger, httpClientFactory)
|
: base(configuration, cacheManager, logger, httpClientFactory)
|
||||||
{
|
{
|
||||||
Repository = new MusicBrainzRepository(configuration, logger);
|
Repository = new MusicBrainzRepository(configuration, logger, httpClientFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<AudioMetaData>> MusicBrainzReleaseTracksAsync(string artistName, string releaseTitle)
|
public async Task<IEnumerable<AudioMetaData>> MusicBrainzReleaseTracksAsync(string artistName, string releaseTitle)
|
||||||
|
@ -61,7 +61,7 @@ namespace Roadie.Library.MetaData.MusicBrainz
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now get The Release Details
|
// Now get The Release Details
|
||||||
release = await MusicBrainzRequestHelper.GetAsync<Release>(MusicBrainzRequestHelper.CreateLookupUrl("release", ReleaseResult.MusicBrainzId, "recordings")).ConfigureAwait(false);
|
release = await MusicBrainzRequestHelper.GetAsync<Release>(_httpClientFactory, MusicBrainzRequestHelper.CreateLookupUrl("release", ReleaseResult.MusicBrainzId, "recordings")).ConfigureAwait(false);
|
||||||
if (release == null) return null;
|
if (release == null) return null;
|
||||||
CacheManager.Add(ReleaseCacheKey, release);
|
CacheManager.Add(ReleaseCacheKey, release);
|
||||||
}
|
}
|
||||||
|
@ -300,7 +300,7 @@ namespace Roadie.Library.MetaData.MusicBrainz
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<CoverArtArchivesResult> CoverArtForMusicBrainzReleaseByIdAsync(string musicBrainzId) => MusicBrainzRequestHelper.GetAsync<CoverArtArchivesResult>(MusicBrainzRequestHelper.CreateCoverArtReleaseUrl(musicBrainzId));
|
private Task<CoverArtArchivesResult> CoverArtForMusicBrainzReleaseByIdAsync(string musicBrainzId) => MusicBrainzRequestHelper.GetAsync<CoverArtArchivesResult>(_httpClientFactory, MusicBrainzRequestHelper.CreateCoverArtReleaseUrl(musicBrainzId));
|
||||||
|
|
||||||
private async Task<IEnumerable<Release>> ReleasesForArtistAsync(string artist, string artistMusicBrainzId = null)
|
private async Task<IEnumerable<Release>> ReleasesForArtistAsync(string artist, string artistMusicBrainzId = null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -13,9 +13,15 @@ namespace Roadie.Library.MetaData.MusicBrainz
|
||||||
public class MusicBrainzRepository
|
public class MusicBrainzRepository
|
||||||
{
|
{
|
||||||
private string FileName { get; }
|
private string FileName { get; }
|
||||||
|
|
||||||
private ILogger<MusicBrainzProvider> Logger { get; }
|
private ILogger<MusicBrainzProvider> Logger { get; }
|
||||||
|
|
||||||
public MusicBrainzRepository(IRoadieSettings configuration, ILogger<MusicBrainzProvider> logger)
|
private IHttpClientFactory HttpClientFactory { get; }
|
||||||
|
|
||||||
|
public MusicBrainzRepository(
|
||||||
|
IRoadieSettings configuration,
|
||||||
|
ILogger<MusicBrainzProvider> logger,
|
||||||
|
IHttpClientFactory httpClientFactory)
|
||||||
{
|
{
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
var location = System.Reflection.Assembly.GetEntryAssembly().Location;
|
var location = System.Reflection.Assembly.GetEntryAssembly().Location;
|
||||||
|
@ -25,6 +31,7 @@ namespace Roadie.Library.MetaData.MusicBrainz
|
||||||
Directory.CreateDirectory(directory);
|
Directory.CreateDirectory(directory);
|
||||||
}
|
}
|
||||||
FileName = Path.Combine(directory, "MusicBrainzRespository.db");
|
FileName = Path.Combine(directory, "MusicBrainzRespository.db");
|
||||||
|
HttpClientFactory = httpClientFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -47,14 +54,14 @@ namespace Roadie.Library.MetaData.MusicBrainz
|
||||||
if (artist == null)
|
if (artist == null)
|
||||||
{
|
{
|
||||||
// Perform a query to get the MbId for the Name
|
// Perform a query to get the MbId for the Name
|
||||||
var artistResult = await MusicBrainzRequestHelper.GetAsync<ArtistResult>(MusicBrainzRequestHelper.CreateSearchTemplate("artist", name, resultsCount ?? 1, 0)).ConfigureAwait(false);
|
var artistResult = await MusicBrainzRequestHelper.GetAsync<ArtistResult>(HttpClientFactory, MusicBrainzRequestHelper.CreateSearchTemplate("artist", name, resultsCount ?? 1, 0)).ConfigureAwait(false);
|
||||||
if (artistResult == null || artistResult.artists == null || !artistResult.artists.Any() || artistResult.count < 1)
|
if (artistResult == null || artistResult.artists == null || !artistResult.artists.Any() || artistResult.count < 1)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
var mbId = artistResult.artists.First().id;
|
var mbId = artistResult.artists.First().id;
|
||||||
// Now perform a detail request to get the details by the MbId
|
// Now perform a detail request to get the details by the MbId
|
||||||
result = await MusicBrainzRequestHelper.GetAsync<Artist>(MusicBrainzRequestHelper.CreateLookupUrl("artist", mbId, "aliases+tags+genres+url-rels")).ConfigureAwait(false);
|
result = await MusicBrainzRequestHelper.GetAsync<Artist>(HttpClientFactory, MusicBrainzRequestHelper.CreateLookupUrl("artist", mbId, "aliases+tags+genres+url-rels")).ConfigureAwait(false);
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
col.Insert(new RepositoryArtist
|
col.Insert(new RepositoryArtist
|
||||||
|
@ -112,7 +119,7 @@ namespace Roadie.Library.MetaData.MusicBrainz
|
||||||
var pageSize = 50;
|
var pageSize = 50;
|
||||||
var page = 0;
|
var page = 0;
|
||||||
var url = MusicBrainzRequestHelper.CreateArtistBrowseTemplate(artistMbId, pageSize, 0);
|
var url = MusicBrainzRequestHelper.CreateArtistBrowseTemplate(artistMbId, pageSize, 0);
|
||||||
var mbReleaseBrowseResult = await MusicBrainzRequestHelper.GetAsync<ReleaseBrowseResult>(url).ConfigureAwait(false);
|
var mbReleaseBrowseResult = await MusicBrainzRequestHelper.GetAsync<ReleaseBrowseResult>(HttpClientFactory, url).ConfigureAwait(false);
|
||||||
var totalReleases = mbReleaseBrowseResult != null ? mbReleaseBrowseResult.releasecount : 0;
|
var totalReleases = mbReleaseBrowseResult != null ? mbReleaseBrowseResult.releasecount : 0;
|
||||||
var totalPages = Math.Ceiling((decimal)totalReleases / pageSize);
|
var totalPages = Math.Ceiling((decimal)totalReleases / pageSize);
|
||||||
var fetchResult = new List<Release>();
|
var fetchResult = new List<Release>();
|
||||||
|
@ -123,7 +130,7 @@ namespace Roadie.Library.MetaData.MusicBrainz
|
||||||
fetchResult.AddRange(mbReleaseBrowseResult.releases.Where(x => !string.IsNullOrEmpty(x.date)));
|
fetchResult.AddRange(mbReleaseBrowseResult.releases.Where(x => !string.IsNullOrEmpty(x.date)));
|
||||||
}
|
}
|
||||||
page++;
|
page++;
|
||||||
mbReleaseBrowseResult = await MusicBrainzRequestHelper.GetAsync<ReleaseBrowseResult>(MusicBrainzRequestHelper.CreateArtistBrowseTemplate(artistMbId, pageSize, pageSize * page)).ConfigureAwait(false);
|
mbReleaseBrowseResult = await MusicBrainzRequestHelper.GetAsync<ReleaseBrowseResult>(HttpClientFactory, MusicBrainzRequestHelper.CreateArtistBrowseTemplate(artistMbId, pageSize, pageSize * page)).ConfigureAwait(false);
|
||||||
} while (page < totalPages);
|
} while (page < totalPages);
|
||||||
var releasesToInsert = fetchResult.GroupBy(x => x.title).Select(x => x.OrderBy(x => x.date).First()).OrderBy(x => x.date).ThenBy(x => x.title);
|
var releasesToInsert = fetchResult.GroupBy(x => x.title).Select(x => x.OrderBy(x => x.date).First()).OrderBy(x => x.date).ThenBy(x => x.title);
|
||||||
col.InsertBulk(releasesToInsert.Where(x => x != null).Select(x => new RepositoryRelease
|
col.InsertBulk(releasesToInsert.Where(x => x != null).Select(x => new RepositoryRelease
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -11,28 +12,23 @@ namespace Roadie.Library.MetaData.MusicBrainz
|
||||||
public static class MusicBrainzRequestHelper
|
public static class MusicBrainzRequestHelper
|
||||||
{
|
{
|
||||||
private const string LookupTemplate = "{0}/{1}/?inc={2}&fmt=json&limit=100";
|
private const string LookupTemplate = "{0}/{1}/?inc={2}&fmt=json&limit=100";
|
||||||
|
|
||||||
private const int MaxRetries = 6;
|
private const int MaxRetries = 6;
|
||||||
|
|
||||||
private const string ReleaseBrowseTemplate = "release?artist={0}&limit={1}&offset={2}&fmt=json&inc={3}";
|
private const string ReleaseBrowseTemplate = "release?artist={0}&limit={1}&offset={2}&fmt=json&inc={3}";
|
||||||
|
|
||||||
private const string SearchTemplate = "{0}?query={1}&limit={2}&offset={3}&fmt=json";
|
private const string SearchTemplate = "{0}?query={1}&limit={2}&offset={3}&fmt=json";
|
||||||
|
|
||||||
private const string WebServiceUrl = "http://musicbrainz.org/ws/2/";
|
private const string WebServiceUrl = "http://musicbrainz.org/ws/2/";
|
||||||
|
|
||||||
internal static string CreateArtistBrowseTemplate(string id, int limit, int offset)
|
internal static string CreateArtistBrowseTemplate(string id, int limit, int offset) => string.Format("{0}{1}", WebServiceUrl, string.Format(ReleaseBrowseTemplate, id, limit, offset, "labels+aliases+recordings+release-groups+media+url-rels+tags+genres"));
|
||||||
{
|
|
||||||
return string.Format("{0}{1}", WebServiceUrl, string.Format(ReleaseBrowseTemplate, id, limit, offset, "labels+aliases+recordings+release-groups+media+url-rels+tags+genres"));
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static string CreateCoverArtReleaseUrl(string musicBrainzId)
|
internal static string CreateCoverArtReleaseUrl(string musicBrainzId) => string.Format("http://coverartarchive.org/release/{0}", musicBrainzId);
|
||||||
{
|
|
||||||
return string.Format("http://coverartarchive.org/release/{0}", musicBrainzId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a webservice lookup template.
|
/// Creates a webservice lookup template.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string CreateLookupUrl(string entity, string mbid, string inc)
|
internal static string CreateLookupUrl(string entity, string mbid, string inc) => string.Format("{0}{1}", WebServiceUrl, string.Format(LookupTemplate, entity, mbid, inc));
|
||||||
{
|
|
||||||
return string.Format("{0}{1}", WebServiceUrl, string.Format(LookupTemplate, entity, mbid, inc));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a webservice search template.
|
/// Creates a webservice search template.
|
||||||
|
@ -44,7 +40,7 @@ namespace Roadie.Library.MetaData.MusicBrainz
|
||||||
return string.Format("{0}{1}", WebServiceUrl, string.Format(SearchTemplate, entity, query, limit, offset));
|
return string.Format("{0}{1}", WebServiceUrl, string.Format(SearchTemplate, entity, query, limit, offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static async Task<T> GetAsync<T>(string url, bool withoutMetadata = true)
|
internal static async Task<T> GetAsync<T>(IHttpClientFactory httpClientFactory, string url, bool withoutMetadata = true)
|
||||||
{
|
{
|
||||||
var tryCount = 0;
|
var tryCount = 0;
|
||||||
var result = default(T);
|
var result = default(T);
|
||||||
|
@ -53,20 +49,20 @@ namespace Roadie.Library.MetaData.MusicBrainz
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var webClient = new WebClient())
|
var client = httpClientFactory.CreateClient();
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||||
|
request.Headers.Add("User-Agent", WebHelper.UserAgent);
|
||||||
|
var response = await client.SendAsync(request).ConfigureAwait(false);
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
webClient.Headers.Add("user-agent", WebHelper.UserAgent);
|
downloadedString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
downloadedString = await webClient.DownloadStringTaskAsync(new Uri(url)).ConfigureAwait(false);
|
result = JsonSerializer.Deserialize<T>(downloadedString);
|
||||||
if (!string.IsNullOrWhiteSpace(downloadedString))
|
|
||||||
{
|
|
||||||
result = JsonSerializer.Deserialize<T>(downloadedString);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (WebException ex)
|
catch (WebException ex)
|
||||||
{
|
{
|
||||||
var response = ex.Response as HttpWebResponse;
|
var response = ex.Response as HttpWebResponse;
|
||||||
if(response?.StatusCode == HttpStatusCode.NotFound)
|
if (response?.StatusCode == HttpStatusCode.NotFound)
|
||||||
{
|
{
|
||||||
Trace.WriteLine($"GetAsync: 404 Response For url [{ url }]", "Warning");
|
Trace.WriteLine($"GetAsync: 404 Response For url [{ url }]", "Warning");
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -1,60 +1,46 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
namespace Roadie.Library.Utility
|
namespace Roadie.Library.Utility
|
||||||
{
|
{
|
||||||
public static class EncryptionHelper
|
public static class EncryptionHelper
|
||||||
{
|
{
|
||||||
|
|
||||||
public static string Decrypt(string cyphertext, string key)
|
public static string Decrypt(string cyphertext, string key)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(cyphertext) || string.IsNullOrEmpty(key)) return null;
|
if (string.IsNullOrEmpty(cyphertext) || string.IsNullOrEmpty(key))
|
||||||
if (key.Length > 16) key = key.Substring(0, 16);
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (key.Length > 16)
|
||||||
|
{
|
||||||
|
key = key.Substring(0, 16);
|
||||||
|
}
|
||||||
return Decrypt(Convert.FromBase64String(cyphertext), System.Text.Encoding.UTF8.GetBytes(key));
|
return Decrypt(Convert.FromBase64String(cyphertext), System.Text.Encoding.UTF8.GetBytes(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string Decrypt(byte[] cyphertext, byte[] key)
|
public static string Decrypt(byte[] encryptedData, byte[] key)
|
||||||
{
|
{
|
||||||
using (var ms = new MemoryStream(cyphertext))
|
return SymmetricEncryptor.DecryptToString(encryptedData, key);
|
||||||
using (var desObj = Rijndael.Create())
|
|
||||||
{
|
|
||||||
desObj.Key = key;
|
|
||||||
var iv = new byte[16];
|
|
||||||
var offset = 0;
|
|
||||||
while (offset < iv.Length) offset += ms.Read(iv, offset, iv.Length - offset);
|
|
||||||
desObj.IV = iv;
|
|
||||||
using (var cs = new CryptoStream(ms, desObj.CreateDecryptor(), CryptoStreamMode.Read))
|
|
||||||
using (var sr = new StreamReader(cs, System.Text.Encoding.UTF8))
|
|
||||||
{
|
|
||||||
return sr.ReadToEnd();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string Encrypt(string plaintext, string key)
|
public static string Encrypt(string plaintext, string key)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(plaintext) || string.IsNullOrEmpty(key)) return null;
|
if (string.IsNullOrEmpty(plaintext) || string.IsNullOrEmpty(key))
|
||||||
if (key.Length > 16) key = key.Substring(0, 16);
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (key.Length > 16)
|
||||||
|
{
|
||||||
|
key = key.Substring(0, 16);
|
||||||
|
}
|
||||||
return Convert.ToBase64String(Encrypt(plaintext, System.Text.Encoding.UTF8.GetBytes(key)));
|
return Convert.ToBase64String(Encrypt(plaintext, System.Text.Encoding.UTF8.GetBytes(key)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] Encrypt(string plaintext, byte[] key)
|
public static byte[] Encrypt(string toEncrypt, byte[] key)
|
||||||
{
|
{
|
||||||
using (var desObj = Rijndael.Create())
|
return SymmetricEncryptor.EncryptString(toEncrypt, key);
|
||||||
{
|
|
||||||
desObj.Key = key;
|
|
||||||
using (var ms = new MemoryStream())
|
|
||||||
{
|
|
||||||
ms.Write(desObj.IV, 0, desObj.IV.Length);
|
|
||||||
using (var cs = new CryptoStream(ms, desObj.CreateEncryptor(), CryptoStreamMode.Write))
|
|
||||||
{
|
|
||||||
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plaintext);
|
|
||||||
cs.Write(plainTextBytes, 0, plainTextBytes.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ms.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
167
Roadie.Api.Library/Utility/SymmetricEncryptor.cs
Normal file
167
Roadie.Api.Library/Utility/SymmetricEncryptor.cs
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
namespace Roadie.Library.Utility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// AES Encryption class, from https://tomrucki.com/posts/aes-encryption-in-csharp/
|
||||||
|
/// </summary>
|
||||||
|
public static class SymmetricEncryptor
|
||||||
|
{
|
||||||
|
private const int AesBlockByteSize = 128 / 8;
|
||||||
|
|
||||||
|
private const int PasswordSaltByteSize = 128 / 8;
|
||||||
|
|
||||||
|
private const int PasswordByteSize = 256 / 8;
|
||||||
|
|
||||||
|
private const int PasswordIterationCount = 100_000;
|
||||||
|
|
||||||
|
private const int SignatureByteSize = 256 / 8;
|
||||||
|
|
||||||
|
private const int MinimumEncryptedMessageByteSize =
|
||||||
|
PasswordSaltByteSize + // auth salt
|
||||||
|
PasswordSaltByteSize + // key salt
|
||||||
|
AesBlockByteSize + // IV
|
||||||
|
AesBlockByteSize + // cipher text min length
|
||||||
|
SignatureByteSize; // signature tag
|
||||||
|
|
||||||
|
private static readonly System.Text.Encoding StringEncoding = System.Text.Encoding.UTF8;
|
||||||
|
|
||||||
|
private static readonly RandomNumberGenerator Random = RandomNumberGenerator.Create();
|
||||||
|
|
||||||
|
public static byte[] EncryptString(string toEncrypt, byte[] password) => EncryptString(toEncrypt, System.Text.Encoding.UTF8.GetString(password));
|
||||||
|
|
||||||
|
public static byte[] EncryptString(string toEncrypt, string password)
|
||||||
|
{
|
||||||
|
// encrypt
|
||||||
|
var keySalt = GenerateRandomBytes(PasswordSaltByteSize);
|
||||||
|
var key = GetKey(password, keySalt);
|
||||||
|
var iv = GenerateRandomBytes(AesBlockByteSize);
|
||||||
|
|
||||||
|
byte[] cipherText;
|
||||||
|
using (var aes = CreateAes())
|
||||||
|
using (var encryptor = aes.CreateEncryptor(key, iv))
|
||||||
|
{
|
||||||
|
var plainText = StringEncoding.GetBytes(toEncrypt);
|
||||||
|
cipherText = encryptor
|
||||||
|
.TransformFinalBlock(plainText, 0, plainText.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// sign
|
||||||
|
var authKeySalt = GenerateRandomBytes(PasswordSaltByteSize);
|
||||||
|
var authKey = GetKey(password, authKeySalt);
|
||||||
|
|
||||||
|
var result = MergeArrays(
|
||||||
|
additionalCapacity: SignatureByteSize,
|
||||||
|
authKeySalt, keySalt, iv, cipherText);
|
||||||
|
|
||||||
|
using (var hmac = new HMACSHA256(authKey))
|
||||||
|
{
|
||||||
|
var payloadToSignLength = result.Length - SignatureByteSize;
|
||||||
|
var signatureTag = hmac.ComputeHash(result, 0, payloadToSignLength);
|
||||||
|
signatureTag.CopyTo(result, payloadToSignLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string DecryptToString(byte[] encryptedData, byte[] password) => DecryptToString(encryptedData, System.Text.Encoding.UTF8.GetString(password));
|
||||||
|
|
||||||
|
public static string DecryptToString(byte[] encryptedData, string password)
|
||||||
|
{
|
||||||
|
if (encryptedData is null
|
||||||
|
|| encryptedData.Length < MinimumEncryptedMessageByteSize)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid length of encrypted data");
|
||||||
|
}
|
||||||
|
|
||||||
|
var authKeySalt = encryptedData
|
||||||
|
.AsSpan(0, PasswordSaltByteSize).ToArray();
|
||||||
|
var keySalt = encryptedData
|
||||||
|
.AsSpan(PasswordSaltByteSize, PasswordSaltByteSize).ToArray();
|
||||||
|
var iv = encryptedData
|
||||||
|
.AsSpan(2 * PasswordSaltByteSize, AesBlockByteSize).ToArray();
|
||||||
|
var signatureTag = encryptedData
|
||||||
|
.AsSpan(encryptedData.Length - SignatureByteSize, SignatureByteSize).ToArray();
|
||||||
|
|
||||||
|
var cipherTextIndex = authKeySalt.Length + keySalt.Length + iv.Length;
|
||||||
|
var cipherTextLength =
|
||||||
|
encryptedData.Length - cipherTextIndex - signatureTag.Length;
|
||||||
|
|
||||||
|
var authKey = GetKey(password, authKeySalt);
|
||||||
|
var key = GetKey(password, keySalt);
|
||||||
|
|
||||||
|
// verify signature
|
||||||
|
using (var hmac = new HMACSHA256(authKey))
|
||||||
|
{
|
||||||
|
var payloadToSignLength = encryptedData.Length - SignatureByteSize;
|
||||||
|
var signatureTagExpected = hmac
|
||||||
|
.ComputeHash(encryptedData, 0, payloadToSignLength);
|
||||||
|
|
||||||
|
// constant time checking to prevent timing attacks
|
||||||
|
var signatureVerificationResult = 0;
|
||||||
|
for (int i = 0; i < signatureTag.Length; i++)
|
||||||
|
{
|
||||||
|
signatureVerificationResult |= signatureTag[i] ^ signatureTagExpected[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signatureVerificationResult != 0)
|
||||||
|
{
|
||||||
|
throw new CryptographicException("Invalid signature");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decrypt
|
||||||
|
using (var aes = CreateAes())
|
||||||
|
{
|
||||||
|
using (var encryptor = aes.CreateDecryptor(key, iv))
|
||||||
|
{
|
||||||
|
var decryptedBytes = encryptor
|
||||||
|
.TransformFinalBlock(encryptedData, cipherTextIndex, cipherTextLength);
|
||||||
|
return StringEncoding.GetString(decryptedBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Aes CreateAes()
|
||||||
|
{
|
||||||
|
var aes = Aes.Create();
|
||||||
|
aes.Mode = CipherMode.CBC;
|
||||||
|
aes.Padding = PaddingMode.PKCS7;
|
||||||
|
return aes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] GetKey(string password, byte[] passwordSalt)
|
||||||
|
{
|
||||||
|
var keyBytes = StringEncoding.GetBytes(password);
|
||||||
|
|
||||||
|
using (var derivator = new Rfc2898DeriveBytes(
|
||||||
|
keyBytes, passwordSalt,
|
||||||
|
PasswordIterationCount, HashAlgorithmName.SHA256))
|
||||||
|
{
|
||||||
|
return derivator.GetBytes(PasswordByteSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] GenerateRandomBytes(int numberOfBytes)
|
||||||
|
{
|
||||||
|
var randomBytes = new byte[numberOfBytes];
|
||||||
|
Random.GetBytes(randomBytes);
|
||||||
|
return randomBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] MergeArrays(int additionalCapacity = 0, params byte[][] arrays)
|
||||||
|
{
|
||||||
|
var merged = new byte[arrays.Sum(a => a.Length) + additionalCapacity];
|
||||||
|
var mergeIndex = 0;
|
||||||
|
for (int i = 0; i < arrays.GetLength(0); i++)
|
||||||
|
{
|
||||||
|
arrays[i].CopyTo(merged, mergeIndex);
|
||||||
|
mergeIndex += arrays[i].Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -658,9 +658,7 @@ namespace Roadie.Api.Services
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Perform checks/setup on start of application
|
|
||||||
/// </summary>
|
|
||||||
public void PerformStartUpTasks()
|
public void PerformStartUpTasks()
|
||||||
{
|
{
|
||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
|
|
|
@ -31,6 +31,9 @@ namespace Roadie.Api.Services
|
||||||
|
|
||||||
Task<OperationResult<Dictionary<string, List<string>>>> MissingCollectionReleasesAsync(User user);
|
Task<OperationResult<Dictionary<string, List<string>>>> MissingCollectionReleasesAsync(User user);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Perform checks/setup on start of application
|
||||||
|
/// </summary>
|
||||||
void PerformStartUpTasks();
|
void PerformStartUpTasks();
|
||||||
|
|
||||||
Task<OperationResult<bool>> ScanAllCollectionsAsync(User user, bool isReadOnly = false, bool doPurgeFirst = false);
|
Task<OperationResult<bool>> ScanAllCollectionsAsync(User user, bool isReadOnly = false, bool doPurgeFirst = false);
|
||||||
|
|
|
@ -17,7 +17,6 @@ using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Roadie.Api.Controllers
|
namespace Roadie.Api.Controllers
|
||||||
|
@ -28,7 +27,6 @@ namespace Roadie.Api.Controllers
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public class AccountController : ControllerBase
|
public class AccountController : ControllerBase
|
||||||
{
|
{
|
||||||
|
|
||||||
private IAdminService AdminService { get; }
|
private IAdminService AdminService { get; }
|
||||||
|
|
||||||
private string BaseUrl
|
private string BaseUrl
|
||||||
|
@ -65,9 +63,13 @@ namespace Roadie.Api.Controllers
|
||||||
private IRoadieSettings RoadieSettings { get; }
|
private IRoadieSettings RoadieSettings { get; }
|
||||||
|
|
||||||
private string _baseUrl;
|
private string _baseUrl;
|
||||||
|
|
||||||
private readonly ILogger<AccountController> Logger;
|
private readonly ILogger<AccountController> Logger;
|
||||||
|
|
||||||
private readonly SignInManager<User> SignInManager;
|
private readonly SignInManager<User> SignInManager;
|
||||||
|
|
||||||
private readonly ITokenService TokenService;
|
private readonly ITokenService TokenService;
|
||||||
|
|
||||||
private readonly UserManager<User> UserManager;
|
private readonly UserManager<User> UserManager;
|
||||||
|
|
||||||
public AccountController(
|
public AccountController(
|
||||||
|
|
|
@ -12,7 +12,6 @@ using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using models = Roadie.Library.Models.Users;
|
using models = Roadie.Library.Models.Users;
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ namespace Roadie.Api.Controllers
|
||||||
[Produces("application/json")]
|
[Produces("application/json")]
|
||||||
[Route("images")]
|
[Route("images")]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
// [Authorize]
|
|
||||||
public class ImageController : EntityControllerBase
|
public class ImageController : EntityControllerBase
|
||||||
{
|
{
|
||||||
private IImageService ImageService { get; }
|
private IImageService ImageService { get; }
|
||||||
|
|
|
@ -110,7 +110,7 @@ namespace Roadie.Api.Controllers
|
||||||
[ProducesResponseType(200)]
|
[ProducesResponseType(200)]
|
||||||
public async Task<IActionResult> List([FromQuery] PagedRequest request, string inc)
|
public async Task<IActionResult> List([FromQuery] PagedRequest request, string inc)
|
||||||
{
|
{
|
||||||
var result = await PlaylistService.ListAsync(roadieUser: await CurrentUserModel().ConfigureAwait(false), request: request).ConfigureAwait(false);
|
var result = await PlaylistService.ListAsync(roadieUser: await CurrentUserModel().ConfigureAwait(false), request: request).ConfigureAwait(false);
|
||||||
if (!result.IsSuccess)
|
if (!result.IsSuccess)
|
||||||
{
|
{
|
||||||
return StatusCode((int)HttpStatusCode.InternalServerError);
|
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||||
|
|
|
@ -14,7 +14,6 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using User = Roadie.Library.Models.Users.User;
|
using User = Roadie.Library.Models.Users.User;
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,9 @@
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Roadie.Api.Services;
|
|
||||||
using Roadie.Library.Caching;
|
using Roadie.Library.Caching;
|
||||||
using Roadie.Library.Configuration;
|
using Roadie.Library.Configuration;
|
||||||
using Roadie.Library.Identity;
|
using Roadie.Library.Identity;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Roadie.Api.Controllers
|
namespace Roadie.Api.Controllers
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,6 +22,7 @@ namespace Roadie.Api.Controllers
|
||||||
public class UserController : EntityControllerBase
|
public class UserController : EntityControllerBase
|
||||||
{
|
{
|
||||||
private IHttpContext RoadieHttpContext { get; }
|
private IHttpContext RoadieHttpContext { get; }
|
||||||
|
|
||||||
private IUserService UserService { get; }
|
private IUserService UserService { get; }
|
||||||
|
|
||||||
private readonly ITokenService TokenService;
|
private readonly ITokenService TokenService;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Roadie.Api
|
namespace Roadie.Api
|
||||||
|
|
|
@ -10,9 +10,9 @@ using System.Threading.Tasks;
|
||||||
namespace Roadie.Api.ModelBinding
|
namespace Roadie.Api.ModelBinding
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is needed as some clienst post some get, some query string some body post.
|
/// This is needed as some clients post some get, some query string some body post.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class SubsonicRequestBinder : IModelBinder
|
internal class SubsonicRequestBinder : IModelBinder
|
||||||
{
|
{
|
||||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,6 +5,7 @@ namespace Roadie.Api.Models
|
||||||
public class LoginModel
|
public class LoginModel
|
||||||
{
|
{
|
||||||
[Required] public string Password { get; set; }
|
[Required] public string Password { get; set; }
|
||||||
|
|
||||||
[Required] public string Username { get; set; }
|
[Required] public string Username { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,13 +4,11 @@ namespace Roadie.Api.Models
|
||||||
{
|
{
|
||||||
public class ResetPasswordModel : LoginModel
|
public class ResetPasswordModel : LoginModel
|
||||||
{
|
{
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
[Compare(nameof(Password))]
|
[Compare(nameof(Password))]
|
||||||
public string PasswordConfirmation { get; set; }
|
public string PasswordConfirmation { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
public string Token { get; set; }
|
public string Token { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -16,7 +16,6 @@ using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using Polly;
|
|
||||||
using Roadie.Api.Hubs;
|
using Roadie.Api.Hubs;
|
||||||
using Roadie.Api.ModelBinding;
|
using Roadie.Api.ModelBinding;
|
||||||
using Roadie.Api.Services;
|
using Roadie.Api.Services;
|
||||||
|
@ -46,7 +45,6 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net.Http;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
@ -64,7 +62,7 @@ namespace Roadie.Api
|
||||||
TypeAdapterConfig.GlobalSettings.Default.PreserveReference(true);
|
TypeAdapterConfig.GlobalSettings.Default.PreserveReference(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IAdminService adminService)
|
||||||
{
|
{
|
||||||
if (env.IsDevelopment())
|
if (env.IsDevelopment())
|
||||||
{
|
{
|
||||||
|
@ -102,6 +100,8 @@ namespace Roadie.Api
|
||||||
endpoints.MapControllers();
|
endpoints.MapControllers();
|
||||||
endpoints.MapDefaultControllerRoute();
|
endpoints.MapDefaultControllerRoute();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
adminService.PerformStartUpTasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to add services to the container.
|
// This method gets called by the runtime. Use this method to add services to the container.
|
||||||
|
@ -117,7 +117,7 @@ namespace Roadie.Api
|
||||||
services.AddSingleton<ICacheSerializer>(options =>
|
services.AddSingleton<ICacheSerializer>(options =>
|
||||||
{
|
{
|
||||||
var logger = options.GetService<ILogger<Utf8JsonCacheSerializer>>();
|
var logger = options.GetService<ILogger<Utf8JsonCacheSerializer>>();
|
||||||
return new Utf8JsonCacheSerializer(logger);
|
return new SystemTextCacheSerializer(logger);
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddSingleton<ICacheManager>(options =>
|
services.AddSingleton<ICacheManager>(options =>
|
||||||
|
@ -226,7 +226,7 @@ namespace Roadie.Api
|
||||||
configuration.GetSection("RoadieSettings").Bind(settings);
|
configuration.GetSection("RoadieSettings").Bind(settings);
|
||||||
var hostingEnvironment = ctx.GetService<IWebHostEnvironment>();
|
var hostingEnvironment = ctx.GetService<IWebHostEnvironment>();
|
||||||
settings.ContentPath = hostingEnvironment.WebRootPath;
|
settings.ContentPath = hostingEnvironment.WebRootPath;
|
||||||
settings.ConnectionString = _configuration.GetConnectionString("RoadieDatabaseConnection");
|
settings.ConnectionString = _configuration.GetConnectionString("RoadieDatabaseConnection");
|
||||||
|
|
||||||
// This is so 'User Secrets' can be used in Debugging
|
// This is so 'User Secrets' can be used in Debugging
|
||||||
var integrationKeys = _configuration.GetSection("IntegrationKeys").Get<IntegrationKey>();
|
var integrationKeys = _configuration.GetSection("IntegrationKeys").Get<IntegrationKey>();
|
||||||
|
@ -335,7 +335,7 @@ namespace Roadie.Api
|
||||||
options.RespectBrowserAcceptHeader = true; // false by default
|
options.RespectBrowserAcceptHeader = true; // false by default
|
||||||
options.ModelBinderProviders.Insert(0, new SubsonicRequestBinderProvider());
|
options.ModelBinderProviders.Insert(0, new SubsonicRequestBinderProvider());
|
||||||
})
|
})
|
||||||
.AddJsonOptions(options => options.JsonSerializerOptions.IgnoreNullValues = true)
|
.AddJsonOptions(options => options.JsonSerializerOptions.DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)
|
||||||
.AddXmlSerializerFormatters();
|
.AddXmlSerializerFormatters();
|
||||||
|
|
||||||
services.Configure<IdentityOptions>(options =>
|
services.Configure<IdentityOptions>(options =>
|
||||||
|
@ -362,9 +362,6 @@ namespace Roadie.Api
|
||||||
return new HttpContext(factory.GetService<IRoadieSettings>(), new UrlHelper(actionContext));
|
return new HttpContext(factory.GetService<IRoadieSettings>(), new UrlHelper(actionContext));
|
||||||
});
|
});
|
||||||
|
|
||||||
var sp = services.BuildServiceProvider();
|
|
||||||
var adminService = sp.GetService<IAdminService>();
|
|
||||||
adminService.PerformStartUpTasks();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string _roadieApiVersion = null;
|
private static string _roadieApiVersion = null;
|
||||||
|
@ -385,9 +382,13 @@ namespace Roadie.Api
|
||||||
private class IntegrationKey
|
private class IntegrationKey
|
||||||
{
|
{
|
||||||
public string BingImageSearch { get; set; }
|
public string BingImageSearch { get; set; }
|
||||||
|
|
||||||
public string DiscogsConsumerKey { get; set; }
|
public string DiscogsConsumerKey { get; set; }
|
||||||
|
|
||||||
public string DiscogsConsumerSecret { get; set; }
|
public string DiscogsConsumerSecret { get; set; }
|
||||||
|
|
||||||
public string LastFMApiKey { get; set; }
|
public string LastFMApiKey { get; set; }
|
||||||
|
|
||||||
public string LastFMSecret { get; set; }
|
public string LastFMSecret { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue