mirror of
https://github.com/Prowlarr/Prowlarr
synced 2026-05-09 05:22:09 +02:00
Compare commits
No commits in common. "develop" and "v2.3.4.5307" have entirely different histories.
develop
...
v2.3.4.530
44 changed files with 199 additions and 546 deletions
26
.github/workflows/close_invalid_issues.yml
vendored
26
.github/workflows/close_invalid_issues.yml
vendored
|
|
@ -1,26 +0,0 @@
|
|||
name: Close issues without labels
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
|
||||
jobs:
|
||||
close-issue:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
sparse-checkout: |
|
||||
.github
|
||||
- name: Close issue if no labels found
|
||||
if: join(github.event.issue.labels) == ''
|
||||
run: |
|
||||
gh issue comment ${{ github.event.issue.number }} --body ":wave: @${{ github.event.issue.user.login }}, this issue was closed automatically because it was created without following an issue template. Please update the issue following the correct template for this issue. Once updated please reply to this issue so we can review and re-open. In the future, use the [issue templates](https://github.com/${{ github.repository }}/issues/new/choose) instead of creating your own."
|
||||
gh issue close ${{ github.event.issue.number }} --reason "not planned"
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
|
@ -9,7 +9,7 @@ variables:
|
|||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '2.3.7'
|
||||
majorVersion: '2.3.4'
|
||||
minorVersion: $[counter('minorVersion', 1)]
|
||||
prowlarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
|
||||
|
|
@ -17,7 +17,7 @@ variables:
|
|||
sentryUrl: 'https://sentry.servarr.com'
|
||||
dotnetVersion: '8.0.405'
|
||||
nodeVersion: '20.X'
|
||||
innoVersion: '6.7.1'
|
||||
innoVersion: '6.2.2'
|
||||
windowsImage: 'windows-2025'
|
||||
linuxImage: 'ubuntu-24.04'
|
||||
macImage: 'macOS-15'
|
||||
|
|
|
|||
4
build.sh
4
build.sh
|
|
@ -253,10 +253,8 @@ InstallInno()
|
|||
{
|
||||
ProgressStart "Installing portable Inno Setup"
|
||||
|
||||
INNOVERSION=${INNOVERSION:-6.7.1}
|
||||
|
||||
rm -rf _inno
|
||||
curl -s -L --output innosetup.exe "https://github.com/jrsoftware/issrc/releases/download/is-${INNOVERSION//./_}/innosetup-${INNOVERSION}.exe"
|
||||
curl -s --output innosetup.exe "https://files.jrsoftware.org/is/6/innosetup-${INNOVERSION:-6.2.2}.exe"
|
||||
mkdir _inno
|
||||
./innosetup.exe //portable=1 //silent //currentuser //dir=.\\_inno
|
||||
rm innosetup.exe
|
||||
|
|
|
|||
|
|
@ -133,12 +133,6 @@ module.exports = (env) => {
|
|||
{
|
||||
source: 'frontend/src/Content/robots.txt',
|
||||
destination: path.join(distFolder, 'Content/robots.txt')
|
||||
},
|
||||
|
||||
// manifest.json and browserconfig.xml
|
||||
{
|
||||
source: 'frontend/src/Content/*.(json|xml)',
|
||||
destination: path.join(distFolder, 'Content')
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
9
frontend/src/Content/Images/Icons/browserconfig.xml
Normal file
9
frontend/src/Content/Images/Icons/browserconfig.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/Content/Images/Icons/mstile-150x150.png"/>
|
||||
<TileColor>#00ccff</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
19
frontend/src/Content/Images/Icons/manifest.json
Normal file
19
frontend/src/Content/Images/Icons/manifest.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "Prowlarr",
|
||||
"icons": [
|
||||
{
|
||||
"src": "android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"start_url": "../../../../",
|
||||
"theme_color": "#3a3f51",
|
||||
"background_color": "#3a3f51",
|
||||
"display": "standalone"
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="__URL_BASE__/Content/Images/Icons/mstile-150x150.png" />
|
||||
<TileColor>
|
||||
#00ccff
|
||||
</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"name": "__INSTANCE_NAME__",
|
||||
"icons": [
|
||||
{
|
||||
"src": "__URL_BASE__/Content/Images/Icons/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "__URL_BASE__/Content/Images/Icons/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"start_url": "__URL_BASE__/",
|
||||
"theme_color": "#3a3f51",
|
||||
"background_color": "#3a3f51",
|
||||
"display": "standalone"
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
sizes="16x16"
|
||||
href="/Content/Images/Icons/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="/Content/manifest.json" crossorigin="use-credentials" />
|
||||
<link rel="manifest" href="/Content/Images/Icons/manifest.json" crossorigin="use-credentials" />
|
||||
<link
|
||||
rel="mask-icon"
|
||||
href="/Content/Images/Icons/safari-pinned-tab.svg"
|
||||
|
|
@ -47,7 +47,7 @@
|
|||
/>
|
||||
<meta
|
||||
name="msapplication-config"
|
||||
content="/Content/browserconfig.xml"
|
||||
content="/Content/Images/Icons/browserconfig.xml"
|
||||
/>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/Content/Fonts/fonts.css">
|
||||
|
|
|
|||
|
|
@ -11,11 +11,8 @@
|
|||
<!-- Android/Apple Phone -->
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta
|
||||
name="apple-mobile-web-app-status-bar-style"
|
||||
content="black-translucent"
|
||||
/>
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
|
||||
<meta name="description" content="Prowlarr" />
|
||||
|
||||
|
|
@ -36,11 +33,7 @@
|
|||
sizes="16x16"
|
||||
href="/Content/Images/Icons/favicon-16x16.png"
|
||||
/>
|
||||
<link
|
||||
rel="manifest"
|
||||
href="/Content/manifest.json"
|
||||
crossorigin="use-credentials"
|
||||
/>
|
||||
<link rel="manifest" href="/Content/Images/Icons/manifest.json" crossorigin="use-credentials" />
|
||||
<link
|
||||
rel="mask-icon"
|
||||
href="/Content/Images/Icons/safari-pinned-tab.svg"
|
||||
|
|
@ -52,7 +45,10 @@
|
|||
href="/favicon.ico"
|
||||
data-no-hash
|
||||
/>
|
||||
<meta name="msapplication-config" content="/Content/browserconfig.xml" />
|
||||
<meta
|
||||
name="msapplication-config"
|
||||
content="/Content/Images/Icons/browserconfig.xml"
|
||||
/>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/Content/styles.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/Content/Fonts/fonts.css" />
|
||||
|
|
@ -63,7 +59,7 @@
|
|||
body {
|
||||
background-color: var(--pageBackground);
|
||||
color: var(--textColor);
|
||||
font-family: 'Roboto', 'open sans', 'Helvetica Neue', Helvetica, Arial,
|
||||
font-family: "Roboto", "open sans", "Helvetica Neue", Helvetica, Arial,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
|
|
@ -213,7 +209,9 @@
|
|||
</div>
|
||||
|
||||
<div class="panel-body">
|
||||
<div class="sign-in">SIGN IN TO CONTINUE</div>
|
||||
<div class="sign-in">
|
||||
SIGN IN TO CONTINUE
|
||||
</div>
|
||||
|
||||
<form
|
||||
role="form"
|
||||
|
|
@ -232,8 +230,8 @@
|
|||
pattern=".{1,}"
|
||||
required
|
||||
title="User name is required"
|
||||
autofocus="true"
|
||||
autocapitalize="false"
|
||||
autoFocus="true"
|
||||
autoCapitalize="false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -284,16 +282,16 @@
|
|||
</body>
|
||||
|
||||
<script type="text/javascript">
|
||||
var yearSpan = document.getElementById('year');
|
||||
yearSpan.innerHTML = '2010-' + new Date().getFullYear();
|
||||
var yearSpan = document.getElementById("year");
|
||||
yearSpan.innerHTML = "2010-" + new Date().getFullYear();
|
||||
|
||||
var copyDiv = document.getElementById('copy');
|
||||
copyDiv.classList.remove('hidden');
|
||||
var copyDiv = document.getElementById("copy");
|
||||
copyDiv.classList.remove("hidden");
|
||||
|
||||
if (window.location.search.indexOf('loginFailed=true') > -1) {
|
||||
var loginFailedDiv = document.getElementById('login-failed');
|
||||
if (window.location.search.indexOf("loginFailed=true") > -1) {
|
||||
var loginFailedDiv = document.getElementById("login-failed");
|
||||
|
||||
loginFailedDiv.classList.remove('hidden');
|
||||
loginFailedDiv.classList.remove("hidden");
|
||||
}
|
||||
|
||||
var light = {
|
||||
|
|
@ -313,7 +311,7 @@
|
|||
primaryHoverBorderColor: '#3483e7',
|
||||
failedColor: '#f05050',
|
||||
forgotPasswordColor: '#909fa7',
|
||||
forgotPasswordAltColor: '#748690',
|
||||
forgotPasswordAltColor: '#748690'
|
||||
};
|
||||
|
||||
var dark = {
|
||||
|
|
@ -333,16 +331,21 @@
|
|||
primaryHoverBorderColor: '#3483e7',
|
||||
failedColor: '#f05050',
|
||||
forgotPasswordColor: '#737d83',
|
||||
forgotPasswordAltColor: '#546067',
|
||||
forgotPasswordAltColor: '#546067'
|
||||
};
|
||||
|
||||
var theme = '_THEME_';
|
||||
var theme = "_THEME_";
|
||||
var defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
var finalTheme =
|
||||
theme === 'dark' || (theme === 'auto' && defaultDark) ? dark : light;
|
||||
var finalTheme = theme === 'dark' || (theme === 'auto' && defaultDark) ?
|
||||
dark :
|
||||
light;
|
||||
|
||||
Object.entries(finalTheme).forEach(([key, value]) => {
|
||||
document.documentElement.style.setProperty(`--${key}`, value);
|
||||
document.documentElement.style.setProperty(
|
||||
`--${key}`,
|
||||
value
|
||||
);
|
||||
});
|
||||
|
||||
</script>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -5,5 +5,6 @@
|
|||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||
<add key="dotnet-bsd-crossbuild" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/dotnet-bsd-crossbuild/nuget/v3/index.json" />
|
||||
<add key="Mono.Posix.NETStandard" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/Mono.Posix.NETStandard/nuget/v3/index.json" />
|
||||
<add key="FluentMigrator" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/FluentMigrator/nuget/v3/index.json" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ public class AssemblyLoader
|
|||
static AssemblyLoader()
|
||||
{
|
||||
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(ContainerResolveEventHandler);
|
||||
RegisterSQLiteResolver();
|
||||
}
|
||||
|
||||
public static IList<Assembly> Load(IList<string> assemblyNames)
|
||||
|
|
@ -22,10 +23,6 @@ public static IList<Assembly> Load(IList<string> assemblyNames)
|
|||
toLoad.Add("Prowlarr.Common");
|
||||
toLoad.Add(OsInfo.IsWindows ? "Prowlarr.Windows" : "Prowlarr.Mono");
|
||||
|
||||
var toRegisterResolver = new List<string> { "System.Data.SQLite" };
|
||||
toRegisterResolver.AddRange(assemblyNames.Intersect(new[] { "Prowlarr.Core" }));
|
||||
RegisterNativeResolver(toRegisterResolver);
|
||||
|
||||
var startupPath = AppDomain.CurrentDomain.BaseDirectory;
|
||||
|
||||
return toLoad
|
||||
|
|
@ -46,46 +43,27 @@ private static Assembly ContainerResolveEventHandler(object sender, ResolveEvent
|
|||
return AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
|
||||
}
|
||||
|
||||
public static void RegisterNativeResolver(IEnumerable<string> assemblyNames)
|
||||
public static void RegisterSQLiteResolver()
|
||||
{
|
||||
foreach (var name in assemblyNames)
|
||||
{
|
||||
// This ensures we look for sqlite3 using libsqlite3.so.0 on Linux and not libsqlite3.so which
|
||||
// is less likely to exist.
|
||||
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(
|
||||
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"{name}.dll"));
|
||||
// This ensures we look for sqlite3 using libsqlite3.so.0 on Linux and not libsqlite3.so which
|
||||
// is less likely to exist.
|
||||
var sqliteAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(
|
||||
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "System.Data.SQLite.dll"));
|
||||
|
||||
try
|
||||
{
|
||||
NativeLibrary.SetDllImportResolver(assembly, LoadNativeLib);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// This can only be set once per assembly
|
||||
// Catch required for NzbDrone.Host tests
|
||||
}
|
||||
try
|
||||
{
|
||||
NativeLibrary.SetDllImportResolver(sqliteAssembly, LoadSqliteNativeLib);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// This can only be set once per assembly
|
||||
// Catch required for NzbDrone.Host tests
|
||||
}
|
||||
}
|
||||
|
||||
private static IntPtr LoadNativeLib(string libraryName, Assembly assembly, DllImportSearchPath? dllImportSearchPath)
|
||||
private static IntPtr LoadSqliteNativeLib(string libraryName, Assembly assembly, DllImportSearchPath? dllImportSearchPath)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(libraryName);
|
||||
|
||||
var mappedName = libraryName;
|
||||
|
||||
if (libraryName is "sqlite3" or "e_sqlite3")
|
||||
{
|
||||
if (OsInfo.IsLinux)
|
||||
{
|
||||
if (NativeLibrary.TryLoad(libraryName, assembly, dllImportSearchPath, out var libHandle))
|
||||
{
|
||||
return libHandle;
|
||||
}
|
||||
|
||||
mappedName = "libsqlite3.so.0";
|
||||
}
|
||||
}
|
||||
|
||||
var mappedName = OsInfo.IsLinux && libraryName == "sqlite3" ? "libsqlite3.so.0" : libraryName;
|
||||
return NativeLibrary.Load(mappedName, assembly, dllImportSearchPath);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,14 +6,13 @@
|
|||
|
||||
namespace NzbDrone.Common.Http
|
||||
{
|
||||
public partial class HttpUri : IEquatable<HttpUri>
|
||||
public 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;
|
||||
|
|
@ -71,9 +70,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 parseSuccess = Uri.TryCreate(_uri, UriKind.RelativeOrAbsolute, out var uri);
|
||||
|
||||
var match = UriRegex().Match(_uri);
|
||||
var match = RegexUri.Match(_uri);
|
||||
|
||||
var scheme = match.Groups["scheme"];
|
||||
var host = match.Groups["host"];
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Maintenance(MigrationStage.BeforeAll, TransactionBehavior.None)]
|
||||
public class DatabaseEngineVersionCheck : ForwardOnlyMigration
|
||||
public class DatabaseEngineVersionCheck : FluentMigrator.Migration
|
||||
{
|
||||
protected readonly Logger _logger;
|
||||
|
||||
|
|
@ -22,6 +22,11 @@ public override void Up()
|
|||
IfDatabase("postgres").Execute.WithConnection(LogPostgresVersion);
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
|
||||
private void LogSqliteVersion(IDbConnection conn, IDbTransaction tran)
|
||||
{
|
||||
using (var versionCmd = conn.CreateCommand())
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
using FluentMigrator.Runner.Initialization;
|
||||
using FluentMigrator.Runner.Processors;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NLog;
|
||||
using NLog.Extensions.Logging;
|
||||
|
||||
|
|
@ -19,10 +20,13 @@ public interface IMigrationController
|
|||
public class MigrationController : IMigrationController
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
private readonly ILoggerProvider _migrationLoggerProvider;
|
||||
|
||||
public MigrationController(Logger logger)
|
||||
public MigrationController(Logger logger,
|
||||
ILoggerProvider migrationLoggerProvider)
|
||||
{
|
||||
_logger = logger;
|
||||
_migrationLoggerProvider = migrationLoggerProvider;
|
||||
}
|
||||
|
||||
public void Migrate(string connectionString, MigrationContext migrationContext, DatabaseType databaseType)
|
||||
|
|
@ -31,13 +35,16 @@ public void Migrate(string connectionString, MigrationContext migrationContext,
|
|||
|
||||
_logger.Info("*** Migrating {0} ***", connectionString);
|
||||
|
||||
ServiceProvider serviceProvider;
|
||||
|
||||
var db = databaseType == DatabaseType.SQLite ? "sqlite" : "postgres";
|
||||
|
||||
var serviceProvider = new ServiceCollection()
|
||||
serviceProvider = new ServiceCollection()
|
||||
.AddLogging(b => b.AddNLog())
|
||||
.AddFluentMigratorCore()
|
||||
.Configure<RunnerOptions>(cfg => cfg.IncludeUntaggedMaintenances = true)
|
||||
.ConfigureRunner(builder => builder
|
||||
.ConfigureRunner(
|
||||
builder => builder
|
||||
.AddPostgres()
|
||||
.AddNzbDroneSQLite()
|
||||
.WithGlobalConnectionString(connectionString)
|
||||
|
|
|
|||
|
|
@ -1,17 +1,11 @@
|
|||
using System.Data;
|
||||
using FluentMigrator;
|
||||
using FluentMigrator;
|
||||
using FluentMigrator.Builders.Create;
|
||||
using FluentMigrator.Builders.Create.Table;
|
||||
using FluentMigrator.Runner;
|
||||
using FluentMigrator.Runner.BatchParser;
|
||||
using FluentMigrator.Runner.Generators;
|
||||
using FluentMigrator.Runner.Generators.SQLite;
|
||||
using FluentMigrator.Runner.Initialization;
|
||||
using FluentMigrator.Runner.Processors;
|
||||
using FluentMigrator.Runner.Processors.SQLite;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||
{
|
||||
|
|
@ -22,49 +16,23 @@ public static ICreateTableColumnOptionOrWithColumnSyntax TableForModel(this ICre
|
|||
return expressionRoot.Table(name).WithColumn("Id").AsInt32().PrimaryKey().Identity();
|
||||
}
|
||||
|
||||
public static IDbCommand CreateCommand(this IDbConnection conn, IDbTransaction tran, string query)
|
||||
{
|
||||
var command = conn.CreateCommand();
|
||||
command.Transaction = tran;
|
||||
command.CommandText = query;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
public static void AddParameter(this IDbCommand command, object value)
|
||||
public static void AddParameter(this System.Data.IDbCommand command, object value)
|
||||
{
|
||||
var parameter = command.CreateParameter();
|
||||
parameter.Value = value;
|
||||
command.Parameters.Add(parameter);
|
||||
}
|
||||
|
||||
public static IMigrationRunnerBuilder AddNzbDroneSQLite(this IMigrationRunnerBuilder builder, bool binaryGuid = false, bool useStrictTables = false)
|
||||
public static IMigrationRunnerBuilder AddNzbDroneSQLite(this IMigrationRunnerBuilder builder)
|
||||
{
|
||||
builder.Services
|
||||
.AddTransient<SQLiteBatchParser>()
|
||||
.AddScoped<SQLiteDbFactory>()
|
||||
.AddScoped<NzbDroneSQLiteProcessor>(sp =>
|
||||
{
|
||||
var factory = sp.GetService<SQLiteDbFactory>();
|
||||
var logger = sp.GetService<ILogger<NzbDroneSQLiteProcessor>>();
|
||||
var options = sp.GetService<IOptionsSnapshot<ProcessorOptions>>();
|
||||
var connectionStringAccessor = sp.GetService<IConnectionStringAccessor>();
|
||||
var sqliteQuoter = new SQLiteQuoter(false);
|
||||
return new NzbDroneSQLiteProcessor(factory, sp.GetService<SQLiteGenerator>(), logger, options, connectionStringAccessor, sp, sqliteQuoter);
|
||||
})
|
||||
.AddScoped<ISQLiteTypeMap>(_ => new NzbDroneSQLiteTypeMap(useStrictTables))
|
||||
.AddScoped<NzbDroneSQLiteProcessor>()
|
||||
.AddScoped<IMigrationProcessor>(sp => sp.GetRequiredService<NzbDroneSQLiteProcessor>())
|
||||
.AddScoped(
|
||||
sp =>
|
||||
{
|
||||
var typeMap = sp.GetRequiredService<ISQLiteTypeMap>();
|
||||
return new SQLiteGenerator(
|
||||
new SQLiteQuoter(binaryGuid),
|
||||
typeMap,
|
||||
new OptionsWrapper<GeneratorOptions>(new GeneratorOptions()));
|
||||
})
|
||||
.AddScoped<SQLiteQuoter>()
|
||||
.AddScoped<SQLiteGenerator>()
|
||||
.AddScoped<IMigrationGenerator>(sp => sp.GetRequiredService<SQLiteGenerator>());
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
|||
{
|
||||
public class NzbDroneSQLiteProcessor : SQLiteProcessor
|
||||
{
|
||||
private readonly SQLiteQuoter _quoter;
|
||||
|
||||
public NzbDroneSQLiteProcessor(SQLiteDbFactory factory,
|
||||
SQLiteGenerator generator,
|
||||
ILogger<NzbDroneSQLiteProcessor> logger,
|
||||
|
|
@ -26,7 +24,6 @@ public NzbDroneSQLiteProcessor(SQLiteDbFactory factory,
|
|||
SQLiteQuoter quoter)
|
||||
: base(factory, generator, logger, options, connectionStringAccessor, serviceProvider, quoter)
|
||||
{
|
||||
_quoter = quoter;
|
||||
}
|
||||
|
||||
public override void Process(AlterColumnExpression expression)
|
||||
|
|
@ -38,7 +35,7 @@ public override void Process(AlterColumnExpression expression)
|
|||
|
||||
if (columnIndex == -1)
|
||||
{
|
||||
throw new ApplicationException($"Column {expression.Column.Name} does not exist on table {expression.TableName}.");
|
||||
throw new ApplicationException(string.Format("Column {0} does not exist on table {1}.", expression.Column.Name, expression.TableName));
|
||||
}
|
||||
|
||||
columnDefinitions[columnIndex] = expression.Column;
|
||||
|
|
@ -48,28 +45,6 @@ public override void Process(AlterColumnExpression expression)
|
|||
ProcessAlterTable(tableDefinition);
|
||||
}
|
||||
|
||||
public override void Process(AlterDefaultConstraintExpression expression)
|
||||
{
|
||||
var tableDefinition = GetTableSchema(expression.TableName);
|
||||
|
||||
var columnDefinitions = tableDefinition.Columns.ToList();
|
||||
var columnIndex = columnDefinitions.FindIndex(c => c.Name == expression.ColumnName);
|
||||
|
||||
if (columnIndex == -1)
|
||||
{
|
||||
throw new ApplicationException($"Column {expression.ColumnName} does not exist on table {expression.TableName}.");
|
||||
}
|
||||
|
||||
var changedColumn = columnDefinitions[columnIndex];
|
||||
changedColumn.DefaultValue = expression.DefaultValue;
|
||||
|
||||
columnDefinitions[columnIndex] = changedColumn;
|
||||
|
||||
tableDefinition.Columns = columnDefinitions;
|
||||
|
||||
ProcessAlterTable(tableDefinition);
|
||||
}
|
||||
|
||||
public override void Process(DeleteColumnExpression expression)
|
||||
{
|
||||
var tableDefinition = GetTableSchema(expression.TableName);
|
||||
|
|
@ -87,7 +62,7 @@ public override void Process(DeleteColumnExpression expression)
|
|||
|
||||
if (columnsToRemove.Any())
|
||||
{
|
||||
throw new ApplicationException($"Column {columnsToRemove.First()} does not exist on table {expression.TableName}.");
|
||||
throw new ApplicationException(string.Format("Column {0} does not exist on table {1}.", columnsToRemove.First(), expression.TableName));
|
||||
}
|
||||
|
||||
ProcessAlterTable(tableDefinition);
|
||||
|
|
@ -103,12 +78,12 @@ public override void Process(RenameColumnExpression expression)
|
|||
|
||||
if (columnIndex == -1)
|
||||
{
|
||||
throw new ApplicationException($"Column {expression.OldName} does not exist on table {expression.TableName}.");
|
||||
throw new ApplicationException(string.Format("Column {0} does not exist on table {1}.", expression.OldName, expression.TableName));
|
||||
}
|
||||
|
||||
if (columnDefinitions.Any(c => c.Name == expression.NewName))
|
||||
{
|
||||
throw new ApplicationException($"Column {expression.NewName} already exists on table {expression.TableName}.");
|
||||
throw new ApplicationException(string.Format("Column {0} already exists on table {1}.", expression.NewName, expression.TableName));
|
||||
}
|
||||
|
||||
oldColumnDefinitions[columnIndex] = (ColumnDefinition)columnDefinitions[columnIndex].Clone();
|
||||
|
|
@ -153,20 +128,21 @@ protected virtual void ProcessAlterTable(TableDefinition tableDefinition, List<C
|
|||
}
|
||||
|
||||
// What is the cleanest way to do this? Add function to Generator?
|
||||
var columnsToInsert = string.Join(", ", tableDefinition.Columns.Select(c => _quoter.QuoteColumnName(c.Name)));
|
||||
var columnsToFetch = string.Join(", ", (oldColumnDefinitions ?? tableDefinition.Columns).Select(c => _quoter.QuoteColumnName(c.Name)));
|
||||
var quoter = new SQLiteQuoter();
|
||||
var columnsToInsert = string.Join(", ", tableDefinition.Columns.Select(c => quoter.QuoteColumnName(c.Name)));
|
||||
var columnsToFetch = string.Join(", ", (oldColumnDefinitions ?? tableDefinition.Columns).Select(c => quoter.QuoteColumnName(c.Name)));
|
||||
|
||||
Process(new CreateTableExpression { TableName = tempTableName, Columns = tableDefinition.Columns.ToList() });
|
||||
Process(new CreateTableExpression() { TableName = tempTableName, Columns = tableDefinition.Columns.ToList() });
|
||||
|
||||
Process($"INSERT INTO {_quoter.QuoteTableName(tempTableName)} ({columnsToInsert}) SELECT {columnsToFetch} FROM {_quoter.QuoteTableName(tableName)}");
|
||||
Process(string.Format("INSERT INTO {0} ({1}) SELECT {2} FROM {3}", quoter.QuoteTableName(tempTableName), columnsToInsert, columnsToFetch, quoter.QuoteTableName(tableName)));
|
||||
|
||||
Process(new DeleteTableExpression { TableName = tableName });
|
||||
Process(new DeleteTableExpression() { TableName = tableName });
|
||||
|
||||
Process(new RenameTableExpression { OldName = tempTableName, NewName = tableName });
|
||||
Process(new RenameTableExpression() { OldName = tempTableName, NewName = tableName });
|
||||
|
||||
foreach (var index in tableDefinition.Indexes)
|
||||
{
|
||||
Process(new CreateIndexExpression { Index = index });
|
||||
Process(new CreateIndexExpression() { Index = index });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,76 +0,0 @@
|
|||
using System.Data;
|
||||
using FluentMigrator.Runner.Generators.Base;
|
||||
using FluentMigrator.Runner.Generators.SQLite;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
// Based on https://github.com/fluentmigrator/fluentmigrator/blob/v6.2.0/src/FluentMigrator.Runner.SQLite/Generators/SQLite/SQLiteTypeMap.cs
|
||||
public sealed class NzbDroneSQLiteTypeMap : TypeMapBase, ISQLiteTypeMap
|
||||
{
|
||||
public bool UseStrictTables { get; }
|
||||
|
||||
public NzbDroneSQLiteTypeMap(bool useStrictTables = false)
|
||||
{
|
||||
UseStrictTables = useStrictTables;
|
||||
|
||||
SetupTypeMaps();
|
||||
}
|
||||
|
||||
// Must be kept in sync with upstream
|
||||
protected override void SetupTypeMaps()
|
||||
{
|
||||
SetTypeMap(DbType.Binary, "BLOB");
|
||||
SetTypeMap(DbType.Byte, "INTEGER");
|
||||
SetTypeMap(DbType.Int16, "INTEGER");
|
||||
SetTypeMap(DbType.Int32, "INTEGER");
|
||||
SetTypeMap(DbType.Int64, "INTEGER");
|
||||
SetTypeMap(DbType.SByte, "INTEGER");
|
||||
SetTypeMap(DbType.UInt16, "INTEGER");
|
||||
SetTypeMap(DbType.UInt32, "INTEGER");
|
||||
SetTypeMap(DbType.UInt64, "INTEGER");
|
||||
|
||||
if (!UseStrictTables)
|
||||
{
|
||||
SetTypeMap(DbType.Currency, "NUMERIC");
|
||||
SetTypeMap(DbType.Decimal, "NUMERIC");
|
||||
SetTypeMap(DbType.Double, "NUMERIC");
|
||||
SetTypeMap(DbType.Single, "NUMERIC");
|
||||
SetTypeMap(DbType.VarNumeric, "NUMERIC");
|
||||
SetTypeMap(DbType.Date, "DATETIME");
|
||||
SetTypeMap(DbType.DateTime, "DATETIME");
|
||||
SetTypeMap(DbType.DateTime2, "DATETIME");
|
||||
SetTypeMap(DbType.Time, "DATETIME");
|
||||
SetTypeMap(DbType.Guid, "UNIQUEIDENTIFIER");
|
||||
|
||||
// Custom so that we can use DateTimeOffset in Postgres for appropriate DB typing
|
||||
SetTypeMap(DbType.DateTimeOffset, "DATETIME");
|
||||
}
|
||||
else
|
||||
{
|
||||
SetTypeMap(DbType.Currency, "TEXT");
|
||||
SetTypeMap(DbType.Decimal, "TEXT");
|
||||
SetTypeMap(DbType.Double, "REAL");
|
||||
SetTypeMap(DbType.Single, "REAL");
|
||||
SetTypeMap(DbType.VarNumeric, "TEXT");
|
||||
SetTypeMap(DbType.Date, "TEXT");
|
||||
SetTypeMap(DbType.DateTime, "TEXT");
|
||||
SetTypeMap(DbType.DateTime2, "TEXT");
|
||||
SetTypeMap(DbType.Time, "TEXT");
|
||||
SetTypeMap(DbType.Guid, "TEXT");
|
||||
|
||||
// Custom so that we can use DateTimeOffset in Postgres for appropriate DB typing
|
||||
SetTypeMap(DbType.DateTimeOffset, "TEXT");
|
||||
}
|
||||
|
||||
SetTypeMap(DbType.AnsiString, "TEXT");
|
||||
SetTypeMap(DbType.String, "TEXT");
|
||||
SetTypeMap(DbType.AnsiStringFixedLength, "TEXT");
|
||||
SetTypeMap(DbType.StringFixedLength, "TEXT");
|
||||
SetTypeMap(DbType.Boolean, "INTEGER");
|
||||
}
|
||||
|
||||
public override string GetTypeMap(DbType type, int? size, int? precision)
|
||||
{
|
||||
return base.GetTypeMap(type, size: null, precision: null);
|
||||
}
|
||||
}
|
||||
|
|
@ -120,7 +120,7 @@ private IEnumerable<IndexerRequest> GetPagedRequests(SearchCriteriaBase searchCr
|
|||
baseUrl += "&apikey=" + Settings.ApiKey;
|
||||
}
|
||||
|
||||
if (searchCriteria.Limit is > 0)
|
||||
if (searchCriteria.Limit.HasValue)
|
||||
{
|
||||
parameters.Add("limit", searchCriteria.Limit.ToString());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -263,7 +263,7 @@ private IEnumerable<IndexerRequest> GetPagedRequests(SearchCriteriaBase searchCr
|
|||
searchUrl += "&apikey=" + Settings.ApiKey;
|
||||
}
|
||||
|
||||
if (searchCriteria.Limit is > 0)
|
||||
if (searchCriteria.Limit.HasValue)
|
||||
{
|
||||
parameters.Set("limit", searchCriteria.Limit.ToString());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
[Obsolete("Migrated to YAML for Torznab API")]
|
||||
public class SceneTime : TorrentIndexerBase<SceneTimeSettings>
|
||||
{
|
||||
public override string Name => "SceneTime";
|
||||
|
|
@ -42,7 +41,7 @@ public override IIndexerRequestGenerator GetRequestGenerator()
|
|||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new SceneTimeParser(Settings, Capabilities.Categories, _logger);
|
||||
return new SceneTimeParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
||||
|
|
@ -60,7 +59,7 @@ protected override IDictionary<string, string> GetCookies()
|
|||
return CookieUtil.CookieHeaderToDictionary(Settings.Cookie);
|
||||
}
|
||||
|
||||
private static IndexerCapabilities SetCapabilities()
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
|
|
@ -214,13 +213,11 @@ public class SceneTimeParser : IParseIndexerResponse
|
|||
{
|
||||
private readonly SceneTimeSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public SceneTimeParser(SceneTimeSettings settings, IndexerCapabilitiesCategories categories, Logger logger)
|
||||
public SceneTimeParser(SceneTimeSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
|
|
@ -230,22 +227,20 @@ public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
|||
var parser = new HtmlParser();
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
var table = dom.QuerySelector("table#torrenttable");
|
||||
var table = dom.QuerySelector("table.movehere");
|
||||
if (table == null)
|
||||
{
|
||||
_logger.Error("No results, table element is not present in page.");
|
||||
return releaseInfos;
|
||||
return releaseInfos; // no results
|
||||
}
|
||||
|
||||
var headerColumns = table.QuerySelectorAll("thead > tr > th")
|
||||
.Select(x => x.GetAttribute("title") ?? x.QuerySelector("a[title]")?.GetAttribute("title") ?? x.TextContent)
|
||||
var headerColumns = table.QuerySelectorAll("thead > tr > th.cat_Head")
|
||||
.Select(x => x.GetAttribute("title").IsNotNullOrWhiteSpace() ? x.GetAttribute("title") : x.TextContent)
|
||||
.ToList();
|
||||
|
||||
var categoryIndex = headerColumns.FindIndex(x => x.Equals("Type", StringComparison.OrdinalIgnoreCase));
|
||||
var nameIndex = headerColumns.FindIndex(x => x.Equals("Name", StringComparison.OrdinalIgnoreCase));
|
||||
var sizeIndex = headerColumns.FindIndex(x => x.Equals("Size", StringComparison.OrdinalIgnoreCase));
|
||||
var seedersIndex = headerColumns.FindIndex(x => x.Equals("Seeders", StringComparison.OrdinalIgnoreCase));
|
||||
var leechersIndex = headerColumns.FindIndex(x => x.Equals("Leechers", StringComparison.OrdinalIgnoreCase));
|
||||
var seedersIndex = headerColumns.FindIndex(x => x.Equals("Seeder(s)", StringComparison.OrdinalIgnoreCase));
|
||||
var leechersIndex = headerColumns.FindIndex(x => x.Equals("Leecher(s)", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var rows = table.QuerySelectorAll("tbody > tr");
|
||||
|
||||
|
|
@ -253,7 +248,7 @@ public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
|||
{
|
||||
var qDescCol = row.Children[nameIndex];
|
||||
var qLink = qDescCol.QuerySelector("a");
|
||||
var title = qLink.QuerySelector("span.bw-torrent-name").TextContent.Trim();
|
||||
var title = qLink.QuerySelector("span.torrent-text").TextContent.Trim();
|
||||
|
||||
var infoUrl = _settings.BaseUrl + qLink.GetAttribute("href")?.TrimStart('/');
|
||||
var torrentId = ParseUtil.GetArgumentFromQueryString(infoUrl, "id");
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@
|
|||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
[Obsolete("Site does not allow automation")]
|
||||
public class ZonaQ : TorrentIndexerBase<UserPassTorrentBaseSettings>
|
||||
{
|
||||
public override string Name => "ZonaQ";
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@
|
|||
"GeneralSettings": "通用设置",
|
||||
"GeneralSettingsSummary": "端口、SSL、用户名/密码、代理、分析、更新",
|
||||
"Genre": "类型",
|
||||
"GrabReleases": "抓取资源",
|
||||
"GrabReleases": "抓取版本",
|
||||
"GrabTitle": "抓取标题",
|
||||
"Grabbed": "已抓取",
|
||||
"Grabs": "抓取",
|
||||
|
|
@ -621,7 +621,7 @@
|
|||
"DownloadClientFreeboxSettingsAppToken": "App Token",
|
||||
"DownloadClientFreeboxSettingsAppTokenHelpText": "创建访问 Freebox API 所需的 App token(即 “ app_token”)",
|
||||
"DownloadClientQbittorrentSettingsInitialStateHelpText": "添加到 qBittorrent 的种子的初始状态。 请注意,强制做种不遵守种子限制",
|
||||
"GrabRelease": "抓取资源",
|
||||
"GrabRelease": "抓取版本",
|
||||
"ManualGrab": "手动抓取",
|
||||
"OverrideAndAddToDownloadClient": "覆盖并添加到下载队列",
|
||||
"OverrideGrabModalTitle": "覆盖并抓取 - {title}",
|
||||
|
|
@ -755,7 +755,7 @@
|
|||
"IndexerFileListSettingsFreeleechOnlyHelpText": "只搜索免费发布",
|
||||
"IndexerFileListSettingsUsernameHelpText": "网站用户名",
|
||||
"IndexerBeyondHDSettingsRefundOnlyHelpText": "Search refund only",
|
||||
"DownloadClientUTorrentProviderMessage": "uTorrent 曾经含有挖矿行为、恶意软件和广告,我们强烈建议你选择其他客户端。",
|
||||
"DownloadClientUTorrentProviderMessage": "由于uTorrent以加密软件、恶意软件和广告而闻名,我们建议切换到更好的客户端,例如qBittorrent、Deluge或ruTorrent。",
|
||||
"IndexerId": "索引器",
|
||||
"IndexerSettingsPasskey": "通行密钥",
|
||||
"IndexerBeyondHDSettingsRefundOnly": "只读",
|
||||
|
|
@ -763,45 +763,5 @@
|
|||
"IndexerBeyondHDSettingsRssKeyHelpText": "来自网站的API密钥(在我的安全 => API密钥)",
|
||||
"IndexerHDBitsSettingsFreeleechOnlyHelpText": "只搜索免费发布",
|
||||
"IndexerHDBitsSettingsOrigins": "原始",
|
||||
"IndexerPassThePopcornSettingsApiUserHelpText": "这些设置位于 PassThePopcorn 安全设置中(编辑个人资料 > 安全,Edit Profile > Security)。",
|
||||
"IndexerBeyondHDSettingsSearchTypesHelpText": "选择你感兴趣的版本类型。若无则使用所有可选项。",
|
||||
"IndexerFileListSettingsPasskeyHelpText": "网站密钥(下载客户端中跟踪器链接显示的由字母和数字构成的字符串)",
|
||||
"IndexerGazelleGamesSettingsApiKeyHelpText": "来自网站的 API 密钥(在设置 => 访问设置中)",
|
||||
"IndexerGazelleGamesSettingsApiKeyHelpTextWarning": "必须具有用户和种子权限",
|
||||
"IndexerGazelleGamesSettingsSearchGroupNames": "搜索群组名",
|
||||
"IndexerGazelleGamesSettingsSearchGroupNamesHelpText": "根据群组名搜索版本",
|
||||
"IndexerHDBitsSettingsPasskeyHelpText": "用户详情中的密钥",
|
||||
"IndexerHDBitsSettingsUseFilenames": "使用文件名",
|
||||
"IndexerHDBitsSettingsUseFilenamesHelpText": "勾选此选项如果你想将种子文件名作为发布资源的标题",
|
||||
"IndexerIPTorrentsSettingsCookieUserAgentHelpText": "浏览器中 cookie 所关联的 User-Agent",
|
||||
"IndexerMTeamTpSettingsApiKeyHelpText": "站点中的 API 密钥(在用户控制面板 => 安全 => 实验室中)",
|
||||
"IndexerNebulanceSettingsApiKeyHelpText": "在用户设置 > API 密钥中的API 密钥。密钥必须有列出和下载的权限",
|
||||
"IndexerNewznabSettingsApiKeyHelpText": "站点 API 密钥",
|
||||
"IndexerSettingsCookieHelpText": "站点 Cookie",
|
||||
"IndexerSettingsFreeleechOnly": "仅免流资源",
|
||||
"IndexerSettingsGrabLimit": "抓取上限",
|
||||
"IndexerSettingsGrabLimitHelpText": "在该时间单位内,{appName} 对此站点所允许的最大抓取数",
|
||||
"IndexerSettingsLimitsUnit": "限制单位",
|
||||
"IndexerSettingsLimitsUnitHelpText": "每个索引器计算上限的时间单位",
|
||||
"IndexerSettingsPreferMagnetUrl": "磁力链接优先",
|
||||
"IndexerSettingsPreferMagnetUrlHelpText": "若开启,该索引器将优先使用磁力链接抓取,而种子链接作为备用",
|
||||
"IndexerSettingsQueryLimit": "请求上限",
|
||||
"IndexerSettingsQueryLimitHelpText": "在该时间单位内,{appName} 对此站点所允许的最大请求数",
|
||||
"IndexerIPTorrentsSettingsCookieUserAgent": "Cookie User-Agent",
|
||||
"IndexerNewznabSettingsVipExpirationHelpText": "输入 VIP 到期日期 (yyyy-mm-dd) 或留空,{appName} 将在到期前 1 星期发送通知",
|
||||
"IndexerNzbIndexSettingsApiKeyHelpText": "站点 API 密钥",
|
||||
"IndexerOrpheusSettingsApiKeyHelpText": "站点 API 密钥(在设置 => 访问设置中)",
|
||||
"IndexerPassThePopcornSettingsApiKeyHelpText": "站点 API 密钥",
|
||||
"IndexerPassThePopcornSettingsGoldenPopcornOnly": "仅 Golden Popcorn",
|
||||
"IndexerPassThePopcornSettingsGoldenPopcornOnlyHelpText": "仅搜索 Golden Popcorn 发布的资源",
|
||||
"IndexerRedactedSettingsApiKeyHelpText": "站点 API 密钥(在设置 => 访问设置中)",
|
||||
"IndexerSettingsBaseUrl": "基础链接",
|
||||
"IndexerSettingsBaseUrlHelpText": "选择 {appName} 请求该站点所使用的基础链接",
|
||||
"IndexerTorrentSyndikatSettingsApiKeyHelpText": "站点 API 密钥",
|
||||
"NoApplicationsFound": "找不到程序",
|
||||
"Open": "打开",
|
||||
"PreferMagnetUrl": "磁力链接优先",
|
||||
"PreferMagnetUrlHelpText": "若开启,该索引器将优先使用磁力链接,种子链接作为备用",
|
||||
"ProwlarrDownloadClientsAlert": "如果你想直接在 {appName} 中搜索,你需要添加下载管理器,否则你不需要在此添加。你的程序中的搜索行为将使用它自己的下载管理器配置。",
|
||||
"ProwlarrDownloadClientsInAppOnlyAlert": "下载管理器仅对 {appName} 中搜索有效,而不会同步到其他程序中。目前没有添加此功能的计划。"
|
||||
"IndexerPassThePopcornSettingsApiUserHelpText": "这些设置位于 PassThePopcorn 安全设置中(编辑个人资料 > 安全,Edit Profile > Security)。"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,16 +6,16 @@
|
|||
<PackageReference Include="AngleSharp.Xml" Version="1.0.0" />
|
||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
||||
<PackageReference Include="Diacritical.Net" Version="1.0.4" />
|
||||
<PackageReference Include="MailKit" Version="4.16.0" />
|
||||
<PackageReference Include="MailKit" Version="4.14.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="8.0.16" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="8.0.16" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.1" />
|
||||
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
|
||||
<PackageReference Include="Npgsql" Version="9.0.3" />
|
||||
<PackageReference Include="Polly" Version="8.6.4" />
|
||||
<PackageReference Include="FluentMigrator.Runner.Core" Version="6.2.0" />
|
||||
<PackageReference Include="FluentMigrator.Runner.Postgres" Version="6.2.0" />
|
||||
<PackageReference Include="FluentMigrator.Runner.SQLite" Version="6.2.0" />
|
||||
<PackageReference Include="Servarr.FluentMigrator.Runner" Version="3.3.2.9" />
|
||||
<PackageReference Include="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" />
|
||||
<PackageReference Include="Servarr.FluentMigrator.Runner.SQLite" Version="3.3.2.9" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.19" />
|
||||
<PackageReference Include="System.Memory" Version="4.6.3" />
|
||||
<PackageReference Include="System.ServiceModel.Syndication" Version="8.0.0" />
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ public virtual Mock<T> GetMock<T>(MockBehavior behavior)
|
|||
|
||||
if (behavior != MockBehavior.Default && mock.Behavior == MockBehavior.Default)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to change be behaviour of an existing mock.");
|
||||
throw new InvalidOperationException("Unable to change be behaviour of a an existing mock.");
|
||||
}
|
||||
|
||||
return mock;
|
||||
|
|
@ -139,7 +139,7 @@ private void SetupAutoMoqer(IContainer container)
|
|||
|
||||
LoadPlatformLibrary();
|
||||
|
||||
AssemblyLoader.RegisterNativeResolver(new[] { "System.Data.SQLite", "Prowlarr.Core" });
|
||||
AssemblyLoader.RegisterSQLiteResolver();
|
||||
}
|
||||
|
||||
private Mock<T> TheRegisteredMockForThisType<T>(Type type)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ public SearchResource()
|
|||
public string Type { get; set; }
|
||||
public List<int> IndexerIds { get; set; }
|
||||
public List<int> Categories { get; set; }
|
||||
public int? Limit { get; set; }
|
||||
public int? Offset { get; set; }
|
||||
public int Limit { get; set; }
|
||||
public int Offset { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,13 +15,11 @@ public BackupFileMapper(IBackupService backupService, IDiskProvider diskProvider
|
|||
_backupService = backupService;
|
||||
}
|
||||
|
||||
protected override string FolderPath => _backupService.GetBackupFolder().TrimEnd(Path.DirectorySeparatorChar);
|
||||
|
||||
protected override string MapPath(string resourceUrl)
|
||||
public override string Map(string resourceUrl)
|
||||
{
|
||||
var path = resourceUrl.Replace("/backup/", "").Replace('/', Path.DirectorySeparatorChar);
|
||||
|
||||
return Path.Combine(FolderPath, path);
|
||||
return Path.Combine(_backupService.GetBackupFolder(), path);
|
||||
}
|
||||
|
||||
public override bool CanHandle(string resourceUrl)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.IO;
|
||||
using System.IO;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
|
@ -6,29 +6,29 @@
|
|||
|
||||
namespace Prowlarr.Http.Frontend.Mappers
|
||||
{
|
||||
public class BrowserConfig : UrlBaseReplacementResourceMapperBase
|
||||
public class BrowserConfig : StaticResourceMapperBase
|
||||
{
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
|
||||
public BrowserConfig(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IConfigFileProvider configFileProvider, Logger logger)
|
||||
: base(diskProvider, configFileProvider, logger)
|
||||
: base(diskProvider, logger)
|
||||
{
|
||||
_appFolderInfo = appFolderInfo;
|
||||
_configFileProvider = configFileProvider;
|
||||
}
|
||||
|
||||
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)
|
||||
public override string Map(string resourceUrl)
|
||||
{
|
||||
return FilePath;
|
||||
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
|
||||
path = path.Trim(Path.DirectorySeparatorChar);
|
||||
|
||||
return Path.ChangeExtension(Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, path), "xml");
|
||||
}
|
||||
|
||||
public override bool CanHandle(string resourceUrl)
|
||||
{
|
||||
return resourceUrl.StartsWith("/Content/browserconfig");
|
||||
return resourceUrl.StartsWith("/content/images/icons/browserconfig");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,12 +30,6 @@ 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('=');
|
||||
|
|
|
|||
|
|
@ -18,9 +18,7 @@ public FaviconMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, I
|
|||
_configFileProvider = configFileProvider;
|
||||
}
|
||||
|
||||
protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder);
|
||||
|
||||
protected override string MapPath(string resourceUrl)
|
||||
public override string Map(string resourceUrl)
|
||||
{
|
||||
var fileName = "favicon.ico";
|
||||
|
||||
|
|
@ -31,7 +29,7 @@ protected override string MapPath(string resourceUrl)
|
|||
|
||||
var path = Path.Combine("Content", "Images", "Icons", fileName);
|
||||
|
||||
return Path.Combine(FolderPath, path);
|
||||
return Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, path);
|
||||
}
|
||||
|
||||
public override bool CanHandle(string resourceUrl)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace Prowlarr.Http.Frontend.Mappers
|
||||
{
|
||||
|
|
@ -14,22 +13,19 @@ 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 abstract string HtmlPath { get; }
|
||||
protected string HtmlPath;
|
||||
protected string UrlBase;
|
||||
|
||||
protected override Stream GetContentStream(string filePath)
|
||||
{
|
||||
|
|
@ -66,10 +62,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;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,6 @@ public interface IMapHttpRequestsToDisk
|
|||
{
|
||||
string Map(string resourceUrl);
|
||||
bool CanHandle(string resourceUrl);
|
||||
Task<IActionResult> GetResponse(string resourceUrl);
|
||||
Task<FileStreamResult> GetResponse(string resourceUrl);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ namespace Prowlarr.Http.Frontend.Mappers
|
|||
{
|
||||
public class IndexHtmlMapper : HtmlMapperBase
|
||||
{
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
|
||||
public IndexHtmlMapper(IAppFolderInfo appFolderInfo,
|
||||
|
|
@ -17,16 +16,15 @@ public IndexHtmlMapper(IAppFolderInfo appFolderInfo,
|
|||
IConfigFileProvider configFileProvider,
|
||||
Lazy<ICacheBreakerProvider> cacheBreakProviderFactory,
|
||||
Logger logger)
|
||||
: base(diskProvider, configFileProvider, cacheBreakProviderFactory, logger)
|
||||
: base(diskProvider, cacheBreakProviderFactory, logger)
|
||||
{
|
||||
_appFolderInfo = appFolderInfo;
|
||||
_configFileProvider = configFileProvider;
|
||||
|
||||
HtmlPath = Path.Combine(appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, "index.html");
|
||||
UrlBase = configFileProvider.UrlBase;
|
||||
}
|
||||
|
||||
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)
|
||||
public override string Map(string resourceUrl)
|
||||
{
|
||||
return HtmlPath;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,14 +16,12 @@ public LogFileMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, L
|
|||
_appFolderInfo = appFolderInfo;
|
||||
}
|
||||
|
||||
protected override string FolderPath => _appFolderInfo.GetLogFolder();
|
||||
|
||||
protected override string MapPath(string resourceUrl)
|
||||
public override string Map(string resourceUrl)
|
||||
{
|
||||
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
|
||||
path = Path.GetFileName(path);
|
||||
|
||||
return Path.Combine(FolderPath, path);
|
||||
return Path.Combine(_appFolderInfo.GetLogFolder(), path);
|
||||
}
|
||||
|
||||
public override bool CanHandle(string resourceUrl)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ namespace Prowlarr.Http.Frontend.Mappers
|
|||
{
|
||||
public class LoginHtmlMapper : HtmlMapperBase
|
||||
{
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
|
||||
public LoginHtmlMapper(IAppFolderInfo appFolderInfo,
|
||||
|
|
@ -17,16 +16,14 @@ public LoginHtmlMapper(IAppFolderInfo appFolderInfo,
|
|||
Lazy<ICacheBreakerProvider> cacheBreakProviderFactory,
|
||||
IConfigFileProvider configFileProvider,
|
||||
Logger logger)
|
||||
: base(diskProvider, configFileProvider, cacheBreakProviderFactory, logger)
|
||||
: base(diskProvider, cacheBreakProviderFactory, logger)
|
||||
{
|
||||
_appFolderInfo = appFolderInfo;
|
||||
_configFileProvider = configFileProvider;
|
||||
HtmlPath = Path.Combine(appFolderInfo.StartUpFolder, configFileProvider.UiFolder, "login.html");
|
||||
UrlBase = configFileProvider.UrlBase;
|
||||
}
|
||||
|
||||
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)
|
||||
public override string Map(string resourceUrl)
|
||||
{
|
||||
return HtmlPath;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.IO;
|
||||
using System.IO;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
|
@ -6,47 +6,29 @@
|
|||
|
||||
namespace Prowlarr.Http.Frontend.Mappers
|
||||
{
|
||||
public class ManifestMapper : UrlBaseReplacementResourceMapperBase
|
||||
public class ManifestMapper : StaticResourceMapperBase
|
||||
{
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
|
||||
private string _generatedContent;
|
||||
|
||||
public ManifestMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IConfigFileProvider configFileProvider, Logger logger)
|
||||
: base(diskProvider, configFileProvider, logger)
|
||||
: base(diskProvider, logger)
|
||||
{
|
||||
_appFolderInfo = appFolderInfo;
|
||||
_configFileProvider = configFileProvider;
|
||||
}
|
||||
|
||||
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)
|
||||
public override string Map(string resourceUrl)
|
||||
{
|
||||
return FilePath;
|
||||
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
|
||||
path = path.Trim(Path.DirectorySeparatorChar);
|
||||
|
||||
return Path.ChangeExtension(Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, path), "json");
|
||||
}
|
||||
|
||||
public override bool CanHandle(string resourceUrl)
|
||||
{
|
||||
return resourceUrl.StartsWith("/Content/manifest");
|
||||
}
|
||||
|
||||
protected override string GetFileText()
|
||||
{
|
||||
if (RuntimeInfo.IsProduction && _generatedContent != null)
|
||||
{
|
||||
return _generatedContent;
|
||||
}
|
||||
|
||||
var text = base.GetFileText();
|
||||
|
||||
text = text.Replace("__INSTANCE_NAME__", _configFileProvider.InstanceName);
|
||||
|
||||
_generatedContent = text;
|
||||
|
||||
return _generatedContent;
|
||||
return resourceUrl.StartsWith("/Content/Images/Icons/manifest");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,9 +22,7 @@ public MediaCoverMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider
|
|||
_diskProvider = diskProvider;
|
||||
}
|
||||
|
||||
protected override string FolderPath => Path.Combine(_appFolderInfo.GetAppDataPath(), "MediaCover");
|
||||
|
||||
protected override string MapPath(string resourceUrl)
|
||||
public override string Map(string resourceUrl)
|
||||
{
|
||||
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
|
||||
path = path.Trim(Path.DirectorySeparatorChar);
|
||||
|
|
|
|||
|
|
@ -18,13 +18,11 @@ public RobotsTxtMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider,
|
|||
_configFileProvider = configFileProvider;
|
||||
}
|
||||
|
||||
protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder);
|
||||
|
||||
protected override string MapPath(string resourceUrl)
|
||||
public override string Map(string resourceUrl)
|
||||
{
|
||||
var path = Path.Combine("Content", "robots.txt");
|
||||
|
||||
return Path.Combine(FolderPath, path);
|
||||
return Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, path);
|
||||
}
|
||||
|
||||
public override bool CanHandle(string resourceUrl)
|
||||
|
|
|
|||
|
|
@ -18,22 +18,20 @@ public StaticResourceMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProv
|
|||
_configFileProvider = configFileProvider;
|
||||
}
|
||||
|
||||
protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder);
|
||||
|
||||
protected override string MapPath(string resourceUrl)
|
||||
public override string Map(string resourceUrl)
|
||||
{
|
||||
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
|
||||
path = path.Trim(Path.DirectorySeparatorChar);
|
||||
|
||||
return Path.Combine(FolderPath, path);
|
||||
return Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, path);
|
||||
}
|
||||
|
||||
public override bool CanHandle(string resourceUrl)
|
||||
{
|
||||
resourceUrl = resourceUrl.ToLowerInvariant();
|
||||
|
||||
if (resourceUrl.StartsWith("/content/manifest") ||
|
||||
resourceUrl.StartsWith("/content/browserconfig"))
|
||||
if (resourceUrl.StartsWith("/content/images/icons/manifest") ||
|
||||
resourceUrl.StartsWith("/content/images/icons/browserconfig"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,28 +27,14 @@ protected StaticResourceMapperBase(IDiskProvider diskProvider, Logger logger)
|
|||
_caseSensitive = RuntimeInfo.IsProduction ? DiskProviderBase.PathStringComparison : StringComparison.OrdinalIgnoreCase;
|
||||
}
|
||||
|
||||
protected abstract string FolderPath { get; }
|
||||
protected abstract string MapPath(string resourceUrl);
|
||||
public abstract string Map(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)
|
||||
public Task<FileStreamResult> 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))
|
||||
|
|
@ -56,7 +42,7 @@ public Task<IActionResult> GetResponse(string resourceUrl)
|
|||
contentType = "application/octet-stream";
|
||||
}
|
||||
|
||||
return Task.FromResult<IActionResult>(new FileStreamResult(GetContentStream(filePath), new MediaTypeHeaderValue(contentType)
|
||||
return Task.FromResult(new FileStreamResult(GetContentStream(filePath), new MediaTypeHeaderValue(contentType)
|
||||
{
|
||||
Encoding = contentType == "text/plain" ? Encoding.UTF8 : null
|
||||
}));
|
||||
|
|
@ -64,7 +50,7 @@ public Task<IActionResult> GetResponse(string resourceUrl)
|
|||
|
||||
_logger.Warn("File {0} not found", filePath);
|
||||
|
||||
return Task.FromResult<IActionResult>(null);
|
||||
return Task.FromResult<FileStreamResult>(null);
|
||||
}
|
||||
|
||||
protected virtual Stream GetContentStream(string filePath)
|
||||
|
|
|
|||
|
|
@ -16,14 +16,12 @@ public UpdateLogFileMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvi
|
|||
_appFolderInfo = appFolderInfo;
|
||||
}
|
||||
|
||||
protected override string FolderPath => _appFolderInfo.GetUpdateLogFolder();
|
||||
|
||||
protected override string MapPath(string resourceUrl)
|
||||
public override string Map(string resourceUrl)
|
||||
{
|
||||
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
|
||||
path = Path.GetFileName(path);
|
||||
|
||||
return Path.Combine(FolderPath, path);
|
||||
return Path.Combine(_appFolderInfo.GetUpdateLogFolder(), path);
|
||||
}
|
||||
|
||||
public override bool CanHandle(string resourceUrl)
|
||||
|
|
|
|||
|
|
@ -1,58 +0,0 @@
|
|||
using System.IO;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace Prowlarr.Http.Frontend.Mappers
|
||||
{
|
||||
public abstract class UrlBaseReplacementResourceMapperBase : StaticResourceMapperBase
|
||||
{
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly string _urlBase;
|
||||
|
||||
private string _generatedContent;
|
||||
|
||||
public UrlBaseReplacementResourceMapperBase(IDiskProvider diskProvider, IConfigFileProvider configFileProvider, Logger logger)
|
||||
: base(diskProvider, logger)
|
||||
{
|
||||
_diskProvider = diskProvider;
|
||||
_urlBase = configFileProvider.UrlBase;
|
||||
}
|
||||
|
||||
protected abstract string FilePath { get; }
|
||||
|
||||
protected override string MapPath(string resourceUrl)
|
||||
{
|
||||
return FilePath;
|
||||
}
|
||||
|
||||
protected override Stream GetContentStream(string filePath)
|
||||
{
|
||||
var text = GetFileText();
|
||||
|
||||
var stream = new MemoryStream();
|
||||
var writer = new StreamWriter(stream);
|
||||
writer.Write(text);
|
||||
writer.Flush();
|
||||
stream.Position = 0;
|
||||
return stream;
|
||||
}
|
||||
|
||||
protected virtual string GetFileText()
|
||||
{
|
||||
if (RuntimeInfo.IsProduction && _generatedContent != null)
|
||||
{
|
||||
return _generatedContent;
|
||||
}
|
||||
|
||||
var text = _diskProvider.ReadAllText(FilePath);
|
||||
|
||||
text = text.Replace("__URL_BASE__", _urlBase);
|
||||
|
||||
_generatedContent = text;
|
||||
|
||||
return _generatedContent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
|
|
@ -17,7 +16,6 @@ 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)
|
||||
|
|
@ -52,11 +50,6 @@ 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)
|
||||
|
|
@ -65,7 +58,7 @@ private async Task<IActionResult> MapResource(string path)
|
|||
|
||||
if (result != null)
|
||||
{
|
||||
if ((result as FileResult)?.ContentType == "text/html")
|
||||
if (result.ContentType == "text/html")
|
||||
{
|
||||
Response.Headers.DisableCache();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2289,9 +2289,9 @@ camelcase@^5.3.1:
|
|||
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
|
||||
|
||||
caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001663, caniuse-lite@^1.0.30001716:
|
||||
version "1.0.30001782"
|
||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001782.tgz"
|
||||
integrity sha512-dZcaJLJeDMh4rELYFw1tvSn1bhZWYFOt468FcbHHxx/Z/dFidd1I6ciyFdi3iwfQCyOjqo9upF6lGQYtMiJWxw==
|
||||
version "1.0.30001718"
|
||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz"
|
||||
integrity sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==
|
||||
|
||||
chalk@^2.4.1, chalk@^2.4.2:
|
||||
version "2.4.2"
|
||||
|
|
|
|||
Loading…
Reference in a new issue