+
+ }
+ confirmLabel={translate('Install')}
+ onConfirm={handleInstallLatestMajorVersionPress}
+ onCancel={handleCancelMajorVersionPress}
+ />
+
+
+ );
+}
+
+export default Updates;
diff --git a/frontend/src/System/Updates/UpdatesConnector.js b/frontend/src/System/Updates/UpdatesConnector.js
deleted file mode 100644
index 38873a990..000000000
--- a/frontend/src/System/Updates/UpdatesConnector.js
+++ /dev/null
@@ -1,101 +0,0 @@
-import PropTypes from 'prop-types';
-import React, { Component } from 'react';
-import { connect } from 'react-redux';
-import { createSelector } from 'reselect';
-import * as commandNames from 'Commands/commandNames';
-import { executeCommand } from 'Store/Actions/commandActions';
-import { fetchGeneralSettings } from 'Store/Actions/settingsActions';
-import { fetchUpdates } from 'Store/Actions/systemActions';
-import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
-import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
-import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
-import Updates from './Updates';
-
-function createMapStateToProps() {
- return createSelector(
- (state) => state.app.version,
- createSystemStatusSelector(),
- (state) => state.system.updates,
- (state) => state.settings.general,
- createUISettingsSelector(),
- createSystemStatusSelector(),
- createCommandExecutingSelector(commandNames.APPLICATION_UPDATE),
- (
- currentVersion,
- status,
- updates,
- generalSettings,
- uiSettings,
- systemStatus,
- isInstallingUpdate
- ) => {
- const {
- error: updatesError,
- items
- } = updates;
-
- const isFetching = updates.isFetching || generalSettings.isFetching;
- const isPopulated = updates.isPopulated && generalSettings.isPopulated;
-
- return {
- currentVersion,
- isFetching,
- isPopulated,
- updatesError,
- generalSettingsError: generalSettings.error,
- items,
- isInstallingUpdate,
- isDocker: systemStatus.isDocker,
- updateMechanism: generalSettings.item.updateMechanism,
- updateMechanismMessage: status.packageUpdateMechanismMessage,
- shortDateFormat: uiSettings.shortDateFormat,
- longDateFormat: uiSettings.longDateFormat,
- timeFormat: uiSettings.timeFormat
- };
- }
- );
-}
-
-const mapDispatchToProps = {
- dispatchFetchUpdates: fetchUpdates,
- dispatchFetchGeneralSettings: fetchGeneralSettings,
- dispatchExecuteCommand: executeCommand
-};
-
-class UpdatesConnector extends Component {
-
- //
- // Lifecycle
-
- componentDidMount() {
- this.props.dispatchFetchUpdates();
- this.props.dispatchFetchGeneralSettings();
- }
-
- //
- // Listeners
-
- onInstallLatestPress = () => {
- this.props.dispatchExecuteCommand({ name: commandNames.APPLICATION_UPDATE });
- };
-
- //
- // Render
-
- render() {
- return (
-
- );
- }
-}
-
-UpdatesConnector.propTypes = {
- dispatchFetchUpdates: PropTypes.func.isRequired,
- dispatchFetchGeneralSettings: PropTypes.func.isRequired,
- dispatchExecuteCommand: PropTypes.func.isRequired
-};
-
-export default connect(createMapStateToProps, mapDispatchToProps)(UpdatesConnector);
diff --git a/frontend/src/Utilities/String/translate.ts b/frontend/src/Utilities/String/translate.ts
index 1faa70ecc..72d3adf40 100644
--- a/frontend/src/Utilities/String/translate.ts
+++ b/frontend/src/Utilities/String/translate.ts
@@ -17,7 +17,7 @@ export async function fetchTranslations(): Promise {
translations = data.Strings;
resolve(true);
- } catch (error) {
+ } catch {
resolve(false);
}
});
diff --git a/frontend/src/Utilities/getUniqueElementId.js b/frontend/src/Utilities/getUniqueElementId.js
index dae5150b7..1b380851d 100644
--- a/frontend/src/Utilities/getUniqueElementId.js
+++ b/frontend/src/Utilities/getUniqueElementId.js
@@ -1,7 +1,9 @@
let i = 0;
-// returns a HTML 4.0 compliant element IDs (http://stackoverflow.com/a/79022)
-
+/**
+ * @deprecated Use React's useId() instead
+ * @returns An HTML 4.0 compliant element IDs (http://stackoverflow.com/a/79022)
+ */
export default function getUniqueElementId() {
return `id-${i++}`;
}
diff --git a/frontend/src/typings/History.ts b/frontend/src/typings/History.ts
index ff13c676a..3e50355dc 100644
--- a/frontend/src/typings/History.ts
+++ b/frontend/src/typings/History.ts
@@ -1,5 +1,12 @@
import ModelBase from 'App/ModelBase';
+export type HistoryQueryType =
+ | 'search'
+ | 'tvsearch'
+ | 'movie'
+ | 'book'
+ | 'music';
+
export interface HistoryData {
source: string;
host: string;
@@ -7,7 +14,7 @@ export interface HistoryData {
offset: number;
elapsedTime: number;
query: string;
- queryType: string;
+ queryType: HistoryQueryType;
}
interface History extends ModelBase {
diff --git a/frontend/src/typings/Settings/General.ts b/frontend/src/typings/Settings/General.ts
new file mode 100644
index 000000000..c867bed74
--- /dev/null
+++ b/frontend/src/typings/Settings/General.ts
@@ -0,0 +1,45 @@
+export type UpdateMechanism =
+ | 'builtIn'
+ | 'script'
+ | 'external'
+ | 'apt'
+ | 'docker';
+
+export default interface General {
+ bindAddress: string;
+ port: number;
+ sslPort: number;
+ enableSsl: boolean;
+ launchBrowser: boolean;
+ authenticationMethod: string;
+ authenticationRequired: string;
+ analyticsEnabled: boolean;
+ username: string;
+ password: string;
+ passwordConfirmation: string;
+ logLevel: string;
+ consoleLogLevel: string;
+ branch: string;
+ apiKey: string;
+ sslCertPath: string;
+ sslCertPassword: string;
+ urlBase: string;
+ instanceName: string;
+ applicationUrl: string;
+ updateAutomatically: boolean;
+ updateMechanism: UpdateMechanism;
+ updateScriptPath: string;
+ proxyEnabled: boolean;
+ proxyType: string;
+ proxyHostname: string;
+ proxyPort: number;
+ proxyUsername: string;
+ proxyPassword: string;
+ proxyBypassFilter: string;
+ proxyBypassLocalAddresses: boolean;
+ certificateValidation: string;
+ backupFolder: string;
+ backupInterval: number;
+ backupRetention: number;
+ id: number;
+}
diff --git a/frontend/src/typings/UiSettings.ts b/frontend/src/typings/Settings/UiSettings.ts
similarity index 79%
rename from frontend/src/typings/UiSettings.ts
rename to frontend/src/typings/Settings/UiSettings.ts
index 3c23a0356..656c4518b 100644
--- a/frontend/src/typings/UiSettings.ts
+++ b/frontend/src/typings/Settings/UiSettings.ts
@@ -1,4 +1,4 @@
-export interface UiSettings {
+export default interface UiSettings {
theme: 'auto' | 'dark' | 'light';
showRelativeDates: boolean;
shortDateFormat: string;
diff --git a/frontend/src/typings/SystemStatus.ts b/frontend/src/typings/SystemStatus.ts
index 668efbfbf..d5eab3ca3 100644
--- a/frontend/src/typings/SystemStatus.ts
+++ b/frontend/src/typings/SystemStatus.ts
@@ -22,6 +22,7 @@ interface SystemStatus {
osVersion: string;
packageAuthor: string;
packageUpdateMechanism: string;
+ packageUpdateMechanismMessage: string;
packageVersion: string;
runtimeName: string;
runtimeVersion: string;
diff --git a/package.json b/package.json
index d42afe1b6..25960d641 100644
--- a/package.json
+++ b/package.json
@@ -23,35 +23,34 @@
"defaults"
],
"dependencies": {
- "@fortawesome/fontawesome-free": "6.6.0",
- "@fortawesome/fontawesome-svg-core": "6.6.0",
- "@fortawesome/free-regular-svg-icons": "6.6.0",
- "@fortawesome/free-solid-svg-icons": "6.6.0",
+ "@fortawesome/fontawesome-free": "6.7.1",
+ "@fortawesome/fontawesome-svg-core": "6.7.1",
+ "@fortawesome/free-regular-svg-icons": "6.7.1",
+ "@fortawesome/free-solid-svg-icons": "6.7.1",
"@fortawesome/react-fontawesome": "0.2.2",
"@juggle/resize-observer": "3.4.0",
"@microsoft/signalr": "6.0.25",
- "@sentry/browser": "7.100.0",
- "@sentry/integrations": "7.100.0",
- "@types/node": "18.19.31",
+ "@sentry/browser": "7.119.1",
+ "@sentry/integrations": "7.119.1",
+ "@types/node": "20.16.11",
"@types/react": "18.2.79",
"@types/react-dom": "18.2.25",
- "chart.js": "4.4.3",
- "classnames": "2.3.2",
- "clipboard": "2.0.11",
+ "chart.js": "4.4.4",
+ "classnames": "2.5.1",
"connected-react-router": "6.9.3",
+ "copy-to-clipboard": "3.3.3",
"element-class": "0.2.2",
- "filesize": "10.0.7",
+ "filesize": "10.1.6",
"history": "4.10.1",
- "https-browserify": "1.0.0",
"jdu": "1.0.0",
"jquery": "3.7.1",
"lodash": "4.17.21",
"mobile-detect": "1.4.5",
- "moment": "2.29.4",
+ "moment": "2.30.1",
"mousetrap": "1.6.5",
"normalize.css": "8.0.1",
"prop-types": "15.8.1",
- "qs": "6.11.1",
+ "qs": "6.13.0",
"react": "17.0.2",
"react-addons-shallow-compare": "15.6.3",
"react-async-script": "1.2.0",
@@ -65,7 +64,6 @@
"react-dom": "17.0.2",
"react-focus-lock": "2.9.4",
"react-google-recaptcha": "2.1.0",
- "react-lazyload": "3.2.0",
"react-measure": "1.4.7",
"react-popper": "1.3.7",
"react-redux": "7.2.4",
@@ -75,55 +73,55 @@
"react-text-truncate": "0.19.0",
"react-use-measure": "2.1.1",
"react-virtualized": "9.21.1",
- "react-window": "1.8.8",
+ "react-window": "1.8.10",
"redux": "4.2.1",
"redux-actions": "2.6.5",
"redux-batched-actions": "0.5.0",
"redux-localstorage": "0.4.1",
"redux-thunk": "2.4.2",
- "reselect": "4.1.7",
+ "reselect": "4.1.8",
"stacktrace-js": "2.0.2",
- "typescript": "5.1.6"
+ "typescript": "5.7.2"
},
"devDependencies": {
- "@babel/core": "7.25.2",
- "@babel/eslint-parser": "7.25.1",
- "@babel/plugin-proposal-export-default-from": "7.24.7",
+ "@babel/core": "7.26.0",
+ "@babel/eslint-parser": "7.25.9",
+ "@babel/plugin-proposal-export-default-from": "7.25.9",
"@babel/plugin-syntax-dynamic-import": "7.8.3",
- "@babel/preset-env": "7.25.3",
- "@babel/preset-react": "7.24.7",
- "@babel/preset-typescript": "7.24.7",
- "@types/lodash": "4.14.194",
- "@types/react-document-title": "2.0.9",
+ "@babel/preset-env": "7.26.0",
+ "@babel/preset-react": "7.26.3",
+ "@babel/preset-typescript": "7.26.0",
+ "@types/lodash": "4.14.195",
+ "@types/react-document-title": "2.0.10",
"@types/react-router-dom": "5.3.3",
- "@types/react-text-truncate": "0.14.1",
- "@types/react-window": "1.8.5",
- "@types/webpack-livereload-plugin": "2.3.3",
- "@typescript-eslint/eslint-plugin": "6.21.0",
- "@typescript-eslint/parser": "6.21.0",
+ "@types/react-text-truncate": "0.19.0",
+ "@types/react-window": "1.8.8",
+ "@types/webpack-livereload-plugin": "2.3.6",
+ "@typescript-eslint/eslint-plugin": "8.18.1",
+ "@typescript-eslint/parser": "8.18.1",
"are-you-es5": "2.1.2",
"autoprefixer": "10.4.20",
- "babel-loader": "9.1.3",
+ "babel-loader": "9.2.1",
"babel-plugin-inline-classnames": "2.0.1",
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
- "core-js": "3.38.0",
+ "core-js": "3.39.0",
"css-loader": "6.7.3",
"css-modules-typescript-loader": "4.0.1",
- "eslint": "8.57.0",
+ "eslint": "8.57.1",
"eslint-config-prettier": "8.10.0",
"eslint-plugin-filenames": "1.3.2",
- "eslint-plugin-import": "2.29.1",
+ "eslint-plugin-import": "2.31.0",
"eslint-plugin-prettier": "4.2.1",
- "eslint-plugin-react": "7.34.1",
- "eslint-plugin-react-hooks": "4.6.0",
- "eslint-plugin-simple-import-sort": "12.1.0",
+ "eslint-plugin-react": "7.37.1",
+ "eslint-plugin-react-hooks": "4.6.2",
+ "eslint-plugin-simple-import-sort": "12.1.1",
"file-loader": "6.2.0",
"filemanager-webpack-plugin": "8.0.0",
"fork-ts-checker-webpack-plugin": "8.0.0",
- "html-webpack-plugin": "5.5.1",
+ "html-webpack-plugin": "5.6.0",
"loader-utils": "^3.2.1",
- "mini-css-extract-plugin": "2.7.5",
- "postcss": "8.4.41",
+ "mini-css-extract-plugin": "2.9.1",
+ "postcss": "8.4.47",
"postcss-color-function": "4.1.0",
"postcss-loader": "7.3.0",
"postcss-mixins": "9.0.4",
@@ -132,17 +130,15 @@
"postcss-url": "10.1.3",
"prettier": "2.8.8",
"require-nocache": "1.0.0",
- "rimraf": "4.4.1",
- "run-sequence": "2.2.1",
- "streamqueue": "1.1.2",
+ "rimraf": "6.0.1",
"style-loader": "3.3.2",
"stylelint": "15.6.1",
- "stylelint-order": "6.0.3",
- "terser-webpack-plugin": "5.3.9",
- "ts-loader": "9.4.2",
+ "stylelint-order": "6.0.4",
+ "terser-webpack-plugin": "5.3.10",
+ "ts-loader": "9.5.1",
"typescript-plugin-css-modules": "5.0.1",
"url-loader": "4.1.1",
- "webpack": "5.94.0",
+ "webpack": "5.95.0",
"webpack-cli": "5.1.4",
"webpack-livereload-plugin": "3.0.2"
}
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index e161b9952..ce3672c38 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -221,7 +221,7 @@
<_UsingDefaultRuntimeIdentifier>true
- osx-x64
+ osx-$(Architecture)
diff --git a/src/NzbDrone.Automation.Test/Prowlarr.Automation.Test.csproj b/src/NzbDrone.Automation.Test/Prowlarr.Automation.Test.csproj
index bb0b5fcc4..78c8b7d0f 100644
--- a/src/NzbDrone.Automation.Test/Prowlarr.Automation.Test.csproj
+++ b/src/NzbDrone.Automation.Test/Prowlarr.Automation.Test.csproj
@@ -4,7 +4,7 @@
-
+
diff --git a/src/NzbDrone.Common.Test/ExtensionTests/IPAddressExtensionsFixture.cs b/src/NzbDrone.Common.Test/ExtensionTests/IPAddressExtensionsFixture.cs
index ff5d7383e..0f7ad3004 100644
--- a/src/NzbDrone.Common.Test/ExtensionTests/IPAddressExtensionsFixture.cs
+++ b/src/NzbDrone.Common.Test/ExtensionTests/IPAddressExtensionsFixture.cs
@@ -21,9 +21,28 @@ namespace NzbDrone.Common.Test.ExtensionTests
[TestCase("1.2.3.4")]
[TestCase("172.55.0.1")]
[TestCase("192.55.0.1")]
+ [TestCase("100.64.0.1")]
+ [TestCase("100.127.255.254")]
public void should_return_false_for_public_ip_address(string ipAddress)
{
IPAddress.Parse(ipAddress).IsLocalAddress().Should().BeFalse();
}
+
+ [TestCase("100.64.0.1")]
+ [TestCase("100.127.255.254")]
+ [TestCase("100.100.100.100")]
+ public void should_return_true_for_cgnat_ip_address(string ipAddress)
+ {
+ IPAddress.Parse(ipAddress).IsCgnatIpAddress().Should().BeTrue();
+ }
+
+ [TestCase("1.2.3.4")]
+ [TestCase("192.168.5.1")]
+ [TestCase("100.63.255.255")]
+ [TestCase("100.128.0.0")]
+ public void should_return_false_for_non_cgnat_ip_address(string ipAddress)
+ {
+ IPAddress.Parse(ipAddress).IsCgnatIpAddress().Should().BeFalse();
+ }
}
}
diff --git a/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs b/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs
index 0c0a338e1..9e2b31d87 100644
--- a/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs
+++ b/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs
@@ -29,6 +29,8 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"https://beyond-hd.me/torrent/download/the-next-365-days-2022-2160p-nf-web-dl-dual-ddp-51-dovi-hdr-hevc-apex.225146.2b51db35e1912ffc138825a12b9933d2")]
[TestCase(@"https://anthelion.me/api.php?api_key=2b51db35e1910123321025a12b9933d2&o=json&t=movie&q=&tmdb=&imdb=&cat=&limit=100&offset=0")]
[TestCase(@"https://avistaz.to/api/v1/jackett/auth: username=mySecret&password=mySecret&pid=mySecret")]
+ [TestCase(@"https://www.sharewood.tv/api/2b51db35e1910123321025a12b9933d2/last-torrents")]
+ [TestCase(@"https://example.org/rss/torrents?rsskey=2b51db35e1910123321025a12b9933d2&search=")]
// Indexer and Download Client Responses
diff --git a/src/NzbDrone.Common.Test/PathExtensionFixture.cs b/src/NzbDrone.Common.Test/PathExtensionFixture.cs
index 3fe72c058..010bc3a02 100644
--- a/src/NzbDrone.Common.Test/PathExtensionFixture.cs
+++ b/src/NzbDrone.Common.Test/PathExtensionFixture.cs
@@ -133,11 +133,16 @@ namespace NzbDrone.Common.Test
[TestCase(@"C:\test\", @"C:\Test\mydir")]
[TestCase(@"C:\test", @"C:\Test\mydir\")]
- public void path_should_be_parent_on_windows_only(string parentPath, string childPath)
+ public void windows_path_should_be_parent(string parentPath, string childPath)
{
- var expectedResult = OsInfo.IsWindows;
+ parentPath.IsParentPath(childPath).Should().Be(true);
+ }
- parentPath.IsParentPath(childPath).Should().Be(expectedResult);
+ [TestCase("/test", "/test/mydir/")]
+ [TestCase("/test/", "/test/mydir")]
+ public void posix_path_should_be_parent(string parentPath, string childPath)
+ {
+ parentPath.IsParentPath(childPath).Should().Be(true);
}
[TestCase(@"C:\Test\mydir", @"C:\Test")]
@@ -145,20 +150,57 @@ namespace NzbDrone.Common.Test
[TestCase(@"C:\", null)]
[TestCase(@"\\server\share", null)]
[TestCase(@"\\server\share\test", @"\\server\share")]
- public void path_should_return_parent_windows(string path, string parentPath)
+ public void windows_path_should_return_parent(string path, string parentPath)
{
- WindowsOnly();
path.GetParentPath().Should().Be(parentPath);
}
[TestCase(@"/", null)]
[TestCase(@"/test", "/")]
- public void path_should_return_parent_mono(string path, string parentPath)
+ [TestCase(@"/test/tv", "/test")]
+ public void unix_path_should_return_parent(string path, string parentPath)
{
- PosixOnly();
path.GetParentPath().Should().Be(parentPath);
}
+ [TestCase(@"C:\Test\mydir", "Test")]
+ [TestCase(@"C:\Test\", @"C:\")]
+ [TestCase(@"C:\Test", @"C:\")]
+ [TestCase(@"C:\", null)]
+ [TestCase(@"\\server\share", null)]
+ [TestCase(@"\\server\share\test", @"\\server\share")]
+ public void path_should_return_parent_name_windows(string path, string parentPath)
+ {
+ path.GetParentName().Should().Be(parentPath);
+ }
+
+ [TestCase(@"/", null)]
+ [TestCase(@"/test", "/")]
+ [TestCase(@"/test/tv", "test")]
+ public void path_should_return_parent_name_mono(string path, string parentPath)
+ {
+ path.GetParentName().Should().Be(parentPath);
+ }
+
+ [TestCase(@"C:\Test\mydir", "mydir")]
+ [TestCase(@"C:\Test\", "Test")]
+ [TestCase(@"C:\Test", "Test")]
+ [TestCase(@"C:\", "C:\\")]
+ [TestCase(@"\\server\share", @"\\server\share")]
+ [TestCase(@"\\server\share\test", "test")]
+ public void path_should_return_directory_name_windows(string path, string parentPath)
+ {
+ path.GetDirectoryName().Should().Be(parentPath);
+ }
+
+ [TestCase(@"/", "/")]
+ [TestCase(@"/test", "test")]
+ [TestCase(@"/test/tv", "tv")]
+ public void path_should_return_directory_name_mono(string path, string parentPath)
+ {
+ path.GetDirectoryName().Should().Be(parentPath);
+ }
+
[Test]
public void path_should_return_parent_for_oversized_path()
{
@@ -166,7 +208,7 @@ namespace NzbDrone.Common.Test
// This test will fail on Windows if long path support is not enabled: https://www.howtogeek.com/266621/how-to-make-windows-10-accept-file-paths-over-260-characters/
// It will also fail if the app isn't configured to use long path (such as resharper): https://blogs.msdn.microsoft.com/jeremykuhne/2016/07/30/net-4-6-2-and-long-paths-on-windows-10/
- var path = @"C:\media\2e168617-f2ae-43fb-b88c-3663af1c8eea\downloads\sabnzbd\nzbdrone\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories".AsOsAgnostic();
+ var path = @"C:\media\2e168617-f2ae-43fb-b88c-3663af1c8eea\downloads\sabnzbd\nzbdrone\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories".AsOsAgnostic();
var parentPath = @"C:\media\2e168617-f2ae-43fb-b88c-3663af1c8eea\downloads\sabnzbd\nzbdrone\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing".AsOsAgnostic();
path.GetParentPath().Should().Be(parentPath);
@@ -350,5 +392,46 @@ namespace NzbDrone.Common.Test
PosixOnly();
path.AsOsAgnostic().IsPathValid(PathValidationType.CurrentOs).Should().BeFalse();
}
+
+ [TestCase(@"C:\", @"C:\")]
+ [TestCase(@"C:\\", @"C:\")]
+ [TestCase(@"C:\Test", @"C:\Test")]
+ [TestCase(@"C:\Test\", @"C:\Test")]
+ [TestCase(@"\\server\share", @"\\server\share")]
+ [TestCase(@"\\server\share\", @"\\server\share")]
+ public void windows_path_should_return_clean_path(string path, string cleanPath)
+ {
+ path.GetCleanPath().Should().Be(cleanPath);
+ }
+
+ [TestCase("/", "/")]
+ [TestCase("//", "/")]
+ [TestCase("/test", "/test")]
+ [TestCase("/test/", "/test")]
+ [TestCase("/test//", "/test")]
+ public void unix_path_should_return_clean_path(string path, string cleanPath)
+ {
+ path.GetCleanPath().Should().Be(cleanPath);
+ }
+
+ [TestCase(@"C:\Test\", @"C:\Test\Series Title", "Series Title")]
+ [TestCase(@"C:\Test\", @"C:\Test\Collection\Series Title", @"Collection\Series Title")]
+ [TestCase(@"C:\Test\mydir\", @"C:\Test\mydir\Collection\Series Title", @"Collection\Series Title")]
+ [TestCase(@"\\server\share", @"\\server\share\Series Title", "Series Title")]
+ [TestCase(@"\\server\share\mydir\", @"\\server\share\mydir\/Collection\Series Title", @"Collection\Series Title")]
+ public void windows_path_should_return_relative_path(string parentPath, string childPath, string relativePath)
+ {
+ parentPath.GetRelativePath(childPath).Should().Be(relativePath);
+ }
+
+ [TestCase(@"/test", "/test/Series Title", "Series Title")]
+ [TestCase(@"/test/", "/test/Collection/Series Title", "Collection/Series Title")]
+ [TestCase(@"/test/mydir", "/test/mydir/Series Title", "Series Title")]
+ [TestCase(@"/test/mydir/", "/test/mydir/Collection/Series Title", "Collection/Series Title")]
+ [TestCase(@"/test/mydir/", @"/test/mydir/\Collection/Series Title", "Collection/Series Title")]
+ public void unix_path_should_return_relative_path(string parentPath, string childPath, string relativePath)
+ {
+ parentPath.GetRelativePath(childPath).Should().Be(relativePath);
+ }
}
}
diff --git a/src/NzbDrone.Common/ArchiveService.cs b/src/NzbDrone.Common/ArchiveService.cs
index 800d240ab..d420bbbc0 100644
--- a/src/NzbDrone.Common/ArchiveService.cs
+++ b/src/NzbDrone.Common/ArchiveService.cs
@@ -42,17 +42,18 @@ namespace NzbDrone.Common
public void CreateZip(string path, IEnumerable files)
{
- using (var zipFile = ZipFile.Create(path))
+ _logger.Debug("Creating archive {0}", path);
+
+ using var zipFile = ZipFile.Create(path);
+
+ zipFile.BeginUpdate();
+
+ foreach (var file in files)
{
- zipFile.BeginUpdate();
-
- foreach (var file in files)
- {
- zipFile.Add(file, Path.GetFileName(file));
- }
-
- zipFile.CommitUpdate();
+ zipFile.Add(file, Path.GetFileName(file));
}
+
+ zipFile.CommitUpdate();
}
private void ExtractZip(string compressedFile, string destination)
diff --git a/src/NzbDrone.Common/Disk/DiskProviderBase.cs b/src/NzbDrone.Common/Disk/DiskProviderBase.cs
index f3333d74e..621d4b258 100644
--- a/src/NzbDrone.Common/Disk/DiskProviderBase.cs
+++ b/src/NzbDrone.Common/Disk/DiskProviderBase.cs
@@ -189,6 +189,25 @@ namespace NzbDrone.Common.Disk
}
var fi = new FileInfo(path);
+
+ try
+ {
+ // If the file is a symlink, resolve the target path and get the size of the target file.
+ if (fi.Attributes.HasFlag(FileAttributes.ReparsePoint))
+ {
+ var targetPath = fi.ResolveLinkTarget(true)?.FullName;
+
+ if (targetPath != null)
+ {
+ fi = new FileInfo(targetPath);
+ }
+ }
+ }
+ catch (IOException ex)
+ {
+ Logger.Trace(ex, "Unable to resolve symlink target for {0}", path);
+ }
+
return fi.Length;
}
diff --git a/src/NzbDrone.Common/Disk/OsPath.cs b/src/NzbDrone.Common/Disk/OsPath.cs
index f6f01fccf..30661d747 100644
--- a/src/NzbDrone.Common/Disk/OsPath.cs
+++ b/src/NzbDrone.Common/Disk/OsPath.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Text.RegularExpressions;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Disk
@@ -9,6 +10,8 @@ namespace NzbDrone.Common.Disk
private readonly string _path;
private readonly OsPathKind _kind;
+ private static readonly Regex UncPathRegex = new Regex(@"(?^\\\\(?:\?\\UNC\\)?[^\\]+\\[^\\]+)(?:\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+
public OsPath(string path)
{
if (path == null)
@@ -96,6 +99,29 @@ namespace NzbDrone.Common.Disk
return path;
}
+ private static string TrimTrailingSlash(string path, OsPathKind kind)
+ {
+ switch (kind)
+ {
+ case OsPathKind.Windows when !path.EndsWith(":\\"):
+ while (!path.EndsWith(":\\") && path.EndsWith('\\'))
+ {
+ path = path[..^1];
+ }
+
+ return path;
+ case OsPathKind.Unix when path != "/":
+ while (path != "/" && path.EndsWith('/'))
+ {
+ path = path[..^1];
+ }
+
+ return path;
+ }
+
+ return path;
+ }
+
public OsPathKind Kind => _kind;
public bool IsWindowsPath => _kind == OsPathKind.Windows;
@@ -130,7 +156,19 @@ namespace NzbDrone.Common.Disk
if (index == -1)
{
- return new OsPath(null);
+ return Null;
+ }
+
+ var rootLength = GetRootLength();
+
+ if (rootLength == _path.Length)
+ {
+ return Null;
+ }
+
+ if (rootLength > index + 1)
+ {
+ return new OsPath(_path.Substring(0, rootLength));
}
return new OsPath(_path.Substring(0, index), _kind).AsDirectory();
@@ -139,6 +177,8 @@ namespace NzbDrone.Common.Disk
public string FullPath => _path;
+ public string PathWithoutTrailingSlash => TrimTrailingSlash(_path, _kind);
+
public string FileName
{
get
@@ -161,6 +201,29 @@ namespace NzbDrone.Common.Disk
}
}
+ public string Name
+ {
+ // Meant to behave similar to DirectoryInfo.Name
+ get
+ {
+ var index = GetFileNameIndex();
+
+ if (index == -1)
+ {
+ return PathWithoutTrailingSlash;
+ }
+
+ var rootLength = GetRootLength();
+
+ if (rootLength > index + 1)
+ {
+ return _path.Substring(0, rootLength);
+ }
+
+ return TrimTrailingSlash(_path.Substring(index).TrimStart('/', '\\'), _kind);
+ }
+ }
+
public bool IsValid => _path.IsPathValid(PathValidationType.CurrentOs);
private int GetFileNameIndex()
@@ -190,11 +253,50 @@ namespace NzbDrone.Common.Disk
return index;
}
+ private int GetRootLength()
+ {
+ if (!IsRooted)
+ {
+ return 0;
+ }
+
+ if (_kind == OsPathKind.Unix)
+ {
+ return 1;
+ }
+
+ if (_kind == OsPathKind.Windows)
+ {
+ if (HasWindowsDriveLetter(_path))
+ {
+ return 3;
+ }
+
+ var uncMatch = UncPathRegex.Match(_path);
+
+ // \\?\UNC\server\share\ or \\server\share
+ if (uncMatch.Success)
+ {
+ return uncMatch.Groups["unc"].Length;
+ }
+
+ // \\?\C:\
+ if (_path.StartsWith(@"\\?\"))
+ {
+ return 7;
+ }
+ }
+
+ return 0;
+ }
+
private string[] GetFragments()
{
return _path.Split(new char[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries);
}
+ public static OsPath Null => new (null);
+
public override string ToString()
{
return _path;
@@ -267,6 +369,11 @@ namespace NzbDrone.Common.Disk
}
public bool Equals(OsPath other)
+ {
+ return Equals(other, false);
+ }
+
+ public bool Equals(OsPath other, bool ignoreTrailingSlash)
{
if (ReferenceEquals(other, null))
{
@@ -278,8 +385,8 @@ namespace NzbDrone.Common.Disk
return true;
}
- var left = _path;
- var right = other._path;
+ var left = ignoreTrailingSlash ? PathWithoutTrailingSlash : _path;
+ var right = ignoreTrailingSlash ? other.PathWithoutTrailingSlash : other._path;
if (Kind == OsPathKind.Windows || other.Kind == OsPathKind.Windows)
{
diff --git a/src/NzbDrone.Common/EnvironmentInfo/OsInfo.cs b/src/NzbDrone.Common/EnvironmentInfo/OsInfo.cs
index b9a206e4e..aece27859 100644
--- a/src/NzbDrone.Common/EnvironmentInfo/OsInfo.cs
+++ b/src/NzbDrone.Common/EnvironmentInfo/OsInfo.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.IO;
using System.Linq;
using NLog;
@@ -25,22 +24,25 @@ namespace NzbDrone.Common.EnvironmentInfo
static OsInfo()
{
- var platform = Environment.OSVersion.Platform;
-
- switch (platform)
+ if (OperatingSystem.IsWindows())
{
- case PlatformID.Win32NT:
- {
- Os = Os.Windows;
- break;
- }
-
- case PlatformID.MacOSX:
- case PlatformID.Unix:
- {
- Os = GetPosixFlavour();
- break;
- }
+ Os = Os.Windows;
+ }
+ else if (OperatingSystem.IsMacOS())
+ {
+ Os = Os.Osx;
+ }
+ else if (OperatingSystem.IsFreeBSD())
+ {
+ Os = Os.Bsd;
+ }
+ else
+ {
+#if ISMUSL
+ Os = Os.LinuxMusl;
+#else
+ Os = Os.Linux;
+#endif
}
}
@@ -84,59 +86,6 @@ namespace NzbDrone.Common.EnvironmentInfo
IsDocker = true;
}
}
-
- private static Os GetPosixFlavour()
- {
- var output = RunAndCapture("uname", "-s");
-
- if (output.StartsWith("Darwin"))
- {
- return Os.Osx;
- }
- else if (output.Contains("BSD"))
- {
- return Os.Bsd;
- }
- else
- {
-#if ISMUSL
- return Os.LinuxMusl;
-#else
- return Os.Linux;
-#endif
- }
- }
-
- private static string RunAndCapture(string filename, string args)
- {
- var processStartInfo = new ProcessStartInfo
- {
- FileName = filename,
- Arguments = args,
- UseShellExecute = false,
- CreateNoWindow = true,
- RedirectStandardOutput = true
- };
-
- var output = string.Empty;
-
- try
- {
- using (var p = Process.Start(processStartInfo))
- {
- // To avoid deadlocks, always read the output stream first and then wait.
- output = p.StandardOutput.ReadToEnd();
-
- p.WaitForExit(1000);
- }
- }
- catch (Exception)
- {
- output = string.Empty;
- }
-
- return output;
- }
}
public interface IOsInfo
diff --git a/src/NzbDrone.Common/Extensions/IpAddressExtensions.cs b/src/NzbDrone.Common/Extensions/IpAddressExtensions.cs
index 7feb431c4..cbc1f5f83 100644
--- a/src/NzbDrone.Common/Extensions/IpAddressExtensions.cs
+++ b/src/NzbDrone.Common/Extensions/IpAddressExtensions.cs
@@ -39,18 +39,24 @@ namespace NzbDrone.Common.Extensions
private static bool IsLocalIPv4(byte[] ipv4Bytes)
{
// Link local (no IP assigned by DHCP): 169.254.0.0 to 169.254.255.255 (169.254.0.0/16)
- bool IsLinkLocal() => ipv4Bytes[0] == 169 && ipv4Bytes[1] == 254;
+ var isLinkLocal = ipv4Bytes[0] == 169 && ipv4Bytes[1] == 254;
// Class A private range: 10.0.0.0 – 10.255.255.255 (10.0.0.0/8)
- bool IsClassA() => ipv4Bytes[0] == 10;
+ var isClassA = ipv4Bytes[0] == 10;
// Class B private range: 172.16.0.0 – 172.31.255.255 (172.16.0.0/12)
- bool IsClassB() => ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31;
+ var isClassB = ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31;
// Class C private range: 192.168.0.0 – 192.168.255.255 (192.168.0.0/16)
- bool IsClassC() => ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168;
+ var isClassC = ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168;
- return IsLinkLocal() || IsClassA() || IsClassC() || IsClassB();
+ return isLinkLocal || isClassA || isClassC || isClassB;
+ }
+
+ public static bool IsCgnatIpAddress(this IPAddress ipAddress)
+ {
+ var bytes = ipAddress.GetAddressBytes();
+ return bytes.Length == 4 && bytes[0] == 100 && bytes[1] >= 64 && bytes[1] <= 127;
}
}
}
diff --git a/src/NzbDrone.Common/Extensions/PathExtensions.cs b/src/NzbDrone.Common/Extensions/PathExtensions.cs
index 0a98cef4c..30a467f21 100644
--- a/src/NzbDrone.Common/Extensions/PathExtensions.cs
+++ b/src/NzbDrone.Common/Extensions/PathExtensions.cs
@@ -25,8 +25,6 @@ namespace NzbDrone.Common.Extensions
private static readonly string UPDATE_CLIENT_FOLDER_NAME = "Prowlarr.Update" + Path.DirectorySeparatorChar;
private static readonly string UPDATE_LOG_FOLDER_NAME = "UpdateLogs" + Path.DirectorySeparatorChar;
- private static readonly Regex PARENT_PATH_END_SLASH_REGEX = new Regex(@"(?[^&=""]+?)(?=[ ""&=]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
+ new (@"(?<=[?&: ;])(apikey|api_key|(?:(?:access|api)[-_]?)?token|pass(?:key|wd)?|auth|authkey|rsskey|user|u?id|api|[a-z_]*apikey|account|pid|pwd)=(?[^&=""]+?)(?=[ ""&=]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"(?<=[?& ;])[^=]*?(_?(?[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"rss(24h)?\.torrentleech\.org/(?!rss)(?[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"torrentleech\.org/rss/download/[0-9]+/(?[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
@@ -21,6 +21,7 @@ namespace NzbDrone.Common.Instrumentation
new (@"(?<=authkey = "")(?[^&=]+?)(?="")", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"(?<=beyond-hd\.[a-z]+/api/torrents/)(?[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"(?<=beyond-hd\.[a-z]+/torrent/download/[\w\d-]+[.]\d+[.])(?[a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
+ new (@"(?:sharewood)\.[a-z]{2,3}/api/(?[a-z0-9]{16,})/", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// UNIT3D
new (@"(?<=[a-z0-9-]+\.[a-z]+/torrent/download/\d+\.)(?[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
diff --git a/src/NzbDrone.Common/Instrumentation/CleansingClefLogLayout.cs b/src/NzbDrone.Common/Instrumentation/CleansingClefLogLayout.cs
new file mode 100644
index 000000000..f110b96ac
--- /dev/null
+++ b/src/NzbDrone.Common/Instrumentation/CleansingClefLogLayout.cs
@@ -0,0 +1,21 @@
+using System.Text;
+using NLog;
+using NLog.Layouts.ClefJsonLayout;
+using NzbDrone.Common.EnvironmentInfo;
+
+namespace NzbDrone.Common.Instrumentation;
+
+public class CleansingClefLogLayout : CompactJsonLayout
+{
+ protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
+ {
+ base.RenderFormattedMessage(logEvent, target);
+
+ if (RuntimeInfo.IsProduction)
+ {
+ var result = CleanseLogMessage.Cleanse(target.ToString());
+ target.Clear();
+ target.Append(result);
+ }
+ }
+}
diff --git a/src/NzbDrone.Common/Instrumentation/CleansingConsoleLogLayout.cs b/src/NzbDrone.Common/Instrumentation/CleansingConsoleLogLayout.cs
new file mode 100644
index 000000000..f894a4df5
--- /dev/null
+++ b/src/NzbDrone.Common/Instrumentation/CleansingConsoleLogLayout.cs
@@ -0,0 +1,26 @@
+using System.Text;
+using NLog;
+using NLog.Layouts;
+using NzbDrone.Common.EnvironmentInfo;
+
+namespace NzbDrone.Common.Instrumentation;
+
+public class CleansingConsoleLogLayout : SimpleLayout
+{
+ public CleansingConsoleLogLayout(string format)
+ : base(format)
+ {
+ }
+
+ protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
+ {
+ base.RenderFormattedMessage(logEvent, target);
+
+ if (RuntimeInfo.IsProduction)
+ {
+ var result = CleanseLogMessage.Cleanse(target.ToString());
+ target.Clear();
+ target.Append(result);
+ }
+ }
+}
diff --git a/src/NzbDrone.Common/Instrumentation/NzbDroneFileTarget.cs b/src/NzbDrone.Common/Instrumentation/CleansingFileTarget.cs
similarity index 87%
rename from src/NzbDrone.Common/Instrumentation/NzbDroneFileTarget.cs
rename to src/NzbDrone.Common/Instrumentation/CleansingFileTarget.cs
index 84658cf74..f74d1fca4 100644
--- a/src/NzbDrone.Common/Instrumentation/NzbDroneFileTarget.cs
+++ b/src/NzbDrone.Common/Instrumentation/CleansingFileTarget.cs
@@ -4,7 +4,7 @@ using NLog.Targets;
namespace NzbDrone.Common.Instrumentation
{
- public class NzbDroneFileTarget : FileTarget
+ public class CleansingFileTarget : FileTarget
{
protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
{
diff --git a/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs b/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs
index 80793e812..d9fdd5b25 100644
--- a/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs
+++ b/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs
@@ -3,7 +3,6 @@ using System.Diagnostics;
using System.IO;
using NLog;
using NLog.Config;
-using NLog.Layouts.ClefJsonLayout;
using NLog.Targets;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
@@ -13,9 +12,11 @@ namespace NzbDrone.Common.Instrumentation
{
public static class NzbDroneLogger
{
- private const string FILE_LOG_LAYOUT = @"${date:format=yyyy-MM-dd HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
- public const string ConsoleLogLayout = "[${level}] ${logger}: ${message} ${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
- public static CompactJsonLayout ClefLogLayout = new CompactJsonLayout();
+ private const string FileLogLayout = @"${date:format=yyyy-MM-dd HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
+ private const string ConsoleFormat = "[${level}] ${logger}: ${message} ${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
+
+ private static readonly CleansingConsoleLogLayout CleansingConsoleLayout = new (ConsoleFormat);
+ private static readonly CleansingClefLogLayout ClefLogLayout = new ();
private static bool _isConfigured;
@@ -119,11 +120,7 @@ namespace NzbDrone.Common.Instrumentation
? formatEnumValue
: ConsoleLogFormat.Standard;
- coloredConsoleTarget.Layout = logFormat switch
- {
- ConsoleLogFormat.Clef => ClefLogLayout,
- _ => ConsoleLogLayout
- };
+ ConfigureConsoleLayout(coloredConsoleTarget, logFormat);
var loggingRule = new LoggingRule("*", level, coloredConsoleTarget);
@@ -140,7 +137,7 @@ namespace NzbDrone.Common.Instrumentation
private static void RegisterAppFile(IAppFolderInfo appFolderInfo, string name, string fileName, int maxArchiveFiles, LogLevel minLogLevel)
{
- var fileTarget = new NzbDroneFileTarget();
+ var fileTarget = new CleansingFileTarget();
fileTarget.Name = name;
fileTarget.FileName = Path.Combine(appFolderInfo.GetLogFolder(), fileName);
@@ -153,7 +150,7 @@ namespace NzbDrone.Common.Instrumentation
fileTarget.MaxArchiveFiles = maxArchiveFiles;
fileTarget.EnableFileDelete = true;
fileTarget.ArchiveNumbering = ArchiveNumberingMode.Rolling;
- fileTarget.Layout = FILE_LOG_LAYOUT;
+ fileTarget.Layout = FileLogLayout;
var loggingRule = new LoggingRule("*", minLogLevel, fileTarget);
@@ -172,7 +169,7 @@ namespace NzbDrone.Common.Instrumentation
fileTarget.ConcurrentWrites = false;
fileTarget.ConcurrentWriteAttemptDelay = 50;
fileTarget.ConcurrentWriteAttempts = 100;
- fileTarget.Layout = FILE_LOG_LAYOUT;
+ fileTarget.Layout = FileLogLayout;
var loggingRule = new LoggingRule("*", LogLevel.Trace, fileTarget);
@@ -217,6 +214,15 @@ namespace NzbDrone.Common.Instrumentation
{
return GetLogger(obj.GetType());
}
+
+ public static void ConfigureConsoleLayout(ColoredConsoleTarget target, ConsoleLogFormat format)
+ {
+ target.Layout = format switch
+ {
+ ConsoleLogFormat.Clef => NzbDroneLogger.ClefLogLayout,
+ _ => NzbDroneLogger.CleansingConsoleLayout
+ };
+ }
}
public enum ConsoleLogFormat
diff --git a/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs b/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs
index 843f904a6..3a4737f74 100644
--- a/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs
+++ b/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs
@@ -119,7 +119,7 @@ namespace NzbDrone.Common.Instrumentation.Sentry
o.Environment = BuildInfo.Branch;
// Crash free run statistics (sends a ping for healthy and for crashes sessions)
- o.AutoSessionTracking = true;
+ o.AutoSessionTracking = false;
// Caches files in the event device is offline
// Sentry creates a 'sentry' sub directory, no need to concat here
@@ -148,7 +148,7 @@ namespace NzbDrone.Common.Instrumentation.Sentry
_debounce = new SentryDebounce();
// initialize to true and reconfigure later
- // Otherwise it will default to false and any errors occuring
+ // Otherwise it will default to false and any errors occurring
// before config file gets read will not be filtered
FilterEvents = true;
SentryEnabled = true;
@@ -207,9 +207,7 @@ namespace NzbDrone.Common.Instrumentation.Sentry
private void OnError(Exception ex)
{
- var webException = ex as WebException;
-
- if (webException != null)
+ if (ex is WebException webException)
{
var response = webException.Response as HttpWebResponse;
var statusCode = response?.StatusCode;
diff --git a/src/NzbDrone.Common/Options/AuthOptions.cs b/src/NzbDrone.Common/Options/AuthOptions.cs
index 2b63308d3..64330b68b 100644
--- a/src/NzbDrone.Common/Options/AuthOptions.cs
+++ b/src/NzbDrone.Common/Options/AuthOptions.cs
@@ -6,4 +6,5 @@ public class AuthOptions
public bool? Enabled { get; set; }
public string Method { get; set; }
public string Required { get; set; }
+ public bool? TrustCgnatIpAddresses { get; set; }
}
diff --git a/src/NzbDrone.Common/Processes/ProcessProvider.cs b/src/NzbDrone.Common/Processes/ProcessProvider.cs
index 5db0565e0..c68207a09 100644
--- a/src/NzbDrone.Common/Processes/ProcessProvider.cs
+++ b/src/NzbDrone.Common/Processes/ProcessProvider.cs
@@ -6,6 +6,7 @@ using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
+using System.Text;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Model;
@@ -117,7 +118,9 @@ namespace NzbDrone.Common.Processes
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
- RedirectStandardInput = true
+ RedirectStandardInput = true,
+ StandardOutputEncoding = Encoding.UTF8,
+ StandardErrorEncoding = Encoding.UTF8
};
if (environmentVariables != null)
@@ -313,7 +316,7 @@ namespace NzbDrone.Common.Processes
processInfo = new ProcessInfo();
processInfo.Id = process.Id;
processInfo.Name = process.ProcessName;
- processInfo.StartPath = process.MainModule.FileName;
+ processInfo.StartPath = process.MainModule?.FileName;
if (process.Id != GetCurrentProcessId() && process.HasExited)
{
diff --git a/src/NzbDrone.Common/Prowlarr.Common.csproj b/src/NzbDrone.Common/Prowlarr.Common.csproj
index f3d735cfd..106890399 100644
--- a/src/NzbDrone.Common/Prowlarr.Common.csproj
+++ b/src/NzbDrone.Common/Prowlarr.Common.csproj
@@ -5,20 +5,21 @@
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
+
+
-
+
diff --git a/src/NzbDrone.Core.Test/Datastore/Converters/TimeSpanConverterFixture.cs b/src/NzbDrone.Core.Test/Datastore/Converters/TimeSpanConverterFixture.cs
new file mode 100644
index 000000000..79d0adaee
--- /dev/null
+++ b/src/NzbDrone.Core.Test/Datastore/Converters/TimeSpanConverterFixture.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Data.SQLite;
+using FluentAssertions;
+using NUnit.Framework;
+using NzbDrone.Core.Datastore.Converters;
+using NzbDrone.Core.Test.Framework;
+
+namespace NzbDrone.Core.Test.Datastore.Converters;
+
+[TestFixture]
+public class TimeSpanConverterFixture : CoreTest
+{
+ private SQLiteParameter _param;
+
+ [SetUp]
+ public void Setup()
+ {
+ _param = new SQLiteParameter();
+ }
+
+ [Test]
+ public void should_return_string_when_saving_timespan_to_db()
+ {
+ var span = TimeSpan.FromMilliseconds(10);
+
+ Subject.SetValue(_param, span);
+ _param.Value.Should().Be(span.ToString());
+ }
+
+ [Test]
+ public void should_return_timespan_when_getting_string_from_db()
+ {
+ var span = TimeSpan.FromMilliseconds(10);
+
+ Subject.Parse(span.ToString()).Should().Be(span);
+ }
+
+ [Test]
+ public void should_return_zero_timespan_for_db_null_value_when_getting_from_db()
+ {
+ Subject.Parse(null).Should().Be(TimeSpan.Zero);
+ }
+}
diff --git a/src/NzbDrone.Core.Test/Datastore/DatabaseVersionParserFixture.cs b/src/NzbDrone.Core.Test/Datastore/DatabaseVersionParserFixture.cs
new file mode 100644
index 000000000..05bf04fea
--- /dev/null
+++ b/src/NzbDrone.Core.Test/Datastore/DatabaseVersionParserFixture.cs
@@ -0,0 +1,38 @@
+using FluentAssertions;
+using NUnit.Framework;
+using NzbDrone.Core.Datastore;
+
+namespace NzbDrone.Core.Test.Datastore;
+
+[TestFixture]
+public class DatabaseVersionParserFixture
+{
+ [TestCase("3.44.2", 3, 44, 2)]
+ public void should_parse_sqlite_database_version(string serverVersion, int majorVersion, int minorVersion, int buildVersion)
+ {
+ var version = DatabaseVersionParser.ParseServerVersion(serverVersion);
+
+ version.Should().NotBeNull();
+ version.Major.Should().Be(majorVersion);
+ version.Minor.Should().Be(minorVersion);
+ version.Build.Should().Be(buildVersion);
+ }
+
+ [TestCase("14.8 (Debian 14.8-1.pgdg110+1)", 14, 8, null)]
+ [TestCase("16.3 (Debian 16.3-1.pgdg110+1)", 16, 3, null)]
+ [TestCase("16.3 - Percona Distribution", 16, 3, null)]
+ [TestCase("17.0 - Percona Server", 17, 0, null)]
+ public void should_parse_postgres_database_version(string serverVersion, int majorVersion, int minorVersion, int? buildVersion)
+ {
+ var version = DatabaseVersionParser.ParseServerVersion(serverVersion);
+
+ version.Should().NotBeNull();
+ version.Major.Should().Be(majorVersion);
+ version.Minor.Should().Be(minorVersion);
+
+ if (buildVersion.HasValue)
+ {
+ version.Build.Should().Be(buildVersion.Value);
+ }
+ }
+}
diff --git a/src/NzbDrone.Core.Test/IndexerTests/AnimeBytesTests/AnimeBytesFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/AnimeBytesTests/AnimeBytesFixture.cs
index a2453592f..ae7eaa762 100644
--- a/src/NzbDrone.Core.Test/IndexerTests/AnimeBytesTests/AnimeBytesFixture.cs
+++ b/src/NzbDrone.Core.Test/IndexerTests/AnimeBytesTests/AnimeBytesFixture.cs
@@ -122,7 +122,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AnimeBytesTests
var fifthTorrentInfo = releases.ElementAt(28) as TorrentInfo;
- fifthTorrentInfo.Title.Should().Be("[-ZR-] Dr. STONE: STONE WARS S02 [Web][MKV][h264][1080p][AAC 2.0][Dual Audio][Softsubs (-ZR-)]");
+ fifthTorrentInfo.Title.Should().Be("[-ZR-] Dr. STONE: STONE WARS 2021 S02 [Web][MKV][h264][1080p][AAC 2.0][Dual Audio][Softsubs (-ZR-)]");
fifthTorrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
fifthTorrentInfo.DownloadUrl.Should().Be("https://animebytes.tv/torrent/944509/download/somepass");
fifthTorrentInfo.InfoUrl.Should().Be("https://animebytes.tv/torrent/944509/group");
diff --git a/src/NzbDrone.Core.Test/IndexerTests/HDBitsTests/HDBitsFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/HDBitsTests/HDBitsFixture.cs
index 39d628d79..06326d162 100644
--- a/src/NzbDrone.Core.Test/IndexerTests/HDBitsTests/HDBitsFixture.cs
+++ b/src/NzbDrone.Core.Test/IndexerTests/HDBitsTests/HDBitsFixture.cs
@@ -26,15 +26,15 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
[SetUp]
public void Setup()
{
- Subject.Definition = new IndexerDefinition()
+ Subject.Definition = new IndexerDefinition
{
Name = "HdBits",
- Settings = new HDBitsSettings() { ApiKey = "fakekey" }
+ Settings = new HDBitsSettings { ApiKey = "fakekey" }
};
_movieSearchCriteria = new MovieSearchCriteria
{
- Categories = new int[] { 2000, 2010 },
+ Categories = new[] { 2000, 2010 },
ImdbId = "0076759"
};
}
@@ -52,7 +52,7 @@ namespace NzbDrone.Core.Test.IndexerTests.HDBitsTests
var torrents = (await Subject.Fetch(_movieSearchCriteria)).Releases;
torrents.Should().HaveCount(2);
- torrents.First().Should().BeOfType();
+ torrents.First().Should().BeOfType();
var first = torrents.First() as TorrentInfo;
diff --git a/src/NzbDrone.Core.Test/IndexerTests/RedactedTests/RedactedFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/RedactedTests/RedactedFixture.cs
index 61c664072..d7eb35cd1 100644
--- a/src/NzbDrone.Core.Test/IndexerTests/RedactedTests/RedactedFixture.cs
+++ b/src/NzbDrone.Core.Test/IndexerTests/RedactedTests/RedactedFixture.cs
@@ -46,8 +46,8 @@ namespace NzbDrone.Core.Test.IndexerTests.RedactedTests
torrentInfo.Title.Should().Be("Red Hot Chili Peppers - Californication (1999) [Album] [US / Reissue 2020] [FLAC 24bit Lossless / Vinyl]");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
- torrentInfo.DownloadUrl.Should().Be("https://redacted.ch/ajax.php?action=download&id=3892313");
- torrentInfo.InfoUrl.Should().Be("https://redacted.ch/torrents.php?id=16720&torrentid=3892313");
+ torrentInfo.DownloadUrl.Should().Be("https://redacted.sh/ajax.php?action=download&id=3892313");
+ torrentInfo.InfoUrl.Should().Be("https://redacted.sh/torrents.php?id=16720&torrentid=3892313");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2022-12-17 08:02:35"));
diff --git a/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs b/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs
index ce2c446e6..5f2aa2662 100644
--- a/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs
@@ -35,18 +35,18 @@ namespace NzbDrone.Core.Test.UpdateTests
{
_updatePackage = new UpdatePackage
{
- FileName = "NzbDrone.develop.2.0.0.0.tar.gz",
+ FileName = "NzbDrone.develop.1.0.0.0.tar.gz",
Url = "http://download.sonarr.tv/v2/develop/mono/NzbDrone.develop.tar.gz",
- Version = new Version("2.0.0.0")
+ Version = new Version("1.0.0.0")
};
}
else
{
_updatePackage = new UpdatePackage
{
- FileName = "NzbDrone.develop.2.0.0.0.zip",
+ FileName = "NzbDrone.develop.1.0.0.0.zip",
Url = "http://download.sonarr.tv/v2/develop/windows/NzbDrone.develop.zip",
- Version = new Version("2.0.0.0")
+ Version = new Version("1.0.0.0")
};
}
@@ -90,17 +90,6 @@ namespace NzbDrone.Core.Test.UpdateTests
.Returns(true);
}
- [Test]
- public void should_not_update_if_inside_docker()
- {
- Mocker.GetMock().Setup(x => x.IsDocker).Returns(true);
-
- Subject.Execute(new ApplicationUpdateCommand());
-
- Mocker.GetMock()
- .Verify(c => c.Start(It.IsAny(), It.Is(s => s.StartsWith("12")), null, null, null), Times.Never());
- }
-
[Test]
public void should_delete_sandbox_before_update_if_folder_exists()
{
@@ -338,6 +327,28 @@ namespace NzbDrone.Core.Test.UpdateTests
.Verify(v => v.SaveConfigDictionary(It.Is>(d => d.ContainsKey("Branch") && (string)d["Branch"] == "fake")), Times.Once());
}
+ [Test]
+ public void should_not_update_with_built_in_updater_inside_docker_container()
+ {
+ Mocker.GetMock().Setup(x => x.PackageUpdateMechanism).Returns(UpdateMechanism.Docker);
+
+ Subject.Execute(new ApplicationUpdateCommand());
+
+ Mocker.GetMock()
+ .Verify(c => c.Start(It.IsAny(), It.Is(s => s.StartsWith("12")), null, null, null), Times.Never());
+ }
+
+ [Test]
+ public void should_not_update_with_built_in_updater_when_external_updater_is_configured()
+ {
+ Mocker.GetMock().Setup(x => x.IsExternalUpdateMechanism).Returns(true);
+
+ Subject.Execute(new ApplicationUpdateCommand());
+
+ Mocker.GetMock()
+ .Verify(c => c.Start(It.IsAny(), It.Is(s => s.StartsWith("12")), null, null, null), Times.Never());
+ }
+
[TearDown]
public void TearDown()
{
diff --git a/src/NzbDrone.Core/Applications/ApplicationFactory.cs b/src/NzbDrone.Core/Applications/ApplicationFactory.cs
index 492710fee..2ecd2e78b 100644
--- a/src/NzbDrone.Core/Applications/ApplicationFactory.cs
+++ b/src/NzbDrone.Core/Applications/ApplicationFactory.cs
@@ -53,7 +53,7 @@ namespace NzbDrone.Core.Applications
foreach (var application in applications)
{
- if (blockedApplications.TryGetValue(application.Definition.Id, out var blockedApplicationStatus))
+ if (blockedApplications.TryGetValue(application.Definition.Id, out var blockedApplicationStatus) && blockedApplicationStatus.DisabledTill.HasValue)
{
_logger.Debug("Temporarily ignoring application {0} till {1} due to recent failures.", application.Definition.Name, blockedApplicationStatus.DisabledTill.Value.ToLocalTime());
continue;
diff --git a/src/NzbDrone.Core/Applications/ApplicationService.cs b/src/NzbDrone.Core/Applications/ApplicationService.cs
index 97e627fd9..0a5a81fbd 100644
--- a/src/NzbDrone.Core/Applications/ApplicationService.cs
+++ b/src/NzbDrone.Core/Applications/ApplicationService.cs
@@ -127,6 +127,8 @@ namespace NzbDrone.Core.Applications
private void SyncIndexers(List applications, List indexers, bool removeRemote = false, bool forceSync = false)
{
+ var sortedIndexers = indexers.OrderBy(i => i.Name, StringComparer.OrdinalIgnoreCase).ToList();
+
foreach (var app in applications)
{
var indexerMappings = _appIndexerMapService.GetMappingsForApp(app.Definition.Id);
@@ -157,7 +159,7 @@ namespace NzbDrone.Core.Applications
}
}
- foreach (var indexer in indexers)
+ foreach (var indexer in sortedIndexers)
{
var definition = indexer;
diff --git a/src/NzbDrone.Core/Applications/Lidarr/Lidarr.cs b/src/NzbDrone.Core/Applications/Lidarr/Lidarr.cs
index 14d6824a4..19c842f5c 100644
--- a/src/NzbDrone.Core/Applications/Lidarr/Lidarr.cs
+++ b/src/NzbDrone.Core/Applications/Lidarr/Lidarr.cs
@@ -64,6 +64,7 @@ namespace NzbDrone.Core.Applications.Lidarr
failures.AddIfNotNull(new ValidationFailure("ProwlarrUrl", "Prowlarr URL is invalid, Lidarr cannot connect to Prowlarr"));
break;
case HttpStatusCode.SeeOther:
+ case HttpStatusCode.TemporaryRedirect:
_logger.Warn(ex, "Lidarr returned redirect and is invalid");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Lidarr URL is invalid, Prowlarr cannot connect to Lidarr - are you missing a URL base?"));
break;
diff --git a/src/NzbDrone.Core/Applications/Lidarr/LidarrV1Proxy.cs b/src/NzbDrone.Core/Applications/Lidarr/LidarrV1Proxy.cs
index 9b7f37685..3fcb337c0 100644
--- a/src/NzbDrone.Core/Applications/Lidarr/LidarrV1Proxy.cs
+++ b/src/NzbDrone.Core/Applications/Lidarr/LidarrV1Proxy.cs
@@ -166,6 +166,7 @@ namespace NzbDrone.Core.Applications.Lidarr
_logger.Error(ex, "Invalid Request");
break;
case HttpStatusCode.SeeOther:
+ case HttpStatusCode.TemporaryRedirect:
_logger.Warn(ex, "App returned redirect and is invalid. Check App URL");
break;
case HttpStatusCode.NotFound:
diff --git a/src/NzbDrone.Core/Applications/Radarr/Radarr.cs b/src/NzbDrone.Core/Applications/Radarr/Radarr.cs
index db645d45b..85b9c4a3b 100644
--- a/src/NzbDrone.Core/Applications/Radarr/Radarr.cs
+++ b/src/NzbDrone.Core/Applications/Radarr/Radarr.cs
@@ -64,6 +64,7 @@ namespace NzbDrone.Core.Applications.Radarr
failures.AddIfNotNull(new ValidationFailure("ProwlarrUrl", "Prowlarr URL is invalid, Radarr cannot connect to Prowlarr"));
break;
case HttpStatusCode.SeeOther:
+ case HttpStatusCode.TemporaryRedirect:
_logger.Warn(ex, "Radarr returned redirect and is invalid");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Radarr URL is invalid, Prowlarr cannot connect to Radarr - are you missing a URL base?"));
break;
diff --git a/src/NzbDrone.Core/Applications/Radarr/RadarrV3Proxy.cs b/src/NzbDrone.Core/Applications/Radarr/RadarrV3Proxy.cs
index e74baad21..d431856aa 100644
--- a/src/NzbDrone.Core/Applications/Radarr/RadarrV3Proxy.cs
+++ b/src/NzbDrone.Core/Applications/Radarr/RadarrV3Proxy.cs
@@ -179,6 +179,7 @@ namespace NzbDrone.Core.Applications.Radarr
_logger.Error(ex, "Invalid Request");
break;
case HttpStatusCode.SeeOther:
+ case HttpStatusCode.TemporaryRedirect:
_logger.Warn(ex, "App returned redirect and is invalid. Check App URL");
break;
case HttpStatusCode.NotFound:
diff --git a/src/NzbDrone.Core/Applications/Readarr/Readarr.cs b/src/NzbDrone.Core/Applications/Readarr/Readarr.cs
index 79ee5c52d..1fc6742ae 100644
--- a/src/NzbDrone.Core/Applications/Readarr/Readarr.cs
+++ b/src/NzbDrone.Core/Applications/Readarr/Readarr.cs
@@ -64,6 +64,7 @@ namespace NzbDrone.Core.Applications.Readarr
failures.AddIfNotNull(new ValidationFailure("ProwlarrUrl", "Prowlarr URL is invalid, Readarr cannot connect to Prowlarr"));
break;
case HttpStatusCode.SeeOther:
+ case HttpStatusCode.TemporaryRedirect:
_logger.Warn(ex, "Readarr returned redirect and is invalid");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Readarr URL is invalid, Prowlarr cannot connect to Readarr - are you missing a URL base?"));
break;
diff --git a/src/NzbDrone.Core/Applications/Readarr/ReadarrV1Proxy.cs b/src/NzbDrone.Core/Applications/Readarr/ReadarrV1Proxy.cs
index 71e8a2c45..899ef79b6 100644
--- a/src/NzbDrone.Core/Applications/Readarr/ReadarrV1Proxy.cs
+++ b/src/NzbDrone.Core/Applications/Readarr/ReadarrV1Proxy.cs
@@ -153,6 +153,7 @@ namespace NzbDrone.Core.Applications.Readarr
_logger.Error(ex, "Invalid Request");
break;
case HttpStatusCode.SeeOther:
+ case HttpStatusCode.TemporaryRedirect:
_logger.Warn(ex, "App returned redirect and is invalid. Check App URL");
break;
case HttpStatusCode.NotFound:
diff --git a/src/NzbDrone.Core/Applications/Sonarr/Sonarr.cs b/src/NzbDrone.Core/Applications/Sonarr/Sonarr.cs
index 0e554ba83..6e5284fc7 100644
--- a/src/NzbDrone.Core/Applications/Sonarr/Sonarr.cs
+++ b/src/NzbDrone.Core/Applications/Sonarr/Sonarr.cs
@@ -64,6 +64,7 @@ namespace NzbDrone.Core.Applications.Sonarr
failures.AddIfNotNull(new ValidationFailure("ProwlarrUrl", "Prowlarr URL is invalid, Sonarr cannot connect to Prowlarr"));
break;
case HttpStatusCode.SeeOther:
+ case HttpStatusCode.TemporaryRedirect:
_logger.Warn(ex, "Sonarr returned redirect and is invalid");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Sonarr URL is invalid, Prowlarr cannot connect to Sonarr - are you missing a URL base?"));
break;
diff --git a/src/NzbDrone.Core/Applications/Sonarr/SonarrV3Proxy.cs b/src/NzbDrone.Core/Applications/Sonarr/SonarrV3Proxy.cs
index 48dcea40d..f92043c99 100644
--- a/src/NzbDrone.Core/Applications/Sonarr/SonarrV3Proxy.cs
+++ b/src/NzbDrone.Core/Applications/Sonarr/SonarrV3Proxy.cs
@@ -166,6 +166,7 @@ namespace NzbDrone.Core.Applications.Sonarr
_logger.Error(ex, "Invalid Request");
break;
case HttpStatusCode.SeeOther:
+ case HttpStatusCode.TemporaryRedirect:
_logger.Warn(ex, "App returned redirect and is invalid. Check App URL");
break;
case HttpStatusCode.NotFound:
diff --git a/src/NzbDrone.Core/Applications/Whisparr/Whisparr.cs b/src/NzbDrone.Core/Applications/Whisparr/Whisparr.cs
index d864152b2..0c149fc7c 100644
--- a/src/NzbDrone.Core/Applications/Whisparr/Whisparr.cs
+++ b/src/NzbDrone.Core/Applications/Whisparr/Whisparr.cs
@@ -64,6 +64,7 @@ namespace NzbDrone.Core.Applications.Whisparr
failures.AddIfNotNull(new ValidationFailure("ProwlarrUrl", "Prowlarr URL is invalid, Whisparr cannot connect to Prowlarr"));
break;
case HttpStatusCode.SeeOther:
+ case HttpStatusCode.TemporaryRedirect:
_logger.Warn(ex, "Whisparr returned redirect and is invalid");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Whisparr URL is invalid, Prowlarr cannot connect to Whisparr - are you missing a URL base?"));
break;
diff --git a/src/NzbDrone.Core/Applications/Whisparr/WhisparrV3Proxy.cs b/src/NzbDrone.Core/Applications/Whisparr/WhisparrV3Proxy.cs
index 530005c5f..e2ee60524 100644
--- a/src/NzbDrone.Core/Applications/Whisparr/WhisparrV3Proxy.cs
+++ b/src/NzbDrone.Core/Applications/Whisparr/WhisparrV3Proxy.cs
@@ -151,6 +151,7 @@ namespace NzbDrone.Core.Applications.Whisparr
_logger.Error(ex, "Invalid Request");
break;
case HttpStatusCode.SeeOther:
+ case HttpStatusCode.TemporaryRedirect:
_logger.Warn(ex, "App returned redirect and is invalid. Check App URL");
break;
case HttpStatusCode.NotFound:
diff --git a/src/NzbDrone.Core/Backup/BackupService.cs b/src/NzbDrone.Core/Backup/BackupService.cs
index 14fc91efc..051d045bb 100644
--- a/src/NzbDrone.Core/Backup/BackupService.cs
+++ b/src/NzbDrone.Core/Backup/BackupService.cs
@@ -66,12 +66,19 @@ namespace NzbDrone.Core.Backup
{
_logger.ProgressInfo("Starting Backup");
+ var backupFolder = GetBackupFolder(backupType);
+
_diskProvider.EnsureFolder(_backupTempFolder);
- _diskProvider.EnsureFolder(GetBackupFolder(backupType));
+ _diskProvider.EnsureFolder(backupFolder);
+
+ if (!_diskProvider.FolderWritable(backupFolder))
+ {
+ throw new UnauthorizedAccessException($"Backup folder {backupFolder} is not writable");
+ }
var dateNow = DateTime.Now;
var backupFilename = $"prowlarr_backup_v{BuildInfo.Version}_{dateNow:yyyy.MM.dd_HH.mm.ss}.zip";
- var backupPath = Path.Combine(GetBackupFolder(backupType), backupFilename);
+ var backupPath = Path.Combine(backupFolder, backupFilename);
Cleanup();
diff --git a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs
index 6b5203e47..f4715b203 100644
--- a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs
+++ b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs
@@ -65,6 +65,7 @@ namespace NzbDrone.Core.Configuration
string PostgresPassword { get; }
string PostgresMainDb { get; }
string PostgresLogDb { get; }
+ bool TrustCgnatIpAddresses { get; }
}
public class ConfigFileProvider : IConfigFileProvider
@@ -273,7 +274,7 @@ namespace NzbDrone.Core.Configuration
{
var instanceName = _appOptions.InstanceName ?? GetValue("InstanceName", BuildInfo.AppName);
- if (instanceName.ContainsIgnoreCase(BuildInfo.AppName))
+ if (instanceName.Contains(BuildInfo.AppName, StringComparison.OrdinalIgnoreCase))
{
return instanceName;
}
@@ -479,5 +480,7 @@ namespace NzbDrone.Core.Configuration
SetValue("ApiKey", GenerateApiKey());
_eventAggregator.PublishEvent(new ApiKeyChangedEvent());
}
+
+ public bool TrustCgnatIpAddresses => _authOptions.TrustCgnatIpAddresses ?? GetValueBoolean("TrustCgnatIpAddresses", false, persist: false);
}
}
diff --git a/src/NzbDrone.Core/Configuration/ConfigService.cs b/src/NzbDrone.Core/Configuration/ConfigService.cs
index 208692c97..27a953823 100644
--- a/src/NzbDrone.Core/Configuration/ConfigService.cs
+++ b/src/NzbDrone.Core/Configuration/ConfigService.cs
@@ -183,6 +183,12 @@ namespace NzbDrone.Core.Configuration
public string ApplicationUrl => GetValue("ApplicationUrl", string.Empty);
+ public bool TrustCgnatIpAddresses
+ {
+ get { return GetValueBoolean("TrustCgnatIpAddresses", false); }
+ set { SetValue("TrustCgnatIpAddresses", value); }
+ }
+
private string GetValue(string key)
{
return GetValue(key, string.Empty);
diff --git a/src/NzbDrone.Core/Datastore/BasicRepository.cs b/src/NzbDrone.Core/Datastore/BasicRepository.cs
index dc76a5a31..796e277b7 100644
--- a/src/NzbDrone.Core/Datastore/BasicRepository.cs
+++ b/src/NzbDrone.Core/Datastore/BasicRepository.cs
@@ -254,7 +254,7 @@ namespace NzbDrone.Core.Datastore
protected void Delete(SqlBuilder builder)
{
- var sql = builder.AddDeleteTemplate(typeof(TModel)).LogQuery();
+ var sql = builder.AddDeleteTemplate(typeof(TModel));
using (var conn = _database.OpenConnection())
{
diff --git a/src/NzbDrone.Core/Datastore/Converters/TimeSpanConverter.cs b/src/NzbDrone.Core/Datastore/Converters/TimeSpanConverter.cs
index 902a26009..fdcb227c6 100644
--- a/src/NzbDrone.Core/Datastore/Converters/TimeSpanConverter.cs
+++ b/src/NzbDrone.Core/Datastore/Converters/TimeSpanConverter.cs
@@ -2,18 +2,17 @@ using System;
using System.Data;
using Dapper;
-namespace NzbDrone.Core.Datastore.Converters
-{
- public class DapperTimeSpanConverter : SqlMapper.TypeHandler
- {
- public override void SetValue(IDbDataParameter parameter, TimeSpan value)
- {
- parameter.Value = value.ToString();
- }
+namespace NzbDrone.Core.Datastore.Converters;
- public override TimeSpan Parse(object value)
- {
- return TimeSpan.Parse((string)value);
- }
+public class TimeSpanConverter : SqlMapper.TypeHandler
+{
+ public override void SetValue(IDbDataParameter parameter, TimeSpan value)
+ {
+ parameter.Value = value.ToString();
+ }
+
+ public override TimeSpan Parse(object value)
+ {
+ return value is string str ? TimeSpan.Parse(str) : TimeSpan.Zero;
}
}
diff --git a/src/NzbDrone.Core/Datastore/Database.cs b/src/NzbDrone.Core/Datastore/Database.cs
index 887039bcb..741a22f0b 100644
--- a/src/NzbDrone.Core/Datastore/Database.cs
+++ b/src/NzbDrone.Core/Datastore/Database.cs
@@ -2,7 +2,6 @@ using System;
using System.Data;
using System.Data.Common;
using System.Data.SQLite;
-using System.Text.RegularExpressions;
using Dapper;
using NLog;
using NzbDrone.Common.Instrumentation;
@@ -52,9 +51,8 @@ namespace NzbDrone.Core.Datastore
{
using var db = _datamapperFactory();
var dbConnection = db as DbConnection;
- var version = Regex.Replace(dbConnection.ServerVersion, @"\(.*?\)", "");
- return new Version(version);
+ return DatabaseVersionParser.ParseServerVersion(dbConnection.ServerVersion);
}
}
diff --git a/src/NzbDrone.Core/Datastore/DatabaseVersionParser.cs b/src/NzbDrone.Core/Datastore/DatabaseVersionParser.cs
new file mode 100644
index 000000000..ffc77cf18
--- /dev/null
+++ b/src/NzbDrone.Core/Datastore/DatabaseVersionParser.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Text.RegularExpressions;
+
+namespace NzbDrone.Core.Datastore;
+
+public static class DatabaseVersionParser
+{
+ private static readonly Regex VersionRegex = new (@"^[^ ]+", RegexOptions.Compiled);
+
+ public static Version ParseServerVersion(string serverVersion)
+ {
+ var match = VersionRegex.Match(serverVersion);
+
+ return match.Success ? new Version(match.Value) : null;
+ }
+}
diff --git a/src/NzbDrone.Core/Datastore/Migration/042_myanonamouse_freeleech_wedge_options.cs b/src/NzbDrone.Core/Datastore/Migration/042_myanonamouse_freeleech_wedge_options.cs
new file mode 100644
index 000000000..5a93488d5
--- /dev/null
+++ b/src/NzbDrone.Core/Datastore/Migration/042_myanonamouse_freeleech_wedge_options.cs
@@ -0,0 +1,60 @@
+using System.Collections.Generic;
+using System.Data;
+using Dapper;
+using FluentMigrator;
+using Newtonsoft.Json.Linq;
+using NzbDrone.Common.Serializer;
+using NzbDrone.Core.Datastore.Migration.Framework;
+
+namespace NzbDrone.Core.Datastore.Migration
+{
+ [Migration(042)]
+ public class myanonamouse_freeleech_wedge_options : NzbDroneMigrationBase
+ {
+ protected override void MainDbUpgrade()
+ {
+ Execute.WithConnection(MigrateIndexersToWedgeOptions);
+ }
+
+ private void MigrateIndexersToWedgeOptions(IDbConnection conn, IDbTransaction tran)
+ {
+ var updated = new List
diff --git a/src/NzbDrone.Core/Update/Commands/ApplicationCheckUpdateCommand.cs b/src/NzbDrone.Core/Update/Commands/ApplicationUpdateCheckCommand.cs
similarity index 65%
rename from src/NzbDrone.Core/Update/Commands/ApplicationCheckUpdateCommand.cs
rename to src/NzbDrone.Core/Update/Commands/ApplicationUpdateCheckCommand.cs
index 6987af3fa..fa2cfbf41 100644
--- a/src/NzbDrone.Core/Update/Commands/ApplicationCheckUpdateCommand.cs
+++ b/src/NzbDrone.Core/Update/Commands/ApplicationUpdateCheckCommand.cs
@@ -2,10 +2,12 @@ using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.Update.Commands
{
- public class ApplicationCheckUpdateCommand : Command
+ public class ApplicationUpdateCheckCommand : Command
{
public override bool SendUpdatesToClient => true;
public override string CompletionMessage => null;
+
+ public bool InstallMajorUpdate { get; set; }
}
}
diff --git a/src/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs b/src/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs
index 59a827a0b..6980af708 100644
--- a/src/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs
+++ b/src/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs
@@ -4,6 +4,7 @@ namespace NzbDrone.Core.Update.Commands
{
public class ApplicationUpdateCommand : Command
{
+ public bool InstallMajorUpdate { get; set; }
public override bool SendUpdatesToClient => true;
public override bool IsExclusive => true;
}
diff --git a/src/NzbDrone.Core/Update/InstallUpdateService.cs b/src/NzbDrone.Core/Update/InstallUpdateService.cs
index 971e1500f..793e1a8ac 100644
--- a/src/NzbDrone.Core/Update/InstallUpdateService.cs
+++ b/src/NzbDrone.Core/Update/InstallUpdateService.cs
@@ -20,7 +20,7 @@ using NzbDrone.Core.Update.Commands;
namespace NzbDrone.Core.Update
{
- public class InstallUpdateService : IExecute, IExecute, IHandle
+ public class InstallUpdateService : IExecute, IExecute, IHandle
{
private readonly ICheckUpdateService _checkUpdateService;
private readonly Logger _logger;
@@ -231,7 +231,7 @@ namespace NzbDrone.Core.Update
}
}
- private UpdatePackage GetUpdatePackage(CommandTrigger updateTrigger)
+ private UpdatePackage GetUpdatePackage(CommandTrigger updateTrigger, bool installMajorUpdate)
{
_logger.ProgressDebug("Checking for updates");
@@ -243,18 +243,24 @@ namespace NzbDrone.Core.Update
return null;
}
- if (_osInfo.IsDocker)
+ if (latestAvailable.Version.Major > BuildInfo.Version.Major && !installMajorUpdate)
{
- _logger.ProgressDebug("Updating is disabled inside a docker container. Please update the container image.");
+ _logger.ProgressInfo("Unable to install major update, please update update manually from System: Updates");
return null;
}
- if (OsInfo.IsNotWindows && !_configFileProvider.UpdateAutomatically && updateTrigger != CommandTrigger.Manual)
+ if (!_configFileProvider.UpdateAutomatically && updateTrigger != CommandTrigger.Manual)
{
_logger.ProgressDebug("Auto-update not enabled, not installing available update.");
return null;
}
+ if (_configFileProvider.UpdateMechanism == UpdateMechanism.BuiltIn && _deploymentInfoProvider.PackageUpdateMechanism == UpdateMechanism.Docker)
+ {
+ _logger.ProgressDebug("Built-In updater disabled inside a docker container. Please update the container image.");
+ return null;
+ }
+
// Safety net, ConfigureUpdateMechanism should take care of invalid settings
if (_configFileProvider.UpdateMechanism == UpdateMechanism.BuiltIn && _deploymentInfoProvider.IsExternalUpdateMechanism)
{
@@ -270,9 +276,9 @@ namespace NzbDrone.Core.Update
return latestAvailable;
}
- public void Execute(ApplicationCheckUpdateCommand message)
+ public void Execute(ApplicationUpdateCheckCommand message)
{
- if (GetUpdatePackage(message.Trigger) != null)
+ if (GetUpdatePackage(message.Trigger, true) != null)
{
_commandQueueManager.Push(new ApplicationUpdateCommand(), trigger: message.Trigger);
}
@@ -280,7 +286,7 @@ namespace NzbDrone.Core.Update
public void Execute(ApplicationUpdateCommand message)
{
- var latestAvailable = GetUpdatePackage(message.Trigger);
+ var latestAvailable = GetUpdatePackage(message.Trigger, message.InstallMajorUpdate);
if (latestAvailable != null)
{
diff --git a/src/NzbDrone.Core/Update/UpdatePackageProvider.cs b/src/NzbDrone.Core/Update/UpdatePackageProvider.cs
index 17743c8b0..58d9872cc 100644
--- a/src/NzbDrone.Core/Update/UpdatePackageProvider.cs
+++ b/src/NzbDrone.Core/Update/UpdatePackageProvider.cs
@@ -47,6 +47,7 @@ namespace NzbDrone.Core.Update
.AddQueryParam("runtime", "netcore")
.AddQueryParam("runtimeVer", _platformInfo.Version)
.AddQueryParam("dbType", _mainDatabase.DatabaseType)
+ .AddQueryParam("includeMajorVersion", true)
.SetSegment("branch", branch);
if (_analyticsService.IsEnabled)
diff --git a/src/NzbDrone.Core/Validation/IpValidation.cs b/src/NzbDrone.Core/Validation/IpValidation.cs
index eb5863caa..f4afa1f66 100644
--- a/src/NzbDrone.Core/Validation/IpValidation.cs
+++ b/src/NzbDrone.Core/Validation/IpValidation.cs
@@ -1,5 +1,4 @@
using FluentValidation;
-using FluentValidation.Validators;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Validation
@@ -10,10 +9,5 @@ namespace NzbDrone.Core.Validation
{
return ruleBuilder.Must(x => x.IsValidIpAddress()).WithMessage("Must contain wildcard (*) or a valid IP Address");
}
-
- public static IRuleBuilderOptions NotListenAllIp4Address(this IRuleBuilder ruleBuilder)
- {
- return ruleBuilder.SetValidator(new RegularExpressionValidator(@"^(?!0\.0\.0\.0)")).WithMessage("Use * instead of 0.0.0.0");
- }
}
}
diff --git a/src/NzbDrone.Host/Prowlarr.Host.csproj b/src/NzbDrone.Host/Prowlarr.Host.csproj
index 28343d318..12bd5168e 100644
--- a/src/NzbDrone.Host/Prowlarr.Host.csproj
+++ b/src/NzbDrone.Host/Prowlarr.Host.csproj
@@ -4,10 +4,10 @@
Library
-
-
-
-
+
+
+
+
diff --git a/src/NzbDrone.Integration.Test/ApiTests/CommandFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/CommandFixture.cs
index 91d0962ab..f8a8ccd51 100644
--- a/src/NzbDrone.Integration.Test/ApiTests/CommandFixture.cs
+++ b/src/NzbDrone.Integration.Test/ApiTests/CommandFixture.cs
@@ -10,7 +10,7 @@ namespace NzbDrone.Integration.Test.ApiTests
[Test]
public void should_be_able_to_run_update_check()
{
- var response = Commands.Post(new SimpleCommandResource { Name = "applicationcheckupdate" });
+ var response = Commands.Post(new SimpleCommandResource { Name = "applicationupdatecheck" });
response.Id.Should().NotBe(0);
}
diff --git a/src/NzbDrone.Integration.Test/Prowlarr.Integration.Test.csproj b/src/NzbDrone.Integration.Test/Prowlarr.Integration.Test.csproj
index d15de3f62..a4c142248 100644
--- a/src/NzbDrone.Integration.Test/Prowlarr.Integration.Test.csproj
+++ b/src/NzbDrone.Integration.Test/Prowlarr.Integration.Test.csproj
@@ -4,7 +4,7 @@
Library
-
+
diff --git a/src/NzbDrone.Test.Common/Prowlarr.Test.Common.csproj b/src/NzbDrone.Test.Common/Prowlarr.Test.Common.csproj
index a580baf44..b9ccacd9e 100644
--- a/src/NzbDrone.Test.Common/Prowlarr.Test.Common.csproj
+++ b/src/NzbDrone.Test.Common/Prowlarr.Test.Common.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/src/NzbDrone.Update/Prowlarr.Update.csproj b/src/NzbDrone.Update/Prowlarr.Update.csproj
index be34014e7..09b95e2f8 100644
--- a/src/NzbDrone.Update/Prowlarr.Update.csproj
+++ b/src/NzbDrone.Update/Prowlarr.Update.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/src/NzbDrone.Windows/Prowlarr.Windows.csproj b/src/NzbDrone.Windows/Prowlarr.Windows.csproj
index 5ce332f73..4a9f864e5 100644
--- a/src/NzbDrone.Windows/Prowlarr.Windows.csproj
+++ b/src/NzbDrone.Windows/Prowlarr.Windows.csproj
@@ -4,7 +4,7 @@
true
-
+
diff --git a/src/Prowlarr.Api.V1/Applications/ApplicationController.cs b/src/Prowlarr.Api.V1/Applications/ApplicationController.cs
index c3819d8f9..a63beedcf 100644
--- a/src/Prowlarr.Api.V1/Applications/ApplicationController.cs
+++ b/src/Prowlarr.Api.V1/Applications/ApplicationController.cs
@@ -1,4 +1,5 @@
using NzbDrone.Core.Applications;
+using NzbDrone.SignalR;
using Prowlarr.Http;
namespace Prowlarr.Api.V1.Applications
@@ -9,8 +10,8 @@ namespace Prowlarr.Api.V1.Applications
public static readonly ApplicationResourceMapper ResourceMapper = new ();
public static readonly ApplicationBulkResourceMapper BulkResourceMapper = new ();
- public ApplicationController(ApplicationFactory applicationsFactory)
- : base(applicationsFactory, "applications", ResourceMapper, BulkResourceMapper)
+ public ApplicationController(IBroadcastSignalRMessage signalRBroadcaster, ApplicationFactory applicationsFactory)
+ : base(signalRBroadcaster, applicationsFactory, "applications", ResourceMapper, BulkResourceMapper)
{
}
}
diff --git a/src/Prowlarr.Api.V1/Commands/CommandController.cs b/src/Prowlarr.Api.V1/Commands/CommandController.cs
index f74192f55..714685b22 100644
--- a/src/Prowlarr.Api.V1/Commands/CommandController.cs
+++ b/src/Prowlarr.Api.V1/Commands/CommandController.cs
@@ -61,9 +61,8 @@ namespace Prowlarr.Api.V1.Commands
using var reader = new StreamReader(Request.Body);
var body = reader.ReadToEnd();
- dynamic command = STJson.Deserialize(body, commandType);
+ var command = STJson.Deserialize(body, commandType) as Command;
- command.Trigger = CommandTrigger.Manual;
command.SuppressMessages = !command.SendUpdatesToClient;
command.SendUpdatesToClient = true;
command.ClientUserAgent = Request.Headers["User-Agent"];
diff --git a/src/Prowlarr.Api.V1/Config/HostConfigController.cs b/src/Prowlarr.Api.V1/Config/HostConfigController.cs
index 6758040be..a844f8d2e 100644
--- a/src/Prowlarr.Api.V1/Config/HostConfigController.cs
+++ b/src/Prowlarr.Api.V1/Config/HostConfigController.cs
@@ -34,7 +34,6 @@ namespace Prowlarr.Api.V1.Config
SharedValidator.RuleFor(c => c.BindAddress)
.ValidIpAddress()
- .NotListenAllIp4Address()
.When(c => c.BindAddress != "*" && c.BindAddress != "localhost");
SharedValidator.RuleFor(c => c.Port).ValidPort();
diff --git a/src/Prowlarr.Api.V1/Config/HostConfigResource.cs b/src/Prowlarr.Api.V1/Config/HostConfigResource.cs
index 77c274d66..4bd0cd10a 100644
--- a/src/Prowlarr.Api.V1/Config/HostConfigResource.cs
+++ b/src/Prowlarr.Api.V1/Config/HostConfigResource.cs
@@ -46,6 +46,7 @@ namespace Prowlarr.Api.V1.Config
public int BackupInterval { get; set; }
public int BackupRetention { get; set; }
public int HistoryCleanupDays { get; set; }
+ public bool TrustCgnatIpAddresses { get; set; }
}
public static class HostConfigResourceMapper
diff --git a/src/Prowlarr.Api.V1/DownloadClient/DownloadClientController.cs b/src/Prowlarr.Api.V1/DownloadClient/DownloadClientController.cs
index 5dd43ea7d..347c7e9c6 100644
--- a/src/Prowlarr.Api.V1/DownloadClient/DownloadClientController.cs
+++ b/src/Prowlarr.Api.V1/DownloadClient/DownloadClientController.cs
@@ -1,4 +1,6 @@
+using FluentValidation;
using NzbDrone.Core.Download;
+using NzbDrone.SignalR;
using Prowlarr.Http;
namespace Prowlarr.Api.V1.DownloadClient
@@ -9,9 +11,10 @@ namespace Prowlarr.Api.V1.DownloadClient
public static readonly DownloadClientResourceMapper ResourceMapper = new ();
public static readonly DownloadClientBulkResourceMapper BulkResourceMapper = new ();
- public DownloadClientController(IDownloadClientFactory downloadClientFactory)
- : base(downloadClientFactory, "downloadclient", ResourceMapper, BulkResourceMapper)
+ public DownloadClientController(IBroadcastSignalRMessage signalRBroadcaster, IDownloadClientFactory downloadClientFactory)
+ : base(signalRBroadcaster, downloadClientFactory, "downloadclient", ResourceMapper, BulkResourceMapper)
{
+ SharedValidator.RuleFor(c => c.Priority).InclusiveBetween(1, 50);
}
}
}
diff --git a/src/Prowlarr.Api.V1/Health/HealthResource.cs b/src/Prowlarr.Api.V1/Health/HealthResource.cs
index 1e2c8ffa5..3f357285d 100644
--- a/src/Prowlarr.Api.V1/Health/HealthResource.cs
+++ b/src/Prowlarr.Api.V1/Health/HealthResource.cs
@@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.Linq;
-using NzbDrone.Common.Http;
using NzbDrone.Core.HealthCheck;
using Prowlarr.Http.REST;
@@ -11,7 +10,7 @@ namespace Prowlarr.Api.V1.Health
public string Source { get; set; }
public HealthCheckResult Type { get; set; }
public string Message { get; set; }
- public HttpUri WikiUrl { get; set; }
+ public string WikiUrl { get; set; }
}
public static class HealthResourceMapper
@@ -29,7 +28,7 @@ namespace Prowlarr.Api.V1.Health
Source = model.Source.Name,
Type = model.Type,
Message = model.Message,
- WikiUrl = model.WikiUrl
+ WikiUrl = model.WikiUrl.FullUri
};
}
diff --git a/src/Prowlarr.Api.V1/IndexerProxies/IndexerProxyController.cs b/src/Prowlarr.Api.V1/IndexerProxies/IndexerProxyController.cs
index ba6cbfbe7..d1be85292 100644
--- a/src/Prowlarr.Api.V1/IndexerProxies/IndexerProxyController.cs
+++ b/src/Prowlarr.Api.V1/IndexerProxies/IndexerProxyController.cs
@@ -1,6 +1,7 @@
using System;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Core.IndexerProxies;
+using NzbDrone.SignalR;
using Prowlarr.Http;
namespace Prowlarr.Api.V1.IndexerProxies
@@ -11,8 +12,8 @@ namespace Prowlarr.Api.V1.IndexerProxies
public static readonly IndexerProxyResourceMapper ResourceMapper = new ();
public static readonly IndexerProxyBulkResourceMapper BulkResourceMapper = new ();
- public IndexerProxyController(IndexerProxyFactory notificationFactory)
- : base(notificationFactory, "indexerProxy", ResourceMapper, BulkResourceMapper)
+ public IndexerProxyController(IBroadcastSignalRMessage signalRBroadcaster, IndexerProxyFactory notificationFactory)
+ : base(signalRBroadcaster, notificationFactory, "indexerProxy", ResourceMapper, BulkResourceMapper)
{
}
diff --git a/src/Prowlarr.Api.V1/Indexers/IndexerController.cs b/src/Prowlarr.Api.V1/Indexers/IndexerController.cs
index 0047acbca..07955299d 100644
--- a/src/Prowlarr.Api.V1/Indexers/IndexerController.cs
+++ b/src/Prowlarr.Api.V1/Indexers/IndexerController.cs
@@ -1,6 +1,7 @@
using FluentValidation;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Validation;
+using NzbDrone.SignalR;
using Prowlarr.Http;
namespace Prowlarr.Api.V1.Indexers
@@ -8,17 +9,19 @@ namespace Prowlarr.Api.V1.Indexers
[V1ApiController]
public class IndexerController : ProviderControllerBase
{
- public IndexerController(IndexerFactory indexerFactory,
+ public IndexerController(IBroadcastSignalRMessage signalRBroadcaster,
+ IndexerFactory indexerFactory,
IndexerResourceMapper resourceMapper,
IndexerBulkResourceMapper bulkResourceMapper,
AppProfileExistsValidator appProfileExistsValidator,
DownloadClientExistsValidator downloadClientExistsValidator)
- : base(indexerFactory, "indexer", resourceMapper, bulkResourceMapper)
+ : base(signalRBroadcaster, indexerFactory, "indexer", resourceMapper, bulkResourceMapper)
{
SharedValidator.RuleFor(c => c.AppProfileId).Cascade(CascadeMode.Stop)
.ValidId()
.SetValidator(appProfileExistsValidator);
+ SharedValidator.RuleFor(c => c.Priority).InclusiveBetween(1, 50);
SharedValidator.RuleFor(c => c.DownloadClientId).SetValidator(downloadClientExistsValidator);
}
}
diff --git a/src/Prowlarr.Api.V1/Indexers/IndexerResource.cs b/src/Prowlarr.Api.V1/Indexers/IndexerResource.cs
index 3dc9a2979..31624dbf2 100644
--- a/src/Prowlarr.Api.V1/Indexers/IndexerResource.cs
+++ b/src/Prowlarr.Api.V1/Indexers/IndexerResource.cs
@@ -119,20 +119,29 @@ namespace Prowlarr.Api.V1.Indexers
var settings = (CardigannSettings)definition.Settings;
- var cardigannDefinition = _definitionService.GetCachedDefinition(settings.DefinitionFile);
-
- foreach (var field in resource.Fields)
+ if (settings.DefinitionFile.IsNotNullOrWhiteSpace())
{
- if (!standardFields.Contains(field.Name))
+ var cardigannDefinition = _definitionService.GetCachedDefinition(settings.DefinitionFile);
+
+ foreach (var field in resource.Fields)
{
- if (field.Name == "cardigannCaptcha")
+ if (!standardFields.Contains(field.Name))
{
- settings.ExtraFieldData["CAPTCHA"] = field.Value?.ToString() ?? string.Empty;
- }
- else
- {
- var cardigannSetting = cardigannDefinition.Settings.FirstOrDefault(x => x.Name == field.Name);
- settings.ExtraFieldData[field.Name] = MapValue(cardigannSetting, field.Value);
+ if (field.Name == "cardigannCaptcha")
+ {
+ settings.ExtraFieldData["CAPTCHA"] = field.Value?.ToString() ?? string.Empty;
+ }
+ else
+ {
+ var cardigannSetting = cardigannDefinition.Settings.FirstOrDefault(x => x.Name == field.Name);
+
+ if (cardigannSetting == null)
+ {
+ throw new ArgumentOutOfRangeException(field.Name, "Unknown Cardigann setting.");
+ }
+
+ settings.ExtraFieldData[field.Name] = MapValue(cardigannSetting, field.Value);
+ }
}
}
}
@@ -193,19 +202,23 @@ namespace Prowlarr.Api.V1.Indexers
{
case "info_cookie":
field.Label = "How to get the Cookie";
- field.Value = "
Login to this tracker with your browser
Open the DevTools panel by pressing F12
Select the Network tab
Click on the Doc button (Chrome Browser) or HTML button (FireFox)
Refresh the page by pressing F5
Click on the first row entry
Select the Headers tab on the Right panel
Find 'cookie:' in the Request Headers section
Select and Copy the whole cookie string (everything after 'cookie: ') and Paste here.
";
- break;
- case "info_flaresolverr":
- field.Label = "FlareSolverr Info";
- field.Value = "This site may use Cloudflare DDoS Protection, therefore Prowlarr requires FlareSolverr to access it.";
+ field.Value = "
Login to this tracker with your browser
If present in the login page, ensure you have the Remember me ticked and the Log Me Out if IP Changes unticked when you login
Navigate to the web site's torrent search page to view the list of available torrents for download
Open the DevTools panel by pressing F12
Select the Network tab
Click on the Doc button (Chrome Browser) or HTML button (FireFox)
Refresh the page by pressing F5
Click on the first row entry
Select the Headers tab on the Right panel
Find 'cookie:' in the Request Headers section
Select and Copy the whole cookie string (everything after 'cookie: ') and Paste here.
";
+ field.HelpLink = "https://wiki.servarr.com/useful-tools#finding-cookies";
break;
case "info_useragent":
field.Label = "How to get the User-Agent";
field.Value = "
From the same place you fetched the cookie,
Find 'user-agent:' in the Request Headers section
Select and Copy the whole user-agent string (everything after 'user-agent: ') and Paste here.