Compare commits

..

11 Commits

Author SHA1 Message Date
Auggie
71d1a59008 chore: Fix Innosetup download URI and bump Innosetup version 2026-03-29 01:34:30 +01:00
Bogdan
33fa39dc84 Fixed: (SceneTime) Update layout selectors 2026-03-29 00:36:43 +01:00
Auggie
d133c82537 Revert incorrectly deleted function in MigrationExtension 2026-03-29 00:35:53 +01:00
Auggie
6b446e1404 chore: Clean up unused NuGet dependencies 2026-03-28 14:24:34 +01:00
Bogdan
b0e879da5c fixed: Loading native libraries on FreeBSD and Linux 2026-03-28 14:24:04 +01:00
Bogdan
5edde8d9bd Switch to FluentMigrator.Runner.Core to avoid extranous platform runners 2026-03-24 20:14:46 +01:00
Bogdan
ef5d670c39 Fallback to host sqlite3 on FreeBSD and Linux 2026-03-24 20:13:46 +01:00
Bogdan
f568906876 Bump FluentMigrator to official 6.2.0 2026-03-24 20:13:46 +01:00
Auggie
331e92ac62 Bump to 2.3.5 2026-03-22 21:21:21 +01:00
Weblate
ec46b25be2 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: 康小广 <kenkangxgwe@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2026-03-22 01:33:26 +01:00
Bogdan
8b3837cb6e Fixed: Parsing URLs on some systems due to Locale 2026-03-15 00:59:58 +01:00
14 changed files with 266 additions and 78 deletions

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '2.3.4'
majorVersion: '2.3.5'
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.2.2'
innoVersion: '6.7.1'
windowsImage: 'windows-2025'
linuxImage: 'ubuntu-24.04'
macImage: 'macOS-15'

View File

@@ -253,8 +253,10 @@ InstallInno()
{
ProgressStart "Installing portable Inno Setup"
INNOVERSION=${INNOVERSION:-6.7.1}
rm -rf _inno
curl -s --output innosetup.exe "https://files.jrsoftware.org/is/6/innosetup-${INNOVERSION:-6.2.2}.exe"
curl -s -L --output innosetup.exe "https://github.com/jrsoftware/issrc/releases/download/is-${INNOVERSION//./_}/innosetup-${INNOVERSION}.exe"
mkdir _inno
./innosetup.exe //portable=1 //silent //currentuser //dir=.\\_inno
rm innosetup.exe

View File

@@ -5,6 +5,5 @@
<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>

View File

@@ -14,7 +14,6 @@ namespace NzbDrone.Common.Composition
static AssemblyLoader()
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(ContainerResolveEventHandler);
RegisterSQLiteResolver();
}
public static IList<Assembly> Load(IList<string> assemblyNames)
@@ -23,6 +22,10 @@ namespace NzbDrone.Common.Composition
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
@@ -43,27 +46,46 @@ namespace NzbDrone.Common.Composition
return AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
}
public static void RegisterSQLiteResolver()
public static void RegisterNativeResolver(IEnumerable<string> assemblyNames)
{
// 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"));
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"));
try
{
NativeLibrary.SetDllImportResolver(sqliteAssembly, LoadSqliteNativeLib);
}
catch (InvalidOperationException)
{
// This can only be set once per assembly
// Catch required for NzbDrone.Host tests
try
{
NativeLibrary.SetDllImportResolver(assembly, LoadNativeLib);
}
catch (InvalidOperationException)
{
// This can only be set once per assembly
// Catch required for NzbDrone.Host tests
}
}
}
private static IntPtr LoadSqliteNativeLib(string libraryName, Assembly assembly, DllImportSearchPath? dllImportSearchPath)
private static IntPtr LoadNativeLib(string libraryName, Assembly assembly, DllImportSearchPath? dllImportSearchPath)
{
var mappedName = OsInfo.IsLinux && libraryName == "sqlite3" ? "libsqlite3.so.0" : libraryName;
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";
}
}
return NativeLibrary.Load(mappedName, assembly, dllImportSearchPath);
}
}

View File

@@ -6,13 +6,14 @@ using NzbDrone.Common.Extensions;
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 @@ namespace NzbDrone.Common.Http
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

@@ -7,7 +7,7 @@ using NzbDrone.Common.Instrumentation;
namespace NzbDrone.Core.Datastore.Migration
{
[Maintenance(MigrationStage.BeforeAll, TransactionBehavior.None)]
public class DatabaseEngineVersionCheck : FluentMigrator.Migration
public class DatabaseEngineVersionCheck : ForwardOnlyMigration
{
protected readonly Logger _logger;
@@ -22,11 +22,6 @@ namespace NzbDrone.Core.Datastore.Migration
IfDatabase("postgres").Execute.WithConnection(LogPostgresVersion);
}
public override void Down()
{
// No-op
}
private void LogSqliteVersion(IDbConnection conn, IDbTransaction tran)
{
using (var versionCmd = conn.CreateCommand())

View File

@@ -6,7 +6,6 @@ using FluentMigrator.Runner.Generators;
using FluentMigrator.Runner.Initialization;
using FluentMigrator.Runner.Processors;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NLog;
using NLog.Extensions.Logging;
@@ -20,13 +19,10 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
public class MigrationController : IMigrationController
{
private readonly Logger _logger;
private readonly ILoggerProvider _migrationLoggerProvider;
public MigrationController(Logger logger,
ILoggerProvider migrationLoggerProvider)
public MigrationController(Logger logger)
{
_logger = logger;
_migrationLoggerProvider = migrationLoggerProvider;
}
public void Migrate(string connectionString, MigrationContext migrationContext, DatabaseType databaseType)
@@ -35,16 +31,13 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
_logger.Info("*** Migrating {0} ***", connectionString);
ServiceProvider serviceProvider;
var db = databaseType == DatabaseType.SQLite ? "sqlite" : "postgres";
serviceProvider = new ServiceCollection()
var serviceProvider = new ServiceCollection()
.AddLogging(b => b.AddNLog())
.AddFluentMigratorCore()
.Configure<RunnerOptions>(cfg => cfg.IncludeUntaggedMaintenances = true)
.ConfigureRunner(
builder => builder
.ConfigureRunner(builder => builder
.AddPostgres()
.AddNzbDroneSQLite()
.WithGlobalConnectionString(connectionString)

View File

@@ -1,11 +1,17 @@
using FluentMigrator;
using System.Data;
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
{
@@ -16,23 +22,49 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
return expressionRoot.Table(name).WithColumn("Id").AsInt32().PrimaryKey().Identity();
}
public static void AddParameter(this System.Data.IDbCommand command, object value)
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)
{
var parameter = command.CreateParameter();
parameter.Value = value;
command.Parameters.Add(parameter);
}
public static IMigrationRunnerBuilder AddNzbDroneSQLite(this IMigrationRunnerBuilder builder)
public static IMigrationRunnerBuilder AddNzbDroneSQLite(this IMigrationRunnerBuilder builder, bool binaryGuid = false, bool useStrictTables = false)
{
builder.Services
.AddTransient<SQLiteBatchParser>()
.AddScoped<SQLiteDbFactory>()
.AddScoped<NzbDroneSQLiteProcessor>()
.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<IMigrationProcessor>(sp => sp.GetRequiredService<NzbDroneSQLiteProcessor>())
.AddScoped<SQLiteQuoter>()
.AddScoped<SQLiteGenerator>()
.AddScoped(
sp =>
{
var typeMap = sp.GetRequiredService<ISQLiteTypeMap>();
return new SQLiteGenerator(
new SQLiteQuoter(binaryGuid),
typeMap,
new OptionsWrapper<GeneratorOptions>(new GeneratorOptions()));
})
.AddScoped<IMigrationGenerator>(sp => sp.GetRequiredService<SQLiteGenerator>());
return builder;
}
}

View File

@@ -15,6 +15,8 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
{
public class NzbDroneSQLiteProcessor : SQLiteProcessor
{
private readonly SQLiteQuoter _quoter;
public NzbDroneSQLiteProcessor(SQLiteDbFactory factory,
SQLiteGenerator generator,
ILogger<NzbDroneSQLiteProcessor> logger,
@@ -24,6 +26,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
SQLiteQuoter quoter)
: base(factory, generator, logger, options, connectionStringAccessor, serviceProvider, quoter)
{
_quoter = quoter;
}
public override void Process(AlterColumnExpression expression)
@@ -35,7 +38,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
if (columnIndex == -1)
{
throw new ApplicationException(string.Format("Column {0} does not exist on table {1}.", expression.Column.Name, expression.TableName));
throw new ApplicationException($"Column {expression.Column.Name} does not exist on table {expression.TableName}.");
}
columnDefinitions[columnIndex] = expression.Column;
@@ -45,6 +48,28 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
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);
@@ -62,7 +87,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
if (columnsToRemove.Any())
{
throw new ApplicationException(string.Format("Column {0} does not exist on table {1}.", columnsToRemove.First(), expression.TableName));
throw new ApplicationException($"Column {columnsToRemove.First()} does not exist on table {expression.TableName}.");
}
ProcessAlterTable(tableDefinition);
@@ -78,12 +103,12 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
if (columnIndex == -1)
{
throw new ApplicationException(string.Format("Column {0} does not exist on table {1}.", expression.OldName, expression.TableName));
throw new ApplicationException($"Column {expression.OldName} does not exist on table {expression.TableName}.");
}
if (columnDefinitions.Any(c => c.Name == expression.NewName))
{
throw new ApplicationException(string.Format("Column {0} already exists on table {1}.", expression.NewName, expression.TableName));
throw new ApplicationException($"Column {expression.NewName} already exists on table {expression.TableName}.");
}
oldColumnDefinitions[columnIndex] = (ColumnDefinition)columnDefinitions[columnIndex].Clone();
@@ -128,21 +153,20 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
}
// What is the cleanest way to do this? Add function to Generator?
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)));
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(string.Format("INSERT INTO {0} ({1}) SELECT {2} FROM {3}", quoter.QuoteTableName(tempTableName), columnsToInsert, columnsToFetch, quoter.QuoteTableName(tableName)));
Process($"INSERT INTO {_quoter.QuoteTableName(tempTableName)} ({columnsToInsert}) SELECT {columnsToFetch} FROM {_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 });
}
}
}

View File

@@ -0,0 +1,76 @@
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);
}
}

View File

@@ -41,7 +41,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IParseIndexerResponse GetParser()
{
return new SceneTimeParser(Settings, Capabilities.Categories);
return new SceneTimeParser(Settings, Capabilities.Categories, _logger);
}
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
@@ -59,7 +59,7 @@ namespace NzbDrone.Core.Indexers.Definitions
return CookieUtil.CookieHeaderToDictionary(Settings.Cookie);
}
private IndexerCapabilities SetCapabilities()
private static IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
@@ -213,11 +213,13 @@ namespace NzbDrone.Core.Indexers.Definitions
{
private readonly SceneTimeSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
private readonly Logger _logger;
public SceneTimeParser(SceneTimeSettings settings, IndexerCapabilitiesCategories categories)
public SceneTimeParser(SceneTimeSettings settings, IndexerCapabilitiesCategories categories, Logger logger)
{
_settings = settings;
_categories = categories;
_logger = logger;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
@@ -227,20 +229,22 @@ namespace NzbDrone.Core.Indexers.Definitions
var parser = new HtmlParser();
using var dom = parser.ParseDocument(indexerResponse.Content);
var table = dom.QuerySelector("table.movehere");
var table = dom.QuerySelector("table#torrenttable");
if (table == null)
{
return releaseInfos; // no results
_logger.Error("No results, table element is not present in page.");
return releaseInfos;
}
var headerColumns = table.QuerySelectorAll("thead > tr > th.cat_Head")
.Select(x => x.GetAttribute("title").IsNotNullOrWhiteSpace() ? x.GetAttribute("title") : x.TextContent)
var headerColumns = table.QuerySelectorAll("thead > tr > th")
.Select(x => x.GetAttribute("title") ?? x.QuerySelector("a[title]")?.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("Seeder(s)", StringComparison.OrdinalIgnoreCase));
var leechersIndex = headerColumns.FindIndex(x => x.Equals("Leecher(s)", StringComparison.OrdinalIgnoreCase));
var seedersIndex = headerColumns.FindIndex(x => x.Equals("Seeders", StringComparison.OrdinalIgnoreCase));
var leechersIndex = headerColumns.FindIndex(x => x.Equals("Leechers", StringComparison.OrdinalIgnoreCase));
var rows = table.QuerySelectorAll("tbody > tr");
@@ -248,7 +252,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var qDescCol = row.Children[nameIndex];
var qLink = qDescCol.QuerySelector("a");
var title = qLink.QuerySelector("span.torrent-text").TextContent.Trim();
var title = qLink.QuerySelector("span.bw-torrent-name").TextContent.Trim();
var infoUrl = _settings.BaseUrl + qLink.GetAttribute("href")?.TrimStart('/');
var torrentId = ParseUtil.GetArgumentFromQueryString(infoUrl, "id");

View File

@@ -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以加密软件、恶意软件和广告而闻名我们建议切换到更好的客户端例如qBittorrent、Deluge或ruTorrent。",
"DownloadClientUTorrentProviderMessage": "uTorrent 曾经含有挖矿行为、恶意软件和广告,我们强烈建议你选择其他客户端。",
"IndexerId": "索引器",
"IndexerSettingsPasskey": "通行密钥",
"IndexerBeyondHDSettingsRefundOnly": "只读",
@@ -763,5 +763,45 @@
"IndexerBeyondHDSettingsRssKeyHelpText": "来自网站的API密钥(在我的安全 => API密钥)",
"IndexerHDBitsSettingsFreeleechOnlyHelpText": "只搜索免费发布",
"IndexerHDBitsSettingsOrigins": "原始",
"IndexerPassThePopcornSettingsApiUserHelpText": "这些设置位于 PassThePopcorn 安全设置中(编辑个人资料 > 安全Edit Profile > Security。"
"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} 中搜索有效,而不会同步到其他程序中。目前没有添加此功能的计划。"
}

View File

@@ -13,9 +13,9 @@
<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="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="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="System.Drawing.Common" Version="8.0.19" />
<PackageReference Include="System.Memory" Version="4.6.3" />
<PackageReference Include="System.ServiceModel.Syndication" Version="8.0.0" />

View File

@@ -51,7 +51,7 @@ namespace NzbDrone.Test.Common.AutoMoq
if (behavior != MockBehavior.Default && mock.Behavior == MockBehavior.Default)
{
throw new InvalidOperationException("Unable to change be behaviour of a an existing mock.");
throw new InvalidOperationException("Unable to change be behaviour of an existing mock.");
}
return mock;
@@ -139,7 +139,7 @@ namespace NzbDrone.Test.Common.AutoMoq
LoadPlatformLibrary();
AssemblyLoader.RegisterSQLiteResolver();
AssemblyLoader.RegisterNativeResolver(new[] { "System.Data.SQLite", "Prowlarr.Core" });
}
private Mock<T> TheRegisteredMockForThisType<T>(Type type)