Merge Lidarr/develop into new-multithreaded-import (stay current + keep multithread)

This commit is contained in:
Sean Parsons 2026-03-25 21:47:28 +00:00
commit 20e8ab9754
19 changed files with 104 additions and 42 deletions

View file

@ -15,11 +15,13 @@ public BackupFileMapper(IBackupService backupService, IDiskProvider diskProvider
_backupService = backupService;
}
public override string Map(string resourceUrl)
protected override string FolderPath => _backupService.GetBackupFolder();
protected override string MapPath(string resourceUrl)
{
var path = resourceUrl.Replace("/backup/", "").Replace('/', Path.DirectorySeparatorChar);
return Path.Combine(_backupService.GetBackupFolder(), path);
return Path.Combine(FolderPath, path);
}
public override bool CanHandle(string resourceUrl)

View file

@ -8,13 +8,20 @@ namespace Lidarr.Http.Frontend.Mappers
{
public class BrowserConfig : UrlBaseReplacementResourceMapperBase
{
private readonly IAppFolderInfo _appFolderInfo;
private readonly IConfigFileProvider _configFileProvider;
public BrowserConfig(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IConfigFileProvider configFileProvider, Logger logger)
: base(diskProvider, configFileProvider, logger)
{
FilePath = Path.Combine(appFolderInfo.StartUpFolder, configFileProvider.UiFolder, "Content", "browserconfig.xml");
_appFolderInfo = appFolderInfo;
_configFileProvider = configFileProvider;
}
public override string Map(string resourceUrl)
protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder);
protected override string FilePath => Path.Combine(FolderPath, "Content", "browserconfig.xml");
protected override string MapPath(string resourceUrl)
{
return FilePath;
}

View file

@ -37,6 +37,12 @@ public string AddCacheBreakerToPath(string resourceUrl)
var mapper = _diskMappers.Single(m => m.CanHandle(resourceUrl));
var pathToFile = mapper.Map(resourceUrl);
if (pathToFile == null)
{
return resourceUrl;
}
var hash = _hashProvider.ComputeMd5(pathToFile).ToBase64();
return resourceUrl + "?h=" + hash.Trim('=');

View file

@ -18,7 +18,9 @@ public FaviconMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, I
_configFileProvider = configFileProvider;
}
public override string Map(string resourceUrl)
protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder);
protected override string MapPath(string resourceUrl)
{
var fileName = "favicon.ico";
@ -29,7 +31,7 @@ public override string Map(string resourceUrl)
var path = Path.Combine("Content", "Images", "Icons", fileName);
return Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, path);
return Path.Combine(FolderPath, path);
}
public override bool CanHandle(string resourceUrl)

View file

@ -4,6 +4,7 @@
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration;
namespace Lidarr.Http.Frontend.Mappers
{
@ -13,19 +14,22 @@ public abstract class HtmlMapperBase : StaticResourceMapperBase
private readonly Lazy<ICacheBreakerProvider> _cacheBreakProviderFactory;
private static readonly Regex ReplaceRegex = new Regex(@"(?:(?<attribute>href|src)=\"")(?<path>.*?(?<extension>css|js|png|ico|ics|svg|json))(?:\"")(?:\s(?<nohash>data-no-hash))?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private string _urlBase;
private string _generatedContent;
protected HtmlMapperBase(IDiskProvider diskProvider,
IConfigFileProvider configFileProvider,
Lazy<ICacheBreakerProvider> cacheBreakProviderFactory,
Logger logger)
: base(diskProvider, logger)
{
_diskProvider = diskProvider;
_cacheBreakProviderFactory = cacheBreakProviderFactory;
_urlBase = configFileProvider.UrlBase;
}
protected string HtmlPath;
protected string UrlBase;
protected abstract string HtmlPath { get; }
protected override Stream GetContentStream(string filePath)
{
@ -62,10 +66,10 @@ protected virtual string GetHtmlText()
url = cacheBreakProvider.AddCacheBreakerToPath(match.Groups["path"].Value);
}
return $"{match.Groups["attribute"].Value}=\"{UrlBase}{url}\"";
return $"{match.Groups["attribute"].Value}=\"{_urlBase}{url}\"";
});
text = text.Replace("__URL_BASE__", UrlBase);
text = text.Replace("__URL_BASE__", _urlBase);
_generatedContent = text;

View file

@ -9,6 +9,7 @@ namespace Lidarr.Http.Frontend.Mappers
{
public class IndexHtmlMapper : HtmlMapperBase
{
private readonly IAppFolderInfo _appFolderInfo;
private readonly IConfigFileProvider _configFileProvider;
public IndexHtmlMapper(IAppFolderInfo appFolderInfo,
@ -16,15 +17,16 @@ public IndexHtmlMapper(IAppFolderInfo appFolderInfo,
IConfigFileProvider configFileProvider,
Lazy<ICacheBreakerProvider> cacheBreakProviderFactory,
Logger logger)
: base(diskProvider, cacheBreakProviderFactory, logger)
: base(diskProvider, configFileProvider, cacheBreakProviderFactory, logger)
{
_appFolderInfo = appFolderInfo;
_configFileProvider = configFileProvider;
HtmlPath = Path.Combine(appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, "index.html");
UrlBase = configFileProvider.UrlBase;
}
public override string Map(string resourceUrl)
protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder);
protected override string HtmlPath => Path.Combine(FolderPath, "index.html");
protected override string MapPath(string resourceUrl)
{
return HtmlPath;
}

View file

@ -16,12 +16,14 @@ public LogFileMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, L
_appFolderInfo = appFolderInfo;
}
public override string Map(string resourceUrl)
protected override string FolderPath => _appFolderInfo.GetLogFolder();
protected override string MapPath(string resourceUrl)
{
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = Path.GetFileName(path);
return Path.Combine(_appFolderInfo.GetLogFolder(), path);
return Path.Combine(FolderPath, path);
}
public override bool CanHandle(string resourceUrl)

View file

@ -9,6 +9,7 @@ namespace Lidarr.Http.Frontend.Mappers
{
public class LoginHtmlMapper : HtmlMapperBase
{
private readonly IAppFolderInfo _appFolderInfo;
private readonly IConfigFileProvider _configFileProvider;
public LoginHtmlMapper(IAppFolderInfo appFolderInfo,
@ -16,14 +17,16 @@ public LoginHtmlMapper(IAppFolderInfo appFolderInfo,
Lazy<ICacheBreakerProvider> cacheBreakProviderFactory,
IConfigFileProvider configFileProvider,
Logger logger)
: base(diskProvider, cacheBreakProviderFactory, logger)
: base(diskProvider, configFileProvider, cacheBreakProviderFactory, logger)
{
_appFolderInfo = appFolderInfo;
_configFileProvider = configFileProvider;
HtmlPath = Path.Combine(appFolderInfo.StartUpFolder, configFileProvider.UiFolder, "login.html");
UrlBase = configFileProvider.UrlBase;
}
public override string Map(string resourceUrl)
protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder);
protected override string HtmlPath => Path.Combine(FolderPath, "login.html");
protected override string MapPath(string resourceUrl)
{
return HtmlPath;
}

View file

@ -8,6 +8,7 @@ namespace Lidarr.Http.Frontend.Mappers
{
public class ManifestMapper : UrlBaseReplacementResourceMapperBase
{
private readonly IAppFolderInfo _appFolderInfo;
private readonly IConfigFileProvider _configFileProvider;
private string _generatedContent;
@ -15,11 +16,14 @@ public class ManifestMapper : UrlBaseReplacementResourceMapperBase
public ManifestMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IConfigFileProvider configFileProvider, Logger logger)
: base(diskProvider, configFileProvider, logger)
{
_appFolderInfo = appFolderInfo;
_configFileProvider = configFileProvider;
FilePath = Path.Combine(appFolderInfo.StartUpFolder, configFileProvider.UiFolder, "Content", "manifest.json");
}
public override string Map(string resourceUrl)
protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder);
protected override string FilePath => Path.Combine(FolderPath, "Content", "manifest.json");
protected override string MapPath(string resourceUrl)
{
return FilePath;
}

View file

@ -22,7 +22,9 @@ public MediaCoverMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider
_diskProvider = diskProvider;
}
public override string Map(string resourceUrl)
protected override string FolderPath => Path.Combine(_appFolderInfo.GetAppDataPath(), "MediaCover");
protected override string MapPath(string resourceUrl)
{
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = path.Trim(Path.DirectorySeparatorChar);

View file

@ -18,11 +18,13 @@ public RobotsTxtMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider,
_configFileProvider = configFileProvider;
}
public override string Map(string resourceUrl)
protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder);
protected override string MapPath(string resourceUrl)
{
var path = Path.Combine("Content", "robots.txt");
return Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, path);
return Path.Combine(FolderPath, path);
}
public override bool CanHandle(string resourceUrl)

View file

@ -18,12 +18,14 @@ public StaticResourceMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProv
_configFileProvider = configFileProvider;
}
public override string Map(string resourceUrl)
protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder);
protected override string MapPath(string resourceUrl)
{
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = path.Trim(Path.DirectorySeparatorChar);
return Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, path);
return Path.Combine(FolderPath, path);
}
public override bool CanHandle(string resourceUrl)

View file

@ -27,14 +27,28 @@ protected StaticResourceMapperBase(IDiskProvider diskProvider, Logger logger)
_caseSensitive = RuntimeInfo.IsProduction ? DiskProviderBase.PathStringComparison : StringComparison.OrdinalIgnoreCase;
}
public abstract string Map(string resourceUrl);
protected abstract string FolderPath { get; }
protected abstract string MapPath(string resourceUrl);
public abstract bool CanHandle(string resourceUrl);
public string Map(string resourceUrl)
{
var filePath = Path.GetFullPath(MapPath(resourceUrl));
var parentPath = Path.GetFullPath(FolderPath) + Path.DirectorySeparatorChar;
return filePath.StartsWith(parentPath) ? filePath : null;
}
public Task<IActionResult> GetResponse(string resourceUrl)
{
var filePath = Map(resourceUrl);
if (filePath == null)
{
return Task.FromResult<IActionResult>(null);
}
if (_diskProvider.FileExists(filePath, _caseSensitive))
{
if (!_mimeTypeProvider.TryGetContentType(filePath, out var contentType))

View file

@ -16,12 +16,14 @@ public UpdateLogFileMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvi
_appFolderInfo = appFolderInfo;
}
public override string Map(string resourceUrl)
protected override string FolderPath => _appFolderInfo.GetUpdateLogFolder();
protected override string MapPath(string resourceUrl)
{
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = Path.GetFileName(path);
return Path.Combine(_appFolderInfo.GetUpdateLogFolder(), path);
return Path.Combine(FolderPath, path);
}
public override bool CanHandle(string resourceUrl)

View file

@ -20,9 +20,9 @@ public UrlBaseReplacementResourceMapperBase(IDiskProvider diskProvider, IConfigF
_urlBase = configFileProvider.UrlBase;
}
protected string FilePath;
protected abstract string FilePath { get; }
public override string Map(string resourceUrl)
protected override string MapPath(string resourceUrl)
{
return FilePath;
}

View file

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Lidarr.Http.Extensions;
using Lidarr.Http.Frontend.Mappers;
@ -16,6 +17,7 @@ public class StaticResourceController : Controller
{
private readonly IEnumerable<IMapHttpRequestsToDisk> _requestMappers;
private readonly Logger _logger;
private static readonly Regex InvalidPathRegex = new (@"([\/\\]|%2f|%5c)\.\.|\.\.([\/\\]|%2f|%5c)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
public StaticResourceController(IEnumerable<IMapHttpRequestsToDisk> requestMappers,
Logger logger)
@ -50,6 +52,11 @@ private async Task<IActionResult> MapResource(string path)
{
path = "/" + (path ?? "");
if (InvalidPathRegex.IsMatch(path))
{
return NotFound();
}
var mapper = _requestMappers.SingleOrDefault(m => m.CanHandle(path));
if (mapper != null)

View file

@ -6,13 +6,14 @@
namespace NzbDrone.Common.Http
{
public class HttpUri : IEquatable<HttpUri>
public partial class HttpUri : IEquatable<HttpUri>
{
private static readonly Regex RegexUri = new Regex(@"^(?:(?<scheme>[a-z]+):)?(?://(?<host>[-_A-Z0-9.]+|\[[[A-F0-9:]+\])(?::(?<port>[0-9]{1,5}))?)?(?<path>(?:(?:(?<=^)|/+)[^/?#\r\n]+)+/*|/+)?(?:\?(?<query>[^#\r\n]*))?(?:\#(?<fragment>.*))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private readonly string _uri;
public string FullUri => _uri;
[GeneratedRegex(@"^(?:(?<scheme>[a-z]+):)?(?://(?<host>[-_A-Z0-9.]+|\[[[A-F0-9:]+\])(?::(?<port>[0-9]{1,5}))?)?(?<path>(?:(?:(?<=^)|/+)[^/?#\r\n]+)+/*|/+)?(?:\?(?<query>[^#\r\n]*))?(?:\#(?<fragment>.*))?$", RegexOptions.IgnoreCase | RegexOptions.Compiled)]
private static partial Regex UriRegex();
public HttpUri(string uri)
{
_uri = uri ?? string.Empty;
@ -70,9 +71,9 @@ public HttpUri(string scheme, string host, int? port, string path, string query,
private void Parse()
{
var parseSuccess = Uri.TryCreate(_uri, UriKind.RelativeOrAbsolute, out var uri);
var parseSuccess = Uri.TryCreate(_uri, UriKind.RelativeOrAbsolute, out _);
var match = RegexUri.Match(_uri);
var match = UriRegex().Match(_uri);
var scheme = match.Groups["scheme"];
var host = match.Groups["host"];

View file

@ -107,8 +107,8 @@ public void no_artist_search_result(string term)
}
[TestCase("Eminem", 0, typeof(Artist), "Eminem")]
[TestCase("Eminem Kamikaze", 0, typeof(Artist), "Eminem")]
[TestCase("Eminem Kamikaze", 1, typeof(Album), "Kamikaze")]
[TestCase("Eminem Kamikaze", 0, typeof(Album), "Kamikaze")]
[TestCase("Eminem Kamikaze", 1, typeof(Artist), "Eminem")]
[TestCase("lidarr:f59c5520-5f46-4d2c-b2c4-822eabf53419", 0, typeof(Artist), "Linkin Park")]
[TestCase("lidarr: d77df681-b779-3d6d-b66a-3bfd15985e3e", 0, typeof(Album), "Pyromania")]
public void successful_combined_search(string query, int position, Type resultType, string expected)

View file

@ -6,7 +6,7 @@
<PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="Diacritical.Net" Version="1.0.4" />
<PackageReference Include="Equ" Version="2.3.0" />
<PackageReference Include="MailKit" Version="4.14.0" />
<PackageReference Include="MailKit" Version="4.15.1" />
<PackageReference Include="Polly" Version="8.6.4" />
<PackageReference Include="System.Text.Json" Version="8.0.6" />
<PackageReference Include="System.Memory" Version="4.6.3" />
@ -27,7 +27,7 @@
<PackageReference Include="TagLibSharp-Lidarr" Version="2.2.0.27" />
<PackageReference Include="Npgsql" Version="9.0.3" />
<PackageReference Include="SpotifyAPI.Web" Version="5.1.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
<PackageReference Include="MonoTorrent" Version="3.0.2" />
<PackageReference Include="System.Drawing.Common" Version="8.0.20" />
</ItemGroup>