Compare commits

..

34 Commits

Author SHA1 Message Date
Qstick
c14ada8c31 New: Add support for Simplepush notifications
Co-Authored-By: Timm Schäuble <Timm.Schaeuble@gmail.com>

(cherry picked from commit 4c7df31070fbd370b26dbcc07131f21eb88d35fc)
2022-11-29 02:38:43 +00:00
Qstick
aa2855a62b Re-saving edited providers will forcibly save them
Fixes #533

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-11-27 19:32:14 -06:00
Qstick
599f52e72f Fixed: Validation when testing indexers, import lists, connections and download clients
Fixes #1612

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-11-27 19:10:12 -06:00
Qstick
c5c2b94b9a Fixup Repack Tests 2022-11-25 22:16:34 -06:00
Qstick
b016b36435 Fixed: Validate if equals or child for startup folder
Fixes #1712
Close #1713

(cherry picked from commit 0991cfe27efd6ddb533227b25754661e18d7e9ad)
2022-11-25 22:07:42 -06:00
Qstick
d93a0c27e9 Sentry logging exceptions
Fixes #855

Co-Authored-By: Taloth <Taloth@users.noreply.github.com>
2022-11-25 21:58:58 -06:00
Qstick
eecf08e063 Updated NLog Version
Co-Authored-By: Robin Dadswell <19610103+RobinDadswell@users.noreply.github.com>
2022-11-25 21:52:52 -06:00
Qstick
fb1643f630 Lint Fixes 2022-11-25 21:39:44 -06:00
bakerboy448
a061179f6b Fixed: Repack Preference Ignored
Fixes #1867

(cherry picked from commit 04447d9d4db8cc3d54baf0a721f4430cf77908c4)
2022-11-25 21:39:44 -06:00
Stevie Robinson
b441f6c05b Fixed: updated rTorrent download client note
Fixes #1882

(cherry picked from commit 743d28b93a55553ee25381570d0daa04ed2117af)
2022-11-25 21:33:35 -06:00
Chromo-residuum-opec
0904eac300 Update help text for rTorrent download client options
Fixes #1868

(cherry picked from commit d2a23f7bcdf71800f019644d7b6b5d712e311d7f)
2022-11-25 21:31:57 -06:00
Qstick
29e3d8f477 Fixed: Fall back to sorting by release title if artist is not matched
Fixes #1870
2022-11-25 21:29:38 -06:00
Qstick
71bd88e531 New: Add Release group to history for all events
Fixes #1499

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-11-25 21:28:05 -06:00
Stevie Robinson
0689bec779 New: Add optional Source Title column to history
(cherry picked from commit 581fb2cb3d47d62fe16b840081647056ec77043d)
2022-11-25 21:16:37 -06:00
Devin Buhl
117a5c8010 New: Add application URL to host configuration settings
Fixes #1816
Closes #1817

(cherry picked from commit 762042ba97c2ae689cee32d8e66a458f6d7a8adc)
2022-11-25 21:16:37 -06:00
Qstick
d10d91439f New: Add indexer name to the download report log
Fixes #1904
2022-11-25 21:16:37 -06:00
Mark McDowall
ed0722bae4 New: Validate that naming formats don't contain illegal characters
Fixes #620

(cherry picked from commit 145c644c9d8f1636027da8a782a7e74f3182c678)
2022-11-25 21:16:37 -06:00
psylenced
e69371deca Fix: Trace logging postgres cleanse for large json files.
(cherry picked from commit 2ce9d099e1001eb4fccd61edcb0597782da872d4)
2022-11-25 20:30:54 -06:00
Chris
5ae8deb9d6 Fixed: Postgres secret regex now less greedy
[common]

(cherry picked from commit 812e5ac5a3d76b69c172c5611b315ea809ab4b48)
2022-11-25 20:30:54 -06:00
Chris
291674fc18 Fixed: Regex in log cleanser taking 10+ minutes on messages longer than 100k.
(cherry picked from commit d01bae92bfd68b04c54ab19bafe8af16c68ce711)
2022-11-25 20:30:54 -06:00
Qstick
774cd04d32 Fixed SeedConfigProvider cache refresh after indexer settings change
Fixes #1036

Co-Authored-By: Taloth <Taloth@users.noreply.github.com>
2022-11-25 20:24:00 -06:00
Qstick
3eb0533b17 New: Disable autocomplete of port number
Fixes #1249

Co-Authored-By: Stevie Robinson <stevie.robinson@gmail.com>
2022-11-25 20:21:10 -06:00
Qstick
2fe11ca1a9 Rename NzbSearchService to ReleaseSearchService
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-11-25 20:18:28 -06:00
Qstick
c8ae6b0299 Fixed: Custom Script Health Issue Level
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-11-25 20:14:47 -06:00
Qstick
2086ae1e2c Ensure .Mono and .Windows projects have all dependencies in build output
Co-Authored-By: ta264 <ta264@users.noreply.github.com>
2022-11-25 20:10:23 -06:00
Chris
2aeec97d5e Added: Ntfy provider for notifications
(cherry picked from commit f6e6bc98f7846328d158ab2f804ca65b49632912)
2022-11-25 20:02:23 -06:00
Qstick
45e9d14916 New: Base API info endpoint
(cherry picked from commit 5e57ffbcf9ac3a346d4bf2b692248393215bad89)
2022-11-25 20:01:21 -06:00
Qstick
ded45a53f3 Improve page scrollbar
Fixed: Scrolling in Firefox in small window (requires refresh)
New: Style scrollbar in Firefox
Fixed: Scrolling with click and drag
Fixes #3088
Fixes #2706
2022-11-25 20:00:56 -06:00
Mark McDowall
d5cbb8f84f Replace react-custom-scrollbars
(cherry picked from commit 1c6c9a7960865d4bf8f58d5d04bef1aa1409594a)
2022-11-25 20:00:56 -06:00
Mark McDowall
cac0fca0d2 Added react-hooks lint rules
(cherry picked from commit 381d64259396582de8d63ada99333e42cf5e3189)
2022-11-25 19:55:55 -06:00
Mark McDowall
9b5a050c40 Publish ApplicationStartingEvent during startup
(cherry picked from commit 5400bce1295bdc4198d2cfe0b9258bbb7ccf0852)
2022-11-25 19:54:05 -06:00
Weblate
9fa67dbe5f Translated using Weblate (German) [skip ci]
Currently translated at 100.0% (879 of 879 strings)

Translated using Weblate (Czech) [skip ci]

Currently translated at 62.4% (549 of 879 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 79.8% (702 of 879 strings)

Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Zalhera <tobias.bechen@gmail.com>
Co-authored-by: marapavelka <mara.pavelka@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fi/
Translation: Servarr/Readarr
2022-11-25 19:25:06 -06:00
Mark McDowall
505ef5ee63 Fixed: Testing SABnzbd when no categories are configured
(cherry picked from commit 0e31281828c737e3f6eecbb870960194888a970a)
2022-11-25 19:24:38 -06:00
Mark McDowall
87a2d7382e Added SECURITY.md
(cherry picked from commit 80af164385d9087a627142ca2281ae74ac0572af)

(cherry picked from commit aa8e886dab3ffedc835900db39787d47dc12465c)
2022-11-21 19:25:11 -06:00
77 changed files with 961 additions and 213 deletions

View File

@@ -3,7 +3,6 @@
## Reporting a Vulnerability ## Reporting a Vulnerability
Please report (suspected) security vulnerabilities on Discord (preferred) to Please report (suspected) security vulnerabilities on Discord (preferred) to
either markus101 any of the Servarr Dev role holders (red names) or via email: development@servarr.com. You will receive a response from
#2148 or Taloth#7357 or via email: security@sonarr.tv. You will receive a response from
us within 72 hours. If the issue is confirmed, we will release a patch as soon us within 72 hours. If the issue is confirmed, we will release a patch as soon
as possible depending on complexity/severity. as possible depending on complexity/severity.

View File

@@ -39,6 +39,7 @@ module.exports = {
plugins: [ plugins: [
'filenames', 'filenames',
'react', 'react',
'react-hooks',
'simple-import-sort', 'simple-import-sort',
'import' 'import'
], ],
@@ -308,7 +309,9 @@ module.exports = {
'react/react-in-jsx-scope': 2, 'react/react-in-jsx-scope': 2,
'react/self-closing-comp': 2, 'react/self-closing-comp': 2,
'react/sort-comp': 2, 'react/sort-comp': 2,
'react/jsx-wrap-multilines': 2 'react/jsx-wrap-multilines': 2,
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'error'
}, },
overrides: [ overrides: [
{ {

View File

@@ -169,6 +169,16 @@ class HistoryRow extends Component {
); );
} }
if (name === 'sourceTitle') {
return (
<TableRowCell
key={name}
>
{sourceTitle}
</TableRowCell>
);
}
if (name === 'details') { if (name === 'details') {
return ( return (
<TableRowCell <TableRowCell

View File

@@ -12,9 +12,9 @@ import ModalBody from 'Components/Modal/ModalBody';
import Portal from 'Components/Portal'; import Portal from 'Components/Portal';
import Scroller from 'Components/Scroller/Scroller'; import Scroller from 'Components/Scroller/Scroller';
import { icons, scrollDirections, sizes } from 'Helpers/Props'; import { icons, scrollDirections, sizes } from 'Helpers/Props';
import { isMobile as isMobileUtil } from 'Utilities/browser';
import * as keyCodes from 'Utilities/Constants/keyCodes'; import * as keyCodes from 'Utilities/Constants/keyCodes';
import getUniqueElememtId from 'Utilities/getUniqueElementId'; import getUniqueElememtId from 'Utilities/getUniqueElementId';
import { isMobile as isMobileUtil } from 'Utilities/mobile';
import HintedSelectInputOption from './HintedSelectInputOption'; import HintedSelectInputOption from './HintedSelectInputOption';
import HintedSelectInputSelectedValue from './HintedSelectInputSelectedValue'; import HintedSelectInputSelectedValue from './HintedSelectInputSelectedValue';
import TextInput from './TextInput'; import TextInput from './TextInput';

View File

@@ -33,7 +33,7 @@ function ConfirmModal(props) {
return () => unbindShortcut('enter', onConfirm); return () => unbindShortcut('enter', onConfirm);
} }
}, [isOpen, onConfirm]); }, [bindShortcut, unbindShortcut, isOpen, onConfirm]);
return ( return (
<Modal <Modal

View File

@@ -6,9 +6,9 @@ import ReactDOM from 'react-dom';
import FocusLock from 'react-focus-lock'; import FocusLock from 'react-focus-lock';
import ErrorBoundary from 'Components/Error/ErrorBoundary'; import ErrorBoundary from 'Components/Error/ErrorBoundary';
import { sizes } from 'Helpers/Props'; import { sizes } from 'Helpers/Props';
import { isIOS } from 'Utilities/browser';
import * as keyCodes from 'Utilities/Constants/keyCodes'; import * as keyCodes from 'Utilities/Constants/keyCodes';
import getUniqueElememtId from 'Utilities/getUniqueElementId'; import getUniqueElememtId from 'Utilities/getUniqueElementId';
import { isIOS } from 'Utilities/mobile';
import { setScrollLock } from 'Utilities/scrollLock'; import { setScrollLock } from 'Utilities/scrollLock';
import ModalError from './ModalError'; import ModalError from './ModalError';
import styles from './Modal.css'; import styles from './Modal.css';

View File

@@ -1,23 +1,12 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import OverlayScroller from 'Components/Scroller/OverlayScroller';
import Scroller from 'Components/Scroller/Scroller'; import Scroller from 'Components/Scroller/Scroller';
import { scrollDirections } from 'Helpers/Props'; import { scrollDirections } from 'Helpers/Props';
import { isMobile as isMobileUtil } from 'Utilities/mobile';
import { isLocked } from 'Utilities/scrollLock'; import { isLocked } from 'Utilities/scrollLock';
import styles from './PageContentBody.css'; import styles from './PageContentBody.css';
class PageContentBody extends Component { class PageContentBody extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this._isMobile = isMobileUtil();
}
// //
// Listeners // Listeners
@@ -41,10 +30,8 @@ class PageContentBody extends Component {
...otherProps ...otherProps
} = this.props; } = this.props;
const ScrollerComponent = this._isMobile ? Scroller : OverlayScroller;
return ( return (
<ScrollerComponent <Scroller
className={className} className={className}
scrollDirection={scrollDirections.VERTICAL} scrollDirection={scrollDirections.VERTICAL}
{...otherProps} {...otherProps}
@@ -53,7 +40,7 @@ class PageContentBody extends Component {
<div className={innerClassName}> <div className={innerClassName}>
{children} {children}
</div> </div>
</ScrollerComponent> </Scroller>
); );
} }
} }

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Scrollbars } from 'react-custom-scrollbars'; import { Scrollbars } from 'react-custom-scrollbars-2';
import { scrollDirections } from 'Helpers/Props'; import { scrollDirections } from 'Helpers/Props';
import styles from './OverlayScroller.css'; import styles from './OverlayScroller.css';

View File

@@ -1,7 +1,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Measure from 'Components/Measure'; import Measure from 'Components/Measure';
import { isMobile as isMobileUtil } from 'Utilities/mobile'; import { isMobile as isMobileUtil } from 'Utilities/browser';
import styles from './SwipeHeader.css'; import styles from './SwipeHeader.css';
function cursorPosition(event) { function cursorPosition(event) {

View File

@@ -5,7 +5,7 @@ import { Manager, Popper, Reference } from 'react-popper';
import Portal from 'Components/Portal'; import Portal from 'Components/Portal';
import { kinds, tooltipPositions } from 'Helpers/Props'; import { kinds, tooltipPositions } from 'Helpers/Props';
import dimensions from 'Styles/Variables/dimensions'; import dimensions from 'Styles/Variables/dimensions';
import { isMobile as isMobileUtil } from 'Utilities/mobile'; import { isMobile as isMobileUtil } from 'Utilities/browser';
import styles from './Tooltip.css'; import styles from './Tooltip.css';
let maxWidth = null; let maxWidth = null;

View File

@@ -21,6 +21,7 @@ function HostSettings(props) {
port, port,
urlBase, urlBase,
instanceName, instanceName,
applicationUrl,
enableSsl, enableSsl,
sslPort, sslPort,
sslCertPath, sslCertPath,
@@ -58,6 +59,7 @@ function HostSettings(props) {
name="port" name="port"
min={1} min={1}
max={65535} max={65535}
autocomplete="off"
helpTextWarning={translate('PortHelpTextWarning')} helpTextWarning={translate('PortHelpTextWarning')}
onChange={onInputChange} onChange={onInputChange}
{...port} {...port}
@@ -95,6 +97,21 @@ function HostSettings(props) {
/> />
</FormGroup> </FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('ApplicationURL')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="applicationUrl"
helpText={translate('ApplicationUrlHelpText')}
onChange={onInputChange}
{...applicationUrl}
/>
</FormGroup>
<FormGroup <FormGroup
advancedSettings={advancedSettings} advancedSettings={advancedSettings}
isAdvanced={true} isAdvanced={true}

View File

@@ -19,7 +19,7 @@ function PendingChangesModal(props) {
useEffect(() => { useEffect(() => {
bindShortcut('enter', onConfirm); bindShortcut('enter', onConfirm);
}, [onConfirm]); }, [bindShortcut, onConfirm]);
return ( return (
<Modal <Modal

View File

@@ -6,6 +6,7 @@ import getProviderState from 'Utilities/State/getProviderState';
import { removeItem, set, updateItem } from '../baseActions'; import { removeItem, set, updateItem } from '../baseActions';
const abortCurrentRequests = {}; const abortCurrentRequests = {};
let lastSaveData = null;
export function createCancelSaveProviderHandler(section) { export function createCancelSaveProviderHandler(section) {
return function(getState, payload, dispatch) { return function(getState, payload, dispatch) {
@@ -27,27 +28,33 @@ function createSaveProviderHandler(section, url, options = {}, removeStale = fal
} = payload; } = payload;
const saveData = Array.isArray(id) ? id.map((x) => getProviderState({ id: x, ...otherPayload }, getState, section)) : getProviderState({ id, ...otherPayload }, getState, section); const saveData = Array.isArray(id) ? id.map((x) => getProviderState({ id: x, ...otherPayload }, getState, section)) : getProviderState({ id, ...otherPayload }, getState, section);
const requestUrl = id ? `${url}/${id}` : url;
const params = { ...queryParams };
// If the user is re-saving the same provider without changes
// force it to be saved. Only applies to editing existing providers.
if (id && _.isEqual(saveData, lastSaveData)) {
params.forceSave = true;
}
lastSaveData = saveData;
const ajaxOptions = { const ajaxOptions = {
url: `${url}?${$.param(queryParams, true)}`, url: `${requestUrl}?${$.param(params, true)}`,
method: 'POST', method: id ? 'PUT' : 'POST',
contentType: 'application/json', contentType: 'application/json',
dataType: 'json', dataType: 'json',
data: JSON.stringify(saveData) data: JSON.stringify(saveData)
}; };
if (id) {
ajaxOptions.method = 'PUT';
if (!Array.isArray(id)) {
ajaxOptions.url = `${url}/${id}?${$.param(queryParams, true)}`;
}
}
const { request, abortRequest } = createAjaxRequest(ajaxOptions); const { request, abortRequest } = createAjaxRequest(ajaxOptions);
abortCurrentRequests[section] = abortRequest; abortCurrentRequests[section] = abortRequest;
request.done((data) => { request.done((data) => {
lastSaveData = null;
if (!Array.isArray(data)) { if (!Array.isArray(data)) {
data = [data]; data = [data];
} }

View File

@@ -71,6 +71,11 @@ export const defaultState = {
label: 'Release Group', label: 'Release Group',
isVisible: false isVisible: false
}, },
{
name: 'sourceTitle',
label: 'Source Title',
isVisible: false
},
{ {
name: 'details', name: 'details',
columnLabel: 'Details', columnLabel: 'Details',

View File

@@ -1,4 +1,7 @@
@define-mixin scrollbar { @define-mixin scrollbar {
scrollbar-color: var(--scrollbarBackgroundColor) transparent;
scrollbar-width: thin;
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 10px; width: 10px;
height: 10px; height: 10px;

View File

@@ -10,3 +10,7 @@ export function isMobile() {
export function isIOS() { export function isIOS() {
return mobileDetect.is('iOS'); return mobileDetect.is('iOS');
} }
export function isFirefox() {
return window.navigator.userAgent.toLowerCase().indexOf('firefox/') >= 0;
}

View File

@@ -58,7 +58,7 @@
"react-addons-shallow-compare": "15.6.3", "react-addons-shallow-compare": "15.6.3",
"react-async-script": "1.2.0", "react-async-script": "1.2.0",
"react-autosuggest": "10.1.0", "react-autosuggest": "10.1.0",
"react-custom-scrollbars": "4.2.1", "react-custom-scrollbars-2": "4.5.0",
"react-dnd": "14.0.2", "react-dnd": "14.0.2",
"react-dnd-html5-backend": "14.0.0", "react-dnd-html5-backend": "14.0.0",
"react-dnd-multi-backend": "6.0.2", "react-dnd-multi-backend": "6.0.2",
@@ -109,6 +109,7 @@
"eslint-plugin-filenames": "1.3.2", "eslint-plugin-filenames": "1.3.2",
"eslint-plugin-import": "2.23.4", "eslint-plugin-import": "2.23.4",
"eslint-plugin-react": "7.24.0", "eslint-plugin-react": "7.24.0",
"eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-simple-import-sort": "7.0.0", "eslint-plugin-simple-import-sort": "7.0.0",
"esprint": "3.1.0", "esprint": "3.1.0",
"file-loader": "6.2.0", "file-loader": "6.2.0",

View File

@@ -29,9 +29,9 @@
<PackageVersion Include="MonoTorrent" Version="2.0.6" /> <PackageVersion Include="MonoTorrent" Version="2.0.6" />
<PackageVersion Include="NBuilder" Version="6.1.0" /> <PackageVersion Include="NBuilder" Version="6.1.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.1" /> <PackageVersion Include="Newtonsoft.Json" Version="13.0.1" />
<PackageVersion Include="NLog.Extensions.Logging" Version="1.7.4" /> <PackageVersion Include="NLog.Extensions.Logging" Version="5.1.0" />
<PackageVersion Include="NLog" Version="4.7.14" /> <PackageVersion Include="NLog" Version="5.0.5" />
<PackageVersion Include="NLog.Targets.Syslog" Version="6.0.2" /> <PackageVersion Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageVersion Include="Npgsql" Version="6.0.3" /> <PackageVersion Include="Npgsql" Version="6.0.3" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.2.1" /> <PackageVersion Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageVersion Include="NUnit" Version="3.13.3" /> <PackageVersion Include="NUnit" Version="3.13.3" />

View File

@@ -61,6 +61,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase("https://notifiarr.com/notifier.php: api=1234530f-422f-4aac-b6b3-01233210aaaa&radarr_health_issue_message=Download")] [TestCase("https://notifiarr.com/notifier.php: api=1234530f-422f-4aac-b6b3-01233210aaaa&radarr_health_issue_message=Download")]
[TestCase("/readarr/signalr/messages/negotiate?access_token=1234530f422f4aacb6b301233210aaaa&negotiateVersion=1")] [TestCase("/readarr/signalr/messages/negotiate?access_token=1234530f422f4aacb6b301233210aaaa&negotiateVersion=1")]
[TestCase(@"[Info] MigrationController: *** Migrating Database=readarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;Enlist=False ***")] [TestCase(@"[Info] MigrationController: *** Migrating Database=readarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;Enlist=False ***")]
[TestCase(@"[Info] MigrationController: *** Migrating Database=readarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
// Announce URLs (passkeys) Magnet & Tracker // Announce URLs (passkeys) Magnet & Tracker
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")] [TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")]
@@ -73,17 +74,36 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"tracker"":""https://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui""}")] [TestCase(@"tracker"":""https://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui""}")]
[TestCase(@"tracker"":""http://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui"",""info"":""http://xxx.yyy/info?a=b""")] [TestCase(@"tracker"":""http://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui"",""info"":""http://xxx.yyy/info?a=b""")]
// Webhooks - Notifiarr // Notifiarr
[TestCase(@"https://xxx.yyy/api/v1/notification/readarr/9pr04sg6-0123-3210-imav-eql2tyu8xyui")] [TestCase(@"https://xxx.yyy/api/v1/notification/readarr/9pr04sg6-0123-3210-imav-eql2tyu8xyui")]
// Discord
[TestCase(@"https://discord.com/api/webhooks/mySecret")]
[TestCase(@"https://discord.com/api/webhooks/mySecret/01233210")]
public void should_clean_message(string message) public void should_clean_message(string message)
{ {
var cleansedMessage = CleanseLogMessage.Cleanse(message); var cleansedMessage = CleanseLogMessage.Cleanse(message);
cleansedMessage.Should().NotContain("mySecret"); cleansedMessage.Should().NotContain("mySecret");
cleansedMessage.Should().NotContain("123%@%_@!#^#@");
cleansedMessage.Should().NotContain("01233210"); cleansedMessage.Should().NotContain("01233210");
} }
[TestCase(@"[Info] MigrationController: *** Migrating Database=radarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
public void should_keep_message(string message)
{
var cleansedMessage = CleanseLogMessage.Cleanse(message);
cleansedMessage.Should().NotContain("mySecret");
cleansedMessage.Should().NotContain("123%@%_@!#^#@");
cleansedMessage.Should().NotContain("01233210");
cleansedMessage.Should().Contain("shouldkeep1");
cleansedMessage.Should().Contain("shouldkeep2");
cleansedMessage.Should().Contain("shouldkeep3");
}
//GoodReads //GoodReads
[TestCase(@"{""signatureMethod"": ""hmacSha1"",""signatureTreatment"": ""escaped"",""type"": ""protectedResource"",""method"": ""GET"",""token"": ""mytoken"",""tokenSecret"": ""mytokensecret"",""requestUrl"": ""https://www.goodreads.com/review/list.xml"",""parameters"": { ""_nc"": ""1"", ""v"": ""2"", ""id"": ""999999999"", ""shelf"": ""currently-reading"", ""per_page"": ""200"", ""page"": ""1""}")] [TestCase(@"{""signatureMethod"": ""hmacSha1"",""signatureTreatment"": ""escaped"",""type"": ""protectedResource"",""method"": ""GET"",""token"": ""mytoken"",""tokenSecret"": ""mytokensecret"",""requestUrl"": ""https://www.goodreads.com/review/list.xml"",""parameters"": { ""_nc"": ""1"", ""v"": ""2"", ""id"": ""999999999"", ""shelf"": ""currently-reading"", ""per_page"": ""200"", ""page"": ""1""}")]
[TestCase(@"https://www.goodreads.com/series/311911?key=1234530f422f4aacb6b301233210aaaa&_nc=1&format=xml")] [TestCase(@"https://www.goodreads.com/series/311911?key=1234530f422f4aacb6b301233210aaaa&_nc=1&format=xml")]

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
@@ -24,7 +24,8 @@ namespace NzbDrone.Common.Disk
"/boot", "/boot",
"/lib", "/lib",
"/sbin", "/sbin",
"/proc" "/proc",
"/usr/bin"
}; };
} }
} }

View File

@@ -18,7 +18,7 @@ namespace NzbDrone.Common.Instrumentation
new Regex(@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase), new Regex(@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled), new Regex(@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
new Regex(@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase), new Regex(@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=[?& ;])[^=]*?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase), new Regex(@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Trackers Announce Keys; Designed for Qbit Json; should work for all in theory // Trackers Announce Keys; Designed for Qbit Json; should work for all in theory
new Regex(@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"), new Regex(@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
@@ -51,7 +51,10 @@ namespace NzbDrone.Common.Instrumentation
// Webhooks // Webhooks
// Notifiarr // Notifiarr
new Regex(@"api/v[0-9]/notification/readarr/(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase) new Regex(@"api/v[0-9]/notification/readarr/(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Discord
new Regex(@"discord.com/api/webhooks/((?<secret>[\w-]+)/)?(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase)
}; };
private static readonly Regex CleanseRemoteIPRegex = new Regex(@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled); private static readonly Regex CleanseRemoteIPRegex = new Regex(@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);

View File

@@ -1,6 +1,6 @@
using System.Linq; using System.Collections.Generic;
using System.Linq;
using NLog; using NLog;
using NLog.Fluent;
namespace NzbDrone.Common.Instrumentation.Extensions namespace NzbDrone.Common.Instrumentation.Extensions
{ {
@@ -8,47 +8,46 @@ namespace NzbDrone.Common.Instrumentation.Extensions
{ {
public static readonly Logger SentryLogger = LogManager.GetLogger("Sentry"); public static readonly Logger SentryLogger = LogManager.GetLogger("Sentry");
public static LogBuilder SentryFingerprint(this LogBuilder logBuilder, params string[] fingerprint) public static LogEventBuilder SentryFingerprint(this LogEventBuilder logBuilder, params string[] fingerprint)
{ {
return logBuilder.Property("Sentry", fingerprint); return logBuilder.Property("Sentry", fingerprint);
} }
public static LogBuilder WriteSentryDebug(this LogBuilder logBuilder, params string[] fingerprint) public static LogEventBuilder WriteSentryDebug(this LogEventBuilder logBuilder, params string[] fingerprint)
{ {
return LogSentryMessage(logBuilder, LogLevel.Debug, fingerprint); return LogSentryMessage(logBuilder, LogLevel.Debug, fingerprint);
} }
public static LogBuilder WriteSentryInfo(this LogBuilder logBuilder, params string[] fingerprint) public static LogEventBuilder WriteSentryInfo(this LogEventBuilder logBuilder, params string[] fingerprint)
{ {
return LogSentryMessage(logBuilder, LogLevel.Info, fingerprint); return LogSentryMessage(logBuilder, LogLevel.Info, fingerprint);
} }
public static LogBuilder WriteSentryWarn(this LogBuilder logBuilder, params string[] fingerprint) public static LogEventBuilder WriteSentryWarn(this LogEventBuilder logBuilder, params string[] fingerprint)
{ {
return LogSentryMessage(logBuilder, LogLevel.Warn, fingerprint); return LogSentryMessage(logBuilder, LogLevel.Warn, fingerprint);
} }
public static LogBuilder WriteSentryError(this LogBuilder logBuilder, params string[] fingerprint) public static LogEventBuilder WriteSentryError(this LogEventBuilder logBuilder, params string[] fingerprint)
{ {
return LogSentryMessage(logBuilder, LogLevel.Error, fingerprint); return LogSentryMessage(logBuilder, LogLevel.Error, fingerprint);
} }
private static LogBuilder LogSentryMessage(LogBuilder logBuilder, LogLevel level, string[] fingerprint) private static LogEventBuilder LogSentryMessage(LogEventBuilder logBuilder, LogLevel level, string[] fingerprint)
{ {
SentryLogger.Log(level) SentryLogger.ForLogEvent(level)
.CopyLogEvent(logBuilder.LogEventInfo) .CopyLogEvent(logBuilder.LogEvent)
.SentryFingerprint(fingerprint) .SentryFingerprint(fingerprint)
.Write(); .Log();
return logBuilder.Property("Sentry", null); return logBuilder.Property<string>("Sentry", null);
} }
private static LogBuilder CopyLogEvent(this LogBuilder logBuilder, LogEventInfo logEvent) private static LogEventBuilder CopyLogEvent(this LogEventBuilder logBuilder, LogEventInfo logEvent)
{ {
return logBuilder.LoggerName(logEvent.LoggerName) return logBuilder.TimeStamp(logEvent.TimeStamp)
.TimeStamp(logEvent.TimeStamp)
.Message(logEvent.Message, logEvent.Parameters) .Message(logEvent.Message, logEvent.Parameters)
.Properties(logEvent.Properties.ToDictionary(v => v.Key, v => v.Value)) .Properties(logEvent.Properties.Select(p => new KeyValuePair<string, object>(p.Key.ToString(), p.Value)))
.Exception(logEvent.Exception); .Exception(logEvent.Exception);
} }
} }

View File

@@ -1,13 +1,15 @@
using NLog; using System.Text;
using NLog;
using NLog.Targets; using NLog.Targets;
namespace NzbDrone.Common.Instrumentation namespace NzbDrone.Common.Instrumentation
{ {
public class NzbDroneFileTarget : FileTarget public class NzbDroneFileTarget : FileTarget
{ {
protected override string GetFormattedMessage(LogEventInfo logEvent) protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
{ {
return CleanseLogMessage.Cleanse(Layout.Render(logEvent)); var result = CleanseLogMessage.Cleanse(Layout.Render(logEvent));
target.Append(result);
} }
} }
} }

View File

@@ -34,6 +34,8 @@ namespace NzbDrone.Common.Instrumentation
var appFolderInfo = new AppFolderInfo(startupContext); var appFolderInfo = new AppFolderInfo(startupContext);
RegisterGlobalFilters();
if (Debugger.IsAttached) if (Debugger.IsAttached)
{ {
RegisterDebugger(); RegisterDebugger();
@@ -101,6 +103,16 @@ namespace NzbDrone.Common.Instrumentation
LogManager.Configuration.LoggingRules.Add(loggingRule); LogManager.Configuration.LoggingRules.Add(loggingRule);
} }
private static void RegisterGlobalFilters()
{
LogManager.Setup().LoadConfiguration(c =>
{
c.ForLogger("Microsoft.Hosting.Lifetime*").WriteToNil(LogLevel.Info);
c.ForLogger("System*").WriteToNil(LogLevel.Warn);
c.ForLogger("Microsoft*").WriteToNil(LogLevel.Warn);
});
}
private static void RegisterConsole() private static void RegisterConsole()
{ {
var level = LogLevel.Trace; var level = LogLevel.Trace;

View File

@@ -206,7 +206,11 @@ namespace NzbDrone.Common.Instrumentation.Sentry
if (ex != null) if (ex != null)
{ {
fingerPrint.Add(ex.GetType().FullName); fingerPrint.Add(ex.GetType().FullName);
fingerPrint.Add(ex.TargetSite.ToString()); if (ex.TargetSite != null)
{
fingerPrint.Add(ex.TargetSite.ToString());
}
if (ex.InnerException != null) if (ex.InnerException != null)
{ {
fingerPrint.Add(ex.InnerException.GetType().FullName); fingerPrint.Add(ex.InnerException.GetType().FullName);

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using FizzWare.NBuilder; using FizzWare.NBuilder;
@@ -5,6 +6,7 @@ using FluentAssertions;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.Books; using NzbDrone.Core.Books;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
@@ -244,5 +246,89 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
.Should() .Should()
.BeFalse(); .BeFalse();
} }
[Test]
public void should_return_true_when_repacks_are_not_preferred()
{
Mocker.GetMock<IConfigService>()
.Setup(s => s.DownloadPropersAndRepacks)
.Returns(ProperDownloadTypes.DoNotPrefer);
_trackFiles.Select(c =>
{
c.ReleaseGroup = "";
return c;
}).ToList();
_trackFiles.Select(c =>
{
c.Quality = new QualityModel(Quality.FLAC);
return c;
}).ToList();
var remoteAlbum = Builder<RemoteBook>.CreateNew()
.With(e => e.ParsedBookInfo = _parsedBookInfo)
.With(e => e.Books = _books)
.Build();
Subject.IsSatisfiedBy(remoteAlbum, null).Accepted.Should().BeTrue();
}
[Test]
public void should_return_true_when_repack_but_auto_download_repacks_is_true()
{
Mocker.GetMock<IConfigService>()
.Setup(s => s.DownloadPropersAndRepacks)
.Returns(ProperDownloadTypes.PreferAndUpgrade);
_parsedBookInfo.Quality.Revision.IsRepack = true;
_trackFiles.Select(c =>
{
c.ReleaseGroup = "Readarr";
return c;
}).ToList();
_trackFiles.Select(c =>
{
c.Quality = new QualityModel(Quality.FLAC);
return c;
}).ToList();
var remoteAlbum = Builder<RemoteBook>.CreateNew()
.With(e => e.ParsedBookInfo = _parsedBookInfo)
.With(e => e.Books = _books)
.Build();
Subject.IsSatisfiedBy(remoteAlbum, null).Accepted.Should().BeTrue();
}
[Test]
public void should_return_false_when_repack_but_auto_download_repacks_is_false()
{
Mocker.GetMock<IConfigService>()
.Setup(s => s.DownloadPropersAndRepacks)
.Returns(ProperDownloadTypes.DoNotUpgrade);
_parsedBookInfo.Quality.Revision.IsRepack = true;
_trackFiles.Select(c =>
{
c.ReleaseGroup = "Readarr";
return c;
}).ToList();
_trackFiles.Select(c =>
{
c.Quality = new QualityModel(Quality.FLAC);
return c;
}).ToList();
var remoteAlbum = Builder<RemoteBook>.CreateNew()
.With(e => e.ParsedBookInfo = _parsedBookInfo)
.With(e => e.Books = _books)
.Build();
Subject.IsSatisfiedBy(remoteAlbum, null).Accepted.Should().BeFalse();
}
} }
} }

View File

@@ -25,7 +25,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
.Setup(s => s.GetAuthor(It.IsAny<int>())) .Setup(s => s.GetAuthor(It.IsAny<int>()))
.Returns(_author); .Returns(_author);
Mocker.GetMock<ISearchForNzb>() Mocker.GetMock<ISearchForReleases>()
.Setup(s => s.AuthorSearch(_author.Id, false, true, false)) .Setup(s => s.AuthorSearch(_author.Id, false, true, false))
.Returns(new List<DownloadDecision>()); .Returns(new List<DownloadDecision>());
@@ -45,7 +45,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
Subject.Execute(new AuthorSearchCommand { AuthorId = _author.Id, Trigger = CommandTrigger.Manual }); Subject.Execute(new AuthorSearchCommand { AuthorId = _author.Id, Trigger = CommandTrigger.Manual });
Mocker.GetMock<ISearchForNzb>() Mocker.GetMock<ISearchForReleases>()
.Verify(v => v.AuthorSearch(_author.Id, false, true, false), .Verify(v => v.AuthorSearch(_author.Id, false, true, false),
Times.Exactly(_author.Books.Value.Count(s => s.Monitored))); Times.Exactly(_author.Books.Value.Count(s => s.Monitored)));
} }

View File

@@ -410,6 +410,8 @@ namespace NzbDrone.Core.Configuration
public CertificateValidationType CertificateValidation => public CertificateValidationType CertificateValidation =>
GetValueEnum("CertificateValidation", CertificateValidationType.Enabled); GetValueEnum("CertificateValidation", CertificateValidationType.Enabled);
public string ApplicationUrl => GetValue("ApplicationUrl", string.Empty);
private string GetValue(string key) private string GetValue(string key)
{ {
return GetValue(key, string.Empty); return GetValue(key, string.Empty);

View File

@@ -97,5 +97,6 @@ namespace NzbDrone.Core.Configuration
int BackupRetention { get; } int BackupRetention { get; }
CertificateValidationType CertificateValidation { get; } CertificateValidationType CertificateValidation { get; }
string ApplicationUrl { get; }
} }
} }

View File

@@ -1,9 +1,11 @@
using System; using System;
using NLog; using NLog;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.DecisionEngine.Specifications namespace NzbDrone.Core.DecisionEngine.Specifications
{ {
@@ -11,12 +13,14 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{ {
private readonly IMediaFileService _mediaFileService; private readonly IMediaFileService _mediaFileService;
private readonly UpgradableSpecification _upgradableSpecification; private readonly UpgradableSpecification _upgradableSpecification;
private readonly IConfigService _configService;
private readonly Logger _logger; private readonly Logger _logger;
public RepackSpecification(IMediaFileService mediaFileService, UpgradableSpecification upgradableSpecification, Logger logger) public RepackSpecification(IMediaFileService mediaFileService, UpgradableSpecification upgradableSpecification, IConfigService configService, Logger logger)
{ {
_mediaFileService = mediaFileService; _mediaFileService = mediaFileService;
_upgradableSpecification = upgradableSpecification; _upgradableSpecification = upgradableSpecification;
_configService = configService;
_logger = logger; _logger = logger;
} }
@@ -30,6 +34,14 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return Decision.Accept(); return Decision.Accept();
} }
var downloadPropersAndRepacks = _configService.DownloadPropersAndRepacks;
if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotPrefer)
{
_logger.Debug("Repacks are not preferred, skipping check");
return Decision.Accept();
}
foreach (var book in subject.Books) foreach (var book in subject.Books)
{ {
var releaseGroup = subject.ParsedBookInfo.ReleaseGroup; var releaseGroup = subject.ParsedBookInfo.ReleaseGroup;
@@ -39,6 +51,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{ {
if (_upgradableSpecification.IsRevisionUpgrade(file.Quality, subject.ParsedBookInfo.Quality)) if (_upgradableSpecification.IsRevisionUpgrade(file.Quality, subject.ParsedBookInfo.Quality))
{ {
if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotUpgrade)
{
_logger.Debug("Auto downloading of repacks is disabled");
return Decision.Reject("Repack downloading is disabled");
}
var fileReleaseGroup = file.ReleaseGroup; var fileReleaseGroup = file.ReleaseGroup;
if (fileReleaseGroup.IsNullOrWhiteSpace()) if (fileReleaseGroup.IsNullOrWhiteSpace())

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json; using Newtonsoft.Json;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Core.Download.Clients.Sabnzbd.JsonConverters; using NzbDrone.Core.Download.Clients.Sabnzbd.JsonConverters;
@@ -7,10 +7,14 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
{ {
public class SabnzbdConfig public class SabnzbdConfig
{ {
public SabnzbdConfig()
{
Categories = new List<SabnzbdCategory>();
Servers = new List<object>();
}
public SabnzbdConfigMisc Misc { get; set; } public SabnzbdConfigMisc Misc { get; set; }
public List<SabnzbdCategory> Categories { get; set; } public List<SabnzbdCategory> Categories { get; set; }
public List<object> Servers { get; set; } public List<object> Servers { get; set; }
} }

View File

@@ -115,7 +115,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
public override string Name => "rTorrent"; public override string Name => "rTorrent";
public override ProviderMessage Message => new ProviderMessage($"Readarr will handle automatic removal of torrents based on the current seed criteria in Settings->Indexers. After importing it will also set \"{_imported_view}\" as an rTorrent view, which can be used in rTorrent scripts to customize behavior.", ProviderMessageType.Info); public override ProviderMessage Message => new ProviderMessage($"rTorrent will not pause torrents when they meet the seed criteria. Readarr will handle automatic removal of torrents based on the current seed criteria in Settings->Indexers only when Remove Completed is enabled. After importing it will also set \"{_imported_view}\" as an rTorrent view, which can be used in rTorrent scripts to customize behavior.", ProviderMessageType.Info);
public override IEnumerable<DownloadClientItem> GetItems() public override IEnumerable<DownloadClientItem> GetItems()
{ {

View File

@@ -37,10 +37,10 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)] [FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; } public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to ruTorrent")] [FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to rTorrent")]
public bool UseSsl { get; set; } public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Url Path", Type = FieldType.Textbox, HelpText = "Path to the XMLRPC endpoint, see http(s)://[host]:[port]/[urlPath]. When using ruTorrent this usually is RPC2 or (path to ruTorrent)/plugins/rpc/rpc.php")] [FieldDefinition(3, Label = "Url Path", Type = FieldType.Textbox, HelpText = "Path to the XMLRPC endpoint, see http(s)://[host]:[port]/[urlPath]. This is usually RPC2 or [path to ruTorrent]/plugins/rpc/rpc.php when using ruTorrent.")]
public string UrlBase { get; set; } public string UrlBase { get; set; }
[FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)] [FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
@@ -64,7 +64,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
[FieldDefinition(10, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing books released over 14 days ago")] [FieldDefinition(10, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing books released over 14 days ago")]
public int OlderTvPriority { get; set; } public int OlderTvPriority { get; set; }
[FieldDefinition(11, Label = "Add Stopped", Type = FieldType.Checkbox, HelpText = "Enabling will add torrents and magnets to ruTorrent in a stopped state")] [FieldDefinition(11, Label = "Add Stopped", Type = FieldType.Checkbox, HelpText = "Enabling will add torrents and magnets to rTorrent in a stopped state. This may break magnet files.")]
public bool AddStopped { get; set; } public bool AddStopped { get; set; }
public NzbDroneValidationResult Validate() public NzbDroneValidationResult Validate()

View File

@@ -164,14 +164,14 @@ namespace NzbDrone.Core.Download
} }
else else
{ {
_logger.Debug() _logger.ForDebugEvent()
.Message("No books were just imported, but all books were previously imported, possible issue with download history.") .Message("No books were just imported, but all books were previously imported, possible issue with download history.")
.Property("AuthorId", trackedDownload.RemoteBook.Author.Id) .Property("AuthorId", trackedDownload.RemoteBook.Author.Id)
.Property("DownloadId", trackedDownload.DownloadItem.DownloadId) .Property("DownloadId", trackedDownload.DownloadItem.DownloadId)
.Property("Title", trackedDownload.DownloadItem.Title) .Property("Title", trackedDownload.DownloadItem.Title)
.Property("Path", trackedDownload.DownloadItem.OutputPath.ToString()) .Property("Path", trackedDownload.DownloadItem.OutputPath.ToString())
.WriteSentryWarn("DownloadHistoryIncomplete") .WriteSentryWarn("DownloadHistoryIncomplete")
.Write(); .Log();
} }
trackedDownload.State = TrackedDownloadState.Imported; trackedDownload.State = TrackedDownloadState.Imported;

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Common.Messaging; using NzbDrone.Common.Messaging;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.Download namespace NzbDrone.Core.Download
@@ -13,5 +14,6 @@ namespace NzbDrone.Core.Download
public DownloadClientItemClientInfo DownloadClientInfo { get; set; } public DownloadClientItemClientInfo DownloadClientInfo { get; set; }
public string DownloadId { get; set; } public string DownloadId { get; set; }
public string Message { get; set; } public string Message { get; set; }
public TrackedDownload TrackedDownload { get; set; }
} }
} }

View File

@@ -110,7 +110,7 @@ namespace NzbDrone.Core.Download
bookGrabbedEvent.DownloadId = downloadClientId; bookGrabbedEvent.DownloadId = downloadClientId;
} }
_logger.ProgressInfo("Report sent to {0}. {1}", downloadClient.Definition.Name, downloadTitle); _logger.ProgressInfo("Report sent to {0} from indexer {1}. {2}", downloadClient.Definition.Name, remoteBook.Release.Indexer, downloadTitle);
_eventAggregator.PublishEvent(bookGrabbedEvent); _eventAggregator.PublishEvent(bookGrabbedEvent);
} }
} }

View File

@@ -42,6 +42,7 @@ namespace NzbDrone.Core.Download
SourceTitle = trackedDownload.DownloadItem.Title, SourceTitle = trackedDownload.DownloadItem.Title,
DownloadClientInfo = trackedDownload.DownloadItem.DownloadClientInfo, DownloadClientInfo = trackedDownload.DownloadItem.DownloadClientInfo,
DownloadId = trackedDownload.DownloadItem.DownloadId, DownloadId = trackedDownload.DownloadItem.DownloadId,
TrackedDownload = trackedDownload,
Message = "Manually ignored" Message = "Manually ignored"
}; };

View File

@@ -200,6 +200,7 @@ namespace NzbDrone.Core.History
}; };
history.Data.Add("StatusMessages", message.TrackedDownload.StatusMessages.ToJson()); history.Data.Add("StatusMessages", message.TrackedDownload.StatusMessages.ToJson());
history.Data.Add("ReleaseGroup", message.TrackedDownload?.RemoteBook?.ParsedBookInfo?.ReleaseGroup);
_historyRepository.Insert(history); _historyRepository.Insert(history);
} }
} }
@@ -235,6 +236,7 @@ namespace NzbDrone.Core.History
history.Data.Add("ImportedPath", message.ImportedBook.Path); history.Data.Add("ImportedPath", message.ImportedBook.Path);
history.Data.Add("DownloadClient", message.DownloadClientInfo?.Type); history.Data.Add("DownloadClient", message.DownloadClientInfo?.Type);
history.Data.Add("DownloadClientName", message.DownloadClientInfo?.Name); history.Data.Add("DownloadClientName", message.DownloadClientInfo?.Name);
history.Data.Add("ReleaseGroup", message.BookInfo.ReleaseGroup);
_historyRepository.Insert(history); _historyRepository.Insert(history);
} }
@@ -286,6 +288,7 @@ namespace NzbDrone.Core.History
}; };
history.Data.Add("Reason", message.Reason.ToString()); history.Data.Add("Reason", message.Reason.ToString());
history.Data.Add("ReleaseGroup", message.BookFile.ReleaseGroup);
_historyRepository.Insert(history); _historyRepository.Insert(history);
} }
@@ -307,6 +310,7 @@ namespace NzbDrone.Core.History
history.Data.Add("SourcePath", sourcePath); history.Data.Add("SourcePath", sourcePath);
history.Data.Add("Path", path); history.Data.Add("Path", path);
history.Data.Add("ReleaseGroup", message.BookFile.ReleaseGroup);
_historyRepository.Insert(history); _historyRepository.Insert(history);
} }
@@ -358,6 +362,7 @@ namespace NzbDrone.Core.History
}; };
history.Data.Add("DownloadClient", message.DownloadClientInfo.Name); history.Data.Add("DownloadClient", message.DownloadClientInfo.Name);
history.Data.Add("ReleaseGroup", message.TrackedDownload?.RemoteBook?.ParsedBookInfo?.ReleaseGroup);
history.Data.Add("Message", message.Message); history.Data.Add("Message", message.Message);
historyToAdd.Add(history); historyToAdd.Add(history);

View File

@@ -7,22 +7,22 @@ namespace NzbDrone.Core.IndexerSearch
{ {
public class AuthorSearchService : IExecute<AuthorSearchCommand> public class AuthorSearchService : IExecute<AuthorSearchCommand>
{ {
private readonly ISearchForNzb _nzbSearchService; private readonly ISearchForReleases _releaseSearchService;
private readonly IProcessDownloadDecisions _processDownloadDecisions; private readonly IProcessDownloadDecisions _processDownloadDecisions;
private readonly Logger _logger; private readonly Logger _logger;
public AuthorSearchService(ISearchForNzb nzbSearchService, public AuthorSearchService(ISearchForReleases releaseSearchService,
IProcessDownloadDecisions processDownloadDecisions, IProcessDownloadDecisions processDownloadDecisions,
Logger logger) Logger logger)
{ {
_nzbSearchService = nzbSearchService; _releaseSearchService = releaseSearchService;
_processDownloadDecisions = processDownloadDecisions; _processDownloadDecisions = processDownloadDecisions;
_logger = logger; _logger = logger;
} }
public void Execute(AuthorSearchCommand message) public void Execute(AuthorSearchCommand message)
{ {
var decisions = _nzbSearchService.AuthorSearch(message.AuthorId, false, message.Trigger == CommandTrigger.Manual, false); var decisions = _releaseSearchService.AuthorSearch(message.AuthorId, false, message.Trigger == CommandTrigger.Manual, false);
var processed = _processDownloadDecisions.ProcessDecisions(decisions); var processed = _processDownloadDecisions.ProcessDecisions(decisions);
_logger.ProgressInfo("Author search completed. {0} reports downloaded.", processed.Grabbed.Count); _logger.ProgressInfo("Author search completed. {0} reports downloaded.", processed.Grabbed.Count);

View File

@@ -17,21 +17,21 @@ namespace NzbDrone.Core.IndexerSearch
IExecute<MissingBookSearchCommand>, IExecute<MissingBookSearchCommand>,
IExecute<CutoffUnmetBookSearchCommand> IExecute<CutoffUnmetBookSearchCommand>
{ {
private readonly ISearchForNzb _nzbSearchService; private readonly ISearchForReleases _releaseSearchService;
private readonly IBookService _bookService; private readonly IBookService _bookService;
private readonly IBookCutoffService _bookCutoffService; private readonly IBookCutoffService _bookCutoffService;
private readonly IQueueService _queueService; private readonly IQueueService _queueService;
private readonly IProcessDownloadDecisions _processDownloadDecisions; private readonly IProcessDownloadDecisions _processDownloadDecisions;
private readonly Logger _logger; private readonly Logger _logger;
public BookSearchService(ISearchForNzb nzbSearchService, public BookSearchService(ISearchForReleases releaseSearchService,
IBookService bookService, IBookService bookService,
IBookCutoffService bookCutoffService, IBookCutoffService bookCutoffService,
IQueueService queueService, IQueueService queueService,
IProcessDownloadDecisions processDownloadDecisions, IProcessDownloadDecisions processDownloadDecisions,
Logger logger) Logger logger)
{ {
_nzbSearchService = nzbSearchService; _releaseSearchService = releaseSearchService;
_bookService = bookService; _bookService = bookService;
_bookCutoffService = bookCutoffService; _bookCutoffService = bookCutoffService;
_queueService = queueService; _queueService = queueService;
@@ -47,7 +47,7 @@ namespace NzbDrone.Core.IndexerSearch
foreach (var book in books) foreach (var book in books)
{ {
List<DownloadDecision> decisions; List<DownloadDecision> decisions;
decisions = _nzbSearchService.BookSearch(book.Id, false, userInvokedSearch, false); decisions = _releaseSearchService.BookSearch(book.Id, false, userInvokedSearch, false);
var processed = _processDownloadDecisions.ProcessDecisions(decisions); var processed = _processDownloadDecisions.ProcessDecisions(decisions);
downloadedCount += processed.Grabbed.Count; downloadedCount += processed.Grabbed.Count;
@@ -61,7 +61,7 @@ namespace NzbDrone.Core.IndexerSearch
foreach (var bookId in message.BookIds) foreach (var bookId in message.BookIds)
{ {
var decisions = var decisions =
_nzbSearchService.BookSearch(bookId, false, message.Trigger == CommandTrigger.Manual, false); _releaseSearchService.BookSearch(bookId, false, message.Trigger == CommandTrigger.Manual, false);
var processed = _processDownloadDecisions.ProcessDecisions(decisions); var processed = _processDownloadDecisions.ProcessDecisions(decisions);
_logger.ProgressInfo("Book search completed. {0} reports downloaded.", processed.Grabbed.Count); _logger.ProgressInfo("Book search completed. {0} reports downloaded.", processed.Grabbed.Count);

View File

@@ -14,13 +14,13 @@ using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.IndexerSearch namespace NzbDrone.Core.IndexerSearch
{ {
public interface ISearchForNzb public interface ISearchForReleases
{ {
List<DownloadDecision> BookSearch(int bookId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch); List<DownloadDecision> BookSearch(int bookId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch);
List<DownloadDecision> AuthorSearch(int authorId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch); List<DownloadDecision> AuthorSearch(int authorId, bool missingOnly, bool userInvokedSearch, bool interactiveSearch);
} }
public class NzbSearchService : ISearchForNzb public class ReleaseSearchService : ISearchForReleases
{ {
private readonly IIndexerFactory _indexerFactory; private readonly IIndexerFactory _indexerFactory;
private readonly IBookService _bookService; private readonly IBookService _bookService;
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.IndexerSearch
private readonly IMakeDownloadDecision _makeDownloadDecision; private readonly IMakeDownloadDecision _makeDownloadDecision;
private readonly Logger _logger; private readonly Logger _logger;
public NzbSearchService(IIndexerFactory indexerFactory, public ReleaseSearchService(IIndexerFactory indexerFactory,
IBookService bookService, IBookService bookService,
IAuthorService authorService, IAuthorService authorService,
IMakeDownloadDecision makeDownloadDecision, IMakeDownloadDecision makeDownloadDecision,

View File

@@ -1,17 +0,0 @@
using NzbDrone.Common.Messaging;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers
{
public class IndexerSettingUpdatedEvent : IEvent
{
public string IndexerName { get; private set; }
public IProviderConfig IndexerSetting { get; private set; }
public IndexerSettingUpdatedEvent(string indexerName, IProviderConfig indexerSetting)
{
IndexerName = indexerName;
IndexerSetting = indexerSetting;
}
}
}

View File

@@ -4,6 +4,7 @@ using NzbDrone.Core.Datastore;
using NzbDrone.Core.Download.Clients; using NzbDrone.Core.Download.Clients;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider.Events;
namespace NzbDrone.Core.Indexers namespace NzbDrone.Core.Indexers
{ {
@@ -13,7 +14,7 @@ namespace NzbDrone.Core.Indexers
TorrentSeedConfiguration GetSeedConfiguration(int indexerId, bool fullSeason); TorrentSeedConfiguration GetSeedConfiguration(int indexerId, bool fullSeason);
} }
public class SeedConfigProvider : ISeedConfigProvider, IHandle<IndexerSettingUpdatedEvent> public class SeedConfigProvider : ISeedConfigProvider, IHandle<ProviderUpdatedEvent<IIndexer>>
{ {
private readonly IIndexerFactory _indexerFactory; private readonly IIndexerFactory _indexerFactory;
private readonly ICached<SeedCriteriaSettings> _cache; private readonly ICached<SeedCriteriaSettings> _cache;
@@ -82,7 +83,7 @@ namespace NzbDrone.Core.Indexers
} }
} }
public void Handle(IndexerSettingUpdatedEvent message) public void Handle(ProviderUpdatedEvent<IIndexer> message)
{ {
_cache.Clear(); _cache.Clear();
} }

View File

@@ -117,7 +117,6 @@ namespace NzbDrone.Core.Instrumentation
syslogTarget.MessageSend.Protocol = ProtocolType.Udp; syslogTarget.MessageSend.Protocol = ProtocolType.Udp;
syslogTarget.MessageSend.Udp.Port = syslogPort; syslogTarget.MessageSend.Udp.Port = syslogPort;
syslogTarget.MessageSend.Udp.Server = syslogServer; syslogTarget.MessageSend.Udp.Server = syslogServer;
syslogTarget.MessageSend.Udp.ReconnectInterval = 500;
syslogTarget.MessageCreation.Rfc = RfcNumber.Rfc5424; syslogTarget.MessageCreation.Rfc = RfcNumber.Rfc5424;
syslogTarget.MessageCreation.Rfc5424.AppName = _configFileProvider.InstanceName; syslogTarget.MessageCreation.Rfc5424.AppName = _configFileProvider.InstanceName;

View File

@@ -51,7 +51,7 @@
"Cancel": "zrušení", "Cancel": "zrušení",
"CancelMessageText": "Opravdu chcete zrušit tento nevyřízený úkol?", "CancelMessageText": "Opravdu chcete zrušit tento nevyřízený úkol?",
"CertificateValidation": "Ověření certifikátu", "CertificateValidation": "Ověření certifikátu",
"CertificateValidationHelpText": "Změňte, jak přísné je ověření certifikace HTTPS", "CertificateValidationHelpText": "Změňte přísnost ověřování certifikátů HTTPS. Neměňte, pokud nerozumíte rizikům.",
"ChangeFileDate": "Změnit datum souboru", "ChangeFileDate": "Změnit datum souboru",
"ChangeHasNotBeenSavedYet": "Změna ještě nebyla uložena", "ChangeHasNotBeenSavedYet": "Změna ještě nebyla uložena",
"ChmodFolder": "Složka chmod", "ChmodFolder": "Složka chmod",

View File

@@ -44,7 +44,7 @@
"Cancel": "Abbrechen", "Cancel": "Abbrechen",
"CancelMessageText": "Diese laufende Aufgabe wirklich abbrechen?", "CancelMessageText": "Diese laufende Aufgabe wirklich abbrechen?",
"CertificateValidation": "Zertifikat Validierung", "CertificateValidation": "Zertifikat Validierung",
"CertificateValidationHelpText": "Ändere wie streng die Validierung der HTTPS-Zertifizierung ist. Nicht anpassen, außer du kennst das Risiko.", "CertificateValidationHelpText": "Ändere wie streng die Validierung der HTTPS-Zertifizierung ist. Ändern Sie nicht wenn Ihnen die Risiken nicht bewusst sind.",
"ChangeFileDate": "Erstelldatum der Datei anpassen", "ChangeFileDate": "Erstelldatum der Datei anpassen",
"ChangeHasNotBeenSavedYet": "Änderung wurde noch nicht gespeichert", "ChangeHasNotBeenSavedYet": "Änderung wurde noch nicht gespeichert",
"ChmodFolder": "chmod Ordner", "ChmodFolder": "chmod Ordner",
@@ -291,7 +291,7 @@
"RemoveFromQueue": "Aus der Warteschlage entfernen", "RemoveFromQueue": "Aus der Warteschlage entfernen",
"RemoveHelpTextWarning": "Dies wird den Download und alle bereits heruntergeladenen Dateien aus dem Downloader entfernen.", "RemoveHelpTextWarning": "Dies wird den Download und alle bereits heruntergeladenen Dateien aus dem Downloader entfernen.",
"RemoveSelected": "Auswahl entfernen", "RemoveSelected": "Auswahl entfernen",
"RemoveSelectedMessageText": "Bist du icher, dass du die ausgewählten Einträge aus der Sperrliste entfernen willst?", "RemoveSelectedMessageText": "Sind sie sicher, dass die ausgewählten Einträge aus der Sperrliste entfernt werden sollen?",
"RemoveTagExistingTag": "Vorhandener Tag", "RemoveTagExistingTag": "Vorhandener Tag",
"RemoveTagRemovingTag": "Tag entfernen", "RemoveTagRemovingTag": "Tag entfernen",
"RemovedFromTaskQueue": "Aus der Aufgabenwarteschlage entfernt", "RemovedFromTaskQueue": "Aus der Aufgabenwarteschlage entfernt",
@@ -301,7 +301,7 @@
"RequiredHelpText": "Das Release mus mindesten eines der Begriffe beinhalten ( Groß-/Kleinschreibung wird nicht beachtet )", "RequiredHelpText": "Das Release mus mindesten eines der Begriffe beinhalten ( Groß-/Kleinschreibung wird nicht beachtet )",
"RequiredPlaceHolder": "Neue Beschränkung hinzufügen", "RequiredPlaceHolder": "Neue Beschränkung hinzufügen",
"RescanAfterRefreshHelpTextWarning": "Wenn nicht \"Immer (Always)\" ausgewählt wird, werden Dateiänderungen nicht automatisch erkannt", "RescanAfterRefreshHelpTextWarning": "Wenn nicht \"Immer (Always)\" ausgewählt wird, werden Dateiänderungen nicht automatisch erkannt",
"RescanAuthorFolderAfterRefresh": "Nach dem aktualisieren den Filmordner neu scannen", "RescanAuthorFolderAfterRefresh": "Nach dem Aktualisieren den Autorordner neu scannen",
"Reset": "Zurücksetzen", "Reset": "Zurücksetzen",
"ResetAPIKey": "API-Schlüssel zurücksetzen", "ResetAPIKey": "API-Schlüssel zurücksetzen",
"ResetAPIKeyMessageText": "Bist du sicher, dass du den API-Schlüssel zurücksetzen willst?", "ResetAPIKeyMessageText": "Bist du sicher, dass du den API-Schlüssel zurücksetzen willst?",
@@ -349,7 +349,7 @@
"ShownAboveEachColumnWhenWeekIsTheActiveView": "Wird in der Wochenansicht über jeder Spalte angezeigt", "ShownAboveEachColumnWhenWeekIsTheActiveView": "Wird in der Wochenansicht über jeder Spalte angezeigt",
"Size": " Größe", "Size": " Größe",
"SkipFreeSpaceCheck": "Pürfung des freien Speichers überspringen", "SkipFreeSpaceCheck": "Pürfung des freien Speichers überspringen",
"SkipFreeSpaceCheckWhenImportingHelpText": "Aktiviere dies wenn es nicht möglich ist, den freien Speicherplatz vom Stammverzeichnis zu ermitteln", "SkipFreeSpaceCheckWhenImportingHelpText": "Aktiviere diese Option, wenn es Readarr nicht möglich ist, den freien Speicherplatz des Stammverzeichnises für Autoren zu erkennen",
"SorryThatAuthorCannotBeFound": "Schade, dieser Film kann nicht gefunden werden.", "SorryThatAuthorCannotBeFound": "Schade, dieser Film kann nicht gefunden werden.",
"SorryThatBookCannotBeFound": "Schade, dieser Film kann nicht gefunden werden.", "SorryThatBookCannotBeFound": "Schade, dieser Film kann nicht gefunden werden.",
"Source": "Quelle", "Source": "Quelle",
@@ -426,7 +426,7 @@
"UnmonitoredHelpText": "Nicht beobachtete Filme im iCal-Feed einschließen", "UnmonitoredHelpText": "Nicht beobachtete Filme im iCal-Feed einschließen",
"UpdateAll": "Alle aktualisieren", "UpdateAll": "Alle aktualisieren",
"UpdateAutomaticallyHelpText": "Updates automatisch herunteraden und installieren. Es kann weiterhin unter \"System -> Updates\" ein manuelles Update angestoßen werden", "UpdateAutomaticallyHelpText": "Updates automatisch herunteraden und installieren. Es kann weiterhin unter \"System -> Updates\" ein manuelles Update angestoßen werden",
"UpdateMechanismHelpText": "Benutze den Built-In Updater oder ein Script", "UpdateMechanismHelpText": "Benutze Readarr's Built-In Updater oder ein Script",
"UpdateScriptPathHelpText": "Pfad zu einem benutzerdefinierten Skript, das ein extrahiertes Update-Paket übernimmt und den Rest des Update-Prozesses abwickelt", "UpdateScriptPathHelpText": "Pfad zu einem benutzerdefinierten Skript, das ein extrahiertes Update-Paket übernimmt und den Rest des Update-Prozesses abwickelt",
"Updates": "Updates", "Updates": "Updates",
"UpgradeAllowedHelpText": "Wenn deaktiviert wird die Qualität nicht verbessert", "UpgradeAllowedHelpText": "Wenn deaktiviert wird die Qualität nicht verbessert",
@@ -702,7 +702,7 @@
"ImportListStatusCheckSingleClientMessage": "Listen aufgrund von Fehlern nicht verfügbar: {0}", "ImportListStatusCheckSingleClientMessage": "Listen aufgrund von Fehlern nicht verfügbar: {0}",
"ImportMechanismHealthCheckMessage": "Aktiviere die Verarbeitung der abgeschlossenen Downloads", "ImportMechanismHealthCheckMessage": "Aktiviere die Verarbeitung der abgeschlossenen Downloads",
"IndexerRssHealthCheckNoIndexers": "Da keine Indexer mit aktivierter RSS-Synchronisierung aktiviert sind, erfasst Readarr neue Erscheinungen nicht automatisch", "IndexerRssHealthCheckNoIndexers": "Da keine Indexer mit aktivierter RSS-Synchronisierung aktiviert sind, erfasst Readarr neue Erscheinungen nicht automatisch",
"IndexerSearchCheckNoInteractiveMessage": "Keine Indexer mit interaktiver Suche aktiviert, Readarr liefert keine interaktiven Suchergebnisse", "IndexerSearchCheckNoInteractiveMessage": "Keine Indexer mit interaktiver Suche verfügbar, Readarr liefert keine interaktiven Suchergebnisse",
"IndexerStatusCheckSingleClientMessage": "Indexer aufgrund von Fehlern nicht verfügbar: {0}", "IndexerStatusCheckSingleClientMessage": "Indexer aufgrund von Fehlern nicht verfügbar: {0}",
"ChownGroup": "chown Gruppe", "ChownGroup": "chown Gruppe",
"AllowFingerprintingHelpText": "Benutze Fingerabdrücke um die Genauigkeit der Buch Übereinstimmungen zu verbessern", "AllowFingerprintingHelpText": "Benutze Fingerabdrücke um die Genauigkeit der Buch Übereinstimmungen zu verbessern",
@@ -725,7 +725,7 @@
"RemotePathMappingCheckDockerFolderMissing": "Docker erkannt; Downloader {0} speichert Downloads in {1}, aber dieser Ordner scheint nicht im Container zu existieren. Überprüfe die Remote-Pfadzuordnungen und die Container Volume Einstellungen.", "RemotePathMappingCheckDockerFolderMissing": "Docker erkannt; Downloader {0} speichert Downloads in {1}, aber dieser Ordner scheint nicht im Container zu existieren. Überprüfe die Remote-Pfadzuordnungen und die Container Volume Einstellungen.",
"RemotePathMappingCheckFilesGenericPermissions": "Downloader {0} meldet Dateien in {1}, aber Radarr kann dieses Verzeichnis nicht sehen.Möglicherweise müssen die Verzeichnisreche angepasst werden.", "RemotePathMappingCheckFilesGenericPermissions": "Downloader {0} meldet Dateien in {1}, aber Radarr kann dieses Verzeichnis nicht sehen.Möglicherweise müssen die Verzeichnisreche angepasst werden.",
"RemotePathMappingCheckFolderPermissions": "Radarr kann das Downloadverzeichnis sehen, aber nicht verarbeiten {0}. Möglicherwiese ein Rechteproblem.", "RemotePathMappingCheckFolderPermissions": "Radarr kann das Downloadverzeichnis sehen, aber nicht verarbeiten {0}. Möglicherwiese ein Rechteproblem.",
"RemotePathMappingCheckGenericPermissions": "Downloader {0} speichert Downloads in {1}, aber Radarr kann dieses Verzeichnis nicht sehen. Möglicherweise müssen die Verzeichnisrechte angepasst werden.", "RemotePathMappingCheckGenericPermissions": "Downloader {0} speichert Downloads in {1}, aber Readarr kann dieses Verzeichnis nicht sehen. Möglicherweise müssen die Verzeichnisrechte angepasst werden.",
"RemotePathMappingCheckLocalWrongOSPath": "Downloader {0} speichert Downloads in {1}, aber dies ist kein valider {2} Pfad. Überprüfe die Downloader Einstellungen.", "RemotePathMappingCheckLocalWrongOSPath": "Downloader {0} speichert Downloads in {1}, aber dies ist kein valider {2} Pfad. Überprüfe die Downloader Einstellungen.",
"Monitor": "Beobachten", "Monitor": "Beobachten",
"MusicBrainzAuthorID": "MusicBranz Künstler Id", "MusicBrainzAuthorID": "MusicBranz Künstler Id",
@@ -780,12 +780,12 @@
"InstanceNameHelpText": "Instanzname im Browser-Tab und für Syslog-Anwendungsname", "InstanceNameHelpText": "Instanzname im Browser-Tab und für Syslog-Anwendungsname",
"RestartRequiredHelpTextWarning": "Erfordert einen Neustart", "RestartRequiredHelpTextWarning": "Erfordert einen Neustart",
"UseCalibreContentServer": "Calibre-Content-Server", "UseCalibreContentServer": "Calibre-Content-Server",
"DataExistingBooks": "Beobachte Alben die Dateien haben oder noch nicht veröffentlicht wurden", "DataExistingBooks": "Beobachte Bücher die Dateien haben oder noch nicht veröffentlicht wurden",
"DataFirstBook": "Beobachte die ersten Alben. Alle anderen Alben werden ignoriert", "DataFirstBook": "Beobachte das erste Buch. Alle anderen Bücher werden ignoriert",
"DataFuturebooks": "Überwachung von Alben die noch nicht veröffentlicht wurden", "DataFuturebooks": "Überwachung von Büchern die noch nicht veröffentlicht wurden",
"DataMissingBooks": "Beobachte Alben, die noch keine Dateien haben oder noch nicht veröffentlicht wurden", "DataMissingBooks": "Beobachte Bücher, die noch keine Dateien haben oder noch nicht veröffentlicht wurden",
"Test": "Testen", "Test": "Testen",
"DataNone": "Es werden keine Alben beobachtet", "DataNone": "Es werden keine Bücher beobachtet",
"RenameFiles": "Dateien umbenennen", "RenameFiles": "Dateien umbenennen",
"LoadingEditionsFailed": "Das Laden der Ausgaben ist fehlgeschlagen", "LoadingEditionsFailed": "Das Laden der Ausgaben ist fehlgeschlagen",
"MissingBooksAuthorMonitored": "Fehlende Bücher (Autoren überwacht)", "MissingBooksAuthorMonitored": "Fehlende Bücher (Autoren überwacht)",
@@ -795,7 +795,7 @@
"MediaManagementSettingsSummary": "Namensgebung, Dateimanagement-Einstellungen und Root-Ordner", "MediaManagementSettingsSummary": "Namensgebung, Dateimanagement-Einstellungen und Root-Ordner",
"MinimumPopularity": "Mindestpolularität", "MinimumPopularity": "Mindestpolularität",
"MinPagesHelpText": "Bücher mit weniger Seiten als dieses ignorieren", "MinPagesHelpText": "Bücher mit weniger Seiten als dieses ignorieren",
"MinPopularityHelpText": "Popularität ist Durchschnittsbewertung * Stimmen", "MinPopularityHelpText": "Popularität ist Durchschnittsbewertung * Anzahl der Stimmen",
"MissingBooks": "Fehlende Bücher", "MissingBooks": "Fehlende Bücher",
"MonitorAuthor": "Autor überwachen", "MonitorAuthor": "Autor überwachen",
"MissingBooksAuthorNotMonitored": "Fehlende Bücher (Autor nicht überwacht)", "MissingBooksAuthorNotMonitored": "Fehlende Bücher (Autor nicht überwacht)",
@@ -812,5 +812,70 @@
"DataNewAllBooks": "Alle neuen Bücher überwachen", "DataNewAllBooks": "Alle neuen Bücher überwachen",
"DataNewBooks": "Alle neuen Bücher, welche nach dem neusten existierenden Buch veröffentlicht werden, überwachen", "DataNewBooks": "Alle neuen Bücher, welche nach dem neusten existierenden Buch veröffentlicht werden, überwachen",
"DataNewNone": "Keine neuen Bücher überwachen", "DataNewNone": "Keine neuen Bücher überwachen",
"MassBookSearchWarning": "Sind Sie sicher, dass Sie eine Massensuche für {0} Bücher starten wollen?" "MassBookSearchWarning": "Sind Sie sicher, dass Sie eine Massensuche für {0} Bücher starten wollen?",
"NoTagsHaveBeenAddedYet": "Es wurden bisher noch keine Tags hinzugefügt. Fügen Sie Tags hinzu um Autoren mit Verzögerunsprofilen, Einschränkungen oder Benachrichtigungen zu verknüpfen. Klicken Sie {0} um mehr über Tags in Readarr herauszufinden.",
"OutputFormatHelpText": "Optional kann Calibre aufgefordert werden, beim Import in andere Formate zu konvertieren. Kommagetrennte Liste.",
"UsernameHelpText": "Calibre Inhaltsserver Benutzername",
"MonitorExistingBooks": "Vorhandene Bücher überwachen",
"MonitoringOptionsHelpText": "Welche Bücher sollen überwacht werden nachdem der Autor hinzugefügt wurde (einmalige Anpassung)",
"MonitorNewBooks": "Neue Bücher überwachen",
"MonitorNewItems": "Neue Bücher überwachen",
"MonitorNewItemsHelpText": "Welche neuen Bücher sollen überwacht werden",
"NewBooks": "Neue Bücher",
"NoHistoryBlocklist": "Keine History Blockliste",
"NoName": "Namen nicht anzeigen",
"OnAuthorDelete": "Beim Löschen eines Autors",
"OnAuthorDeleteHelpText": "Beim Löschen eines Autors",
"OnBookDelete": "Beim Löschen eines Buches",
"OnBookRetagHelpText": "Bei neu markieren eines Buches",
"RefreshBook": "Buch aktualisieren",
"SearchBoxPlaceHolder": "z.B Krieg und Frieden, goodreads:656, isbn:067003469X, asin:B00JCDK5ME",
"SendMetadataToCalibre": "Metadaten an Calibre senden",
"SeriesTotal": "Serie ({0})",
"SetReadarrTags": "Setze Readarr Tags",
"ShouldMonitorExisting": "Existierende Bücher überwachen",
"ShouldMonitorHelpText": "Neue Autoren und Bücher aus dieser Liste überwachen",
"ShouldSearchHelpText": "Dursuche Indexer nach neu hinzugefügten Einträgen. Vorsichtig benutzen bei großen Listen.",
"SkipSecondarySeriesBooks": "Überspringe Bücher von Sekundarserien",
"SpecificBook": "Bestimmtes Buch",
"TheFollowingFilesWillBeDeleted": "Die folgenden Dateien werden gelöscht:",
"TooManyBooks": "Fehlende oder zu viele Bücher? Ändern oder erstellen Sie ein neues",
"TrackTitle": "Track Titel",
"UseSslHelpText": "Benutze SSL um mit dem Calibre Inhaltsserver zu verbinden",
"WriteMetadataTags": "Schreibe Metadaten Tags",
"WriteTagsAll": "Alle Dateien; nur erster Import",
"WriteTagsNew": "Nur für neue Downloads",
"WriteTagsSync": "Alle Dateien; mit Goodreads synchronisiert bleiben",
"SearchForAllCutoffUnmetBooks": "Suche nach allen abgeschnittenen unerfüllten Büchern",
"OnBookTagUpdate": "Bei Update eines Buch Tags",
"ProfilesSettingsSummary": "Qualität, Metadaten, Verzögerung und Release Profile",
"RenameBooks": "Bücher umbenennen",
"SearchForNewItems": "Suche nach neuen Einträgen",
"SeriesNumber": "Seriennummer",
"ShouldMonitorExistingHelpText": "Bücher in dieser Liste welche bereits in Readarr sind überwachen",
"SkipBooksWithNoISBNOrASIN": "Überspringe Bücher ohne ISBN oder ASIN",
"StatusEndedDeceased": "Verstorben",
"UpdateCovers": "Covers aktualisieren",
"UpdateCoversHelpText": "Buchcovers in Calibre so einstellen, dass sie mit denen aus Readarr übereinstimmen",
"UrlBaseHelpText": "Fügt ein Prefix zur Calibre Url hinzu, z.B http://[host]:[port]/[urlBasis]",
"UseSSL": "Benutze SSL",
"MonitorBookExistingOnlyWarning": "Dies ist eine einmalige Anpassung der Überwachungseinstellung für jedes Buch. Verwenden Sie die Option unter Autor/Bearbeiten, um festzulegen, was bei neu hinzugefügten Büchern geschieht",
"MonitoredHelpText": "Readarr wird das Buch suchen und herunterladen",
"MonitoredAuthorIsUnmonitored": "Autor wird nicht überwacht",
"MonitoredAuthorIsMonitored": "Autor wird überwacht",
"NameLastFirst": "Nachname, Vorname",
"NameFirstLast": "Vorname Nachname",
"NameStyle": "Autor Namensstil",
"NETCore": ".NET Core",
"ShowLastBook": "Zeige letztes Buch",
"SkipBooksWithMissingReleaseDate": "Überspringe Bücher ohne Erscheinungsdatum",
"Monitoring": "Überwachung",
"OnBookDeleteHelpText": "Beim Löschen eines Buches",
"RefreshAuthor": "Autor aktualisieren",
"RefreshInformation": "Informationen aktualisieren",
"SearchBook": "Buch suchen",
"ShowBookCount": "Zeige Anzahl an Büchern",
"SkipPartBooksAndSets": "Überspringe Teilbücher und Sets",
"TagsHelpText": "Gilt für Autoren mit mindestens einem passenden Tag. Leer lassen, um auf alle Autoren anzuwenden",
"TagsSettingsSummary": "Verwalten von Autoren-, Profil-, Beschränkungs- und Benachrichtigungs-Tags"
} }

View File

@@ -34,6 +34,8 @@
"ApiKeyHelpTextWarning": "Requires restart to take effect", "ApiKeyHelpTextWarning": "Requires restart to take effect",
"AppDataDirectory": "AppData directory", "AppDataDirectory": "AppData directory",
"AppDataLocationHealthCheckMessage": "Updating will not be possible to prevent deleting AppData on Update", "AppDataLocationHealthCheckMessage": "Updating will not be possible to prevent deleting AppData on Update",
"ApplicationURL": "Application URL",
"ApplicationUrlHelpText": "This application's external URL including http(s)://, port and URL base",
"ApplyTags": "Apply Tags", "ApplyTags": "Apply Tags",
"ApplyTagsHelpTexts1": "How to apply tags to the selected author", "ApplyTagsHelpTexts1": "How to apply tags to the selected author",
"ApplyTagsHelpTexts2": "Add: Add the tags the existing list of tags", "ApplyTagsHelpTexts2": "Add: Add the tags the existing list of tags",

View File

@@ -36,7 +36,7 @@
"BackupRetentionHelpText": "Säilytysjaksoa vanhemmat, automaattiset varmuuskopiot poistetaan automaattisesti.", "BackupRetentionHelpText": "Säilytysjaksoa vanhemmat, automaattiset varmuuskopiot poistetaan automaattisesti.",
"Backups": "Varmuuskopiointi", "Backups": "Varmuuskopiointi",
"BindAddress": "Sidososoite", "BindAddress": "Sidososoite",
"BindAddressHelpText": "Toimiva IPv4-osoite tai jokerimerkkinä '*' (tähti) kaikille yhteyksille.", "BindAddressHelpText": "Toimiva IPv4-osoite tai '*' (tähti) kaikille yhteyksille.",
"BindAddressHelpTextWarning": "Käyttöönotto vaatii uudelleenkäynnistyksen.", "BindAddressHelpTextWarning": "Käyttöönotto vaatii uudelleenkäynnistyksen.",
"BookIsDownloading": "Kirjaa ladataan", "BookIsDownloading": "Kirjaa ladataan",
"BookIsDownloadingInterp": "Kirjaa ladataan - {0} % {1}", "BookIsDownloadingInterp": "Kirjaa ladataan - {0} % {1}",
@@ -47,7 +47,7 @@
"Cancel": "Peruuta", "Cancel": "Peruuta",
"CancelMessageText": "Haluatko varmasti perua tämän odottavan tehtävän?", "CancelMessageText": "Haluatko varmasti perua tämän odottavan tehtävän?",
"CertificateValidation": "Varmenteen vahvistus", "CertificateValidation": "Varmenteen vahvistus",
"CertificateValidationHelpText": "Valitse HTTPS-varmenteen vahvistuksen tarkkuus. Älä muuta, jollet ymmärrä tähän liittyviä riskejä.", "CertificateValidationHelpText": "Muuta HTTPS-varmennevahvistuksen tarkkuutta. Älä muuta, jollet ymmärrä tähän liittyviä riskejä.",
"ChangeFileDate": "Muuta tiedoston päiväys", "ChangeFileDate": "Muuta tiedoston päiväys",
"ChangeHasNotBeenSavedYet": "Muutosta ei ole vielä tallennettu", "ChangeHasNotBeenSavedYet": "Muutosta ei ole vielä tallennettu",
"ChmodFolder": "chmod-kansio", "ChmodFolder": "chmod-kansio",
@@ -141,7 +141,7 @@
"Fixed": "Korjattu", "Fixed": "Korjattu",
"Folder": "Kansio", "Folder": "Kansio",
"Folders": "Kansioiden käsittely", "Folders": "Kansioiden käsittely",
"ForMoreInformationOnTheIndividualDownloadClientsClickOnTheInfoButtons": "Lue lisää yksittäisistä lataustyökaluista painamalla 'Lisätietoja'.", "ForMoreInformationOnTheIndividualDownloadClientsClickOnTheInfoButtons": "Lataustyökalukohtaisia tietoja saat painamalla lisätietopainikkeita.",
"ForMoreInformationOnTheIndividualIndexersClickOnTheInfoButtons": "Lue lisää tietolähteestä painamalla 'Lisätietoja'.", "ForMoreInformationOnTheIndividualIndexersClickOnTheInfoButtons": "Lue lisää tietolähteestä painamalla 'Lisätietoja'.",
"ForMoreInformationOnTheIndividualListsClickOnTheInfoButtons": "Lue lisää tuontilistoista painamalla 'Lisätietoja'.", "ForMoreInformationOnTheIndividualListsClickOnTheInfoButtons": "Lue lisää tuontilistoista painamalla 'Lisätietoja'.",
"GeneralSettings": "Yleiset asetukset", "GeneralSettings": "Yleiset asetukset",
@@ -271,7 +271,7 @@
"ReadarrTags": "Radarr-tunnisteet", "ReadarrTags": "Radarr-tunnisteet",
"Real": "Todellinen", "Real": "Todellinen",
"Reason": "Syy", "Reason": "Syy",
"RecycleBinCleanupDaysHelpText": "Älä tyhjennä automaattisesti asettamalla arvoksi '0'.", "RecycleBinCleanupDaysHelpText": "Poista automaattinen tyhjennys käytöstä asettamalla arvoksi '0'.",
"RecycleBinCleanupDaysHelpTextWarning": "Tässä määritettyä aikaa vanhemmat tiedostot poistetaan roskakorista pysyvästi automaattisesti.", "RecycleBinCleanupDaysHelpTextWarning": "Tässä määritettyä aikaa vanhemmat tiedostot poistetaan roskakorista pysyvästi automaattisesti.",
"RecycleBinHelpText": "Pysyvän poiston sijaan kirjatiedostot siirretään tähän kansioon.", "RecycleBinHelpText": "Pysyvän poiston sijaan kirjatiedostot siirretään tähän kansioon.",
"RecyclingBin": "Roskakori", "RecyclingBin": "Roskakori",
@@ -297,7 +297,7 @@
"RemoveTagExistingTag": "Olemassa oleva tunniste", "RemoveTagExistingTag": "Olemassa oleva tunniste",
"RemoveTagRemovingTag": "Tunniste poistetaan", "RemoveTagRemovingTag": "Tunniste poistetaan",
"RemovedFromTaskQueue": "Poistettu tehtäväjonosta", "RemovedFromTaskQueue": "Poistettu tehtäväjonosta",
"RenameBooksHelpText": "Jos uudelleennimeäminen ei ole käytössä, käytetään olemassa olevaa tiedostonimeä.", "RenameBooksHelpText": "Jos uudelleennimeäminen ei ole käytössä, käytetään nykyistä tiedostonimeä.",
"Reorder": "Järjestä uudelleen", "Reorder": "Järjestä uudelleen",
"ReplaceIllegalCharacters": "Korvaa kielletyt merkit", "ReplaceIllegalCharacters": "Korvaa kielletyt merkit",
"RequiredHelpText": "Julkaisun tulee sisältää ainakin yksi näistä termeistä (kirjainkokoa ei huomioida).", "RequiredHelpText": "Julkaisun tulee sisältää ainakin yksi näistä termeistä (kirjainkokoa ei huomioida).",
@@ -349,7 +349,7 @@
"ShownAboveEachColumnWhenWeekIsTheActiveView": "Näkyy jokaisen sarakkeen yläpuolella käytettäessä viikkonäkymää.", "ShownAboveEachColumnWhenWeekIsTheActiveView": "Näkyy jokaisen sarakkeen yläpuolella käytettäessä viikkonäkymää.",
"Size": " Koko", "Size": " Koko",
"SkipFreeSpaceCheck": "Ohita vapaan levytilan tarkistus", "SkipFreeSpaceCheck": "Ohita vapaan levytilan tarkistus",
"SkipFreeSpaceCheckWhenImportingHelpText": "Käytä, kun vapaata tilaa ei tunnisteta kirjailijoidesi pääkansiosta.", "SkipFreeSpaceCheckWhenImportingHelpText": "Käytä, kun vapaata tallennustilaa ei tunnisteta kirjailijoiden juurikansiosta.",
"SorryThatAuthorCannotBeFound": "Valitettavasti kirjailijaa ei löydy.", "SorryThatAuthorCannotBeFound": "Valitettavasti kirjailijaa ei löydy.",
"SorryThatBookCannotBeFound": "Valitettavasti elokuvaa ei löydy.", "SorryThatBookCannotBeFound": "Valitettavasti elokuvaa ei löydy.",
"Source": "Lähdekoodi", "Source": "Lähdekoodi",
@@ -367,7 +367,7 @@
"SuccessMyWorkIsDoneNoFilesToRetag": "Menestys! Työni on valmis, ei nimettäviä tiedostoja.", "SuccessMyWorkIsDoneNoFilesToRetag": "Menestys! Työni on valmis, ei nimettäviä tiedostoja.",
"SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS-syötettä ei ole käytettävissä tälle tietolähteelle", "SupportsRssvalueRSSIsNotSupportedWithThisIndexer": "RSS-syötettä ei ole käytettävissä tälle tietolähteelle",
"SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "Hakemistoa ei tueta tällä hakemistolla", "SupportsSearchvalueSearchIsNotSupportedWithThisIndexer": "Hakemistoa ei tueta tällä hakemistolla",
"SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByReadarr": "Käytetään, kun automaattiset haut suoritetaan käyttöliittymän tai Radarrin kautta", "SupportsSearchvalueWillBeUsedWhenAutomaticSearchesArePerformedViaTheUIOrByReadarr": "Profiilia käytetään automaattihaun yhteydessä, kun haku suoritetaan käyttöliittymästä tai Readarrin toimesta.",
"SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Profiilia käytetään vuorovaikutteisen haun yhteydessä.", "SupportsSearchvalueWillBeUsedWhenInteractiveSearchIsUsed": "Profiilia käytetään vuorovaikutteisen haun yhteydessä.",
"TagIsNotUsedAndCanBeDeleted": "Tunnistetta ei ole määritetty millekään kohteelle, joten sen voi poistaa.", "TagIsNotUsedAndCanBeDeleted": "Tunnistetta ei ole määritetty millekään kohteelle, joten sen voi poistaa.",
"Tags": "Tunnisteet", "Tags": "Tunnisteet",
@@ -588,10 +588,10 @@
"MissingBooks": "Puuttuvat kirjat", "MissingBooks": "Puuttuvat kirjat",
"MissingBooksAuthorMonitored": "Puuttuvat kirjat (kirjailijaa valvotaan)", "MissingBooksAuthorMonitored": "Puuttuvat kirjat (kirjailijaa valvotaan)",
"MissingBooksAuthorNotMonitored": "Puuttuvat kirjat (kirjailijaa ei valvota)", "MissingBooksAuthorNotMonitored": "Puuttuvat kirjat (kirjailijaa ei valvota)",
"MonitorBookExistingOnlyWarning": "Tämä on kirjakohtaisen valvonnan kertaluontoinen määritys. Käytä Kirjailija/Muokkaa-valintaa hallinnoidaksesi mitä uusille kirjalisäyksille tehdään.", "MonitorBookExistingOnlyWarning": "Tämä on kirjakohtaisen valvonnan kertaluontoinen määritys. Määritä Kirjailija/Muokkaa-valinnalla mitä uusille kirjalisäyksille tehdään.",
"MonitorNewItems": "Valvo uusia kirjoja", "MonitorNewItems": "Valvo uusia kirjoja",
"MonitorNewItemsHelpText": "Uusien kirjojen valvontatapa.", "MonitorNewItemsHelpText": "Uusien kirjojen valvontatapa.",
"MonitoringOptionsHelpText": "Kansiosta löydetyille kirjailijoille oletusarvoisesti asetettava kirjojen valvontataso (kertaluontoinen määritys).", "MonitoringOptionsHelpText": "Mitkä kirjat asetetaan valvottaviksi kirjailijan lisäyksen yhteydessä (kertaluontoinen määritys).",
"OutputFormatHelpText": "Voit halutessasi pyytää Calibrea muuntamaan kirjat eri muotoihin tuonnin yhteydessä. Pilkulla eroteltu lista.", "OutputFormatHelpText": "Voit halutessasi pyytää Calibrea muuntamaan kirjat eri muotoihin tuonnin yhteydessä. Pilkulla eroteltu lista.",
"PasswordHelpText": "Calibre-sisältöpalvelimen salasana.", "PasswordHelpText": "Calibre-sisältöpalvelimen salasana.",
"PortHelpText": "Calibre-sisältöpalvelimen portti.", "PortHelpText": "Calibre-sisältöpalvelimen portti.",

View File

@@ -400,11 +400,11 @@ namespace NzbDrone.Core.MediaFiles
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Warn() Logger.ForWarnEvent()
.Exception(ex) .Exception(ex)
.Message($"Tag writing failed for {path}") .Message($"Tag writing failed for {path}")
.WriteSentryWarn("Tag writing failed") .WriteSentryWarn("Tag writing failed")
.Write(); .Log();
} }
finally finally
{ {

View File

@@ -147,11 +147,11 @@ namespace NzbDrone.Core.MediaFiles
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Warn() _logger.ForWarnEvent()
.Exception(ex) .Exception(ex)
.Message($"Tag removal failed for {path}") .Message($"Tag removal failed for {path}")
.WriteSentryWarn("Tag removal failed") .WriteSentryWarn("Tag removal failed")
.Write(); .Log();
} }
finally finally
{ {

View File

@@ -65,10 +65,10 @@ namespace NzbDrone.Core.MediaFiles
} }
else else
{ {
Logger.Debug() Logger.ForDebugEvent()
.Message("Unknown audio format: '{0}'.", string.Join(", ", mediaInfo.AudioFormat)) .Message("Unknown audio format: '{0}'.", string.Join(", ", mediaInfo.AudioFormat))
.WriteSentryWarn("UnknownAudioFormat", mediaInfo.AudioFormat) .WriteSentryWarn("UnknownAudioFormat", mediaInfo.AudioFormat)
.Write(); .Log();
return "Unknown"; return "Unknown";
} }

View File

@@ -9,6 +9,7 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Processes; using NzbDrone.Common.Processes;
using NzbDrone.Common.Serializer; using NzbDrone.Common.Serializer;
using NzbDrone.Core.Books; using NzbDrone.Core.Books;
using NzbDrone.Core.HealthCheck;
using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation; using NzbDrone.Core.Validation;
@@ -205,7 +206,7 @@ namespace NzbDrone.Core.Notifications.CustomScript
var environmentVariables = new StringDictionary(); var environmentVariables = new StringDictionary();
environmentVariables.Add("Readarr_EventType", "HealthIssue"); environmentVariables.Add("Readarr_EventType", "HealthIssue");
environmentVariables.Add("Readarr_Health_Issue_Level", nameof(healthCheck.Type)); environmentVariables.Add("Readarr_Health_Issue_Level", Enum.GetName(typeof(HealthCheckResult), healthCheck.Type));
environmentVariables.Add("Readarr_Health_Issue_Message", healthCheck.Message); environmentVariables.Add("Readarr_Health_Issue_Message", healthCheck.Message);
environmentVariables.Add("Readarr_Health_Issue_Type", healthCheck.Source.Name); environmentVariables.Add("Readarr_Health_Issue_Type", healthCheck.Source.Name);
environmentVariables.Add("Readarr_Health_Issue_Wiki", healthCheck.WikiUrl.ToString() ?? string.Empty); environmentVariables.Add("Readarr_Health_Issue_Wiki", healthCheck.WikiUrl.ToString() ?? string.Empty);

View File

@@ -0,0 +1,59 @@
using System.Collections.Generic;
using FluentValidation.Results;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Notifications.Ntfy
{
public class Ntfy : NotificationBase<NtfySettings>
{
private readonly INtfyProxy _proxy;
public Ntfy(INtfyProxy proxy)
{
_proxy = proxy;
}
public override string Name => "ntfy.sh";
public override string Link => "https://ntfy.sh/";
public override void OnGrab(GrabMessage grabMessage)
{
_proxy.SendNotification(BOOK_GRABBED_TITLE_BRANDED, grabMessage.Message, Settings);
}
public override void OnReleaseImport(BookDownloadMessage message)
{
_proxy.SendNotification(BOOK_DOWNLOADED_TITLE_BRANDED, message.Message, Settings);
}
public override void OnAuthorDelete(AuthorDeleteMessage deleteMessage)
{
_proxy.SendNotification(AUTHOR_DELETED_TITLE, deleteMessage.Message, Settings);
}
public override void OnBookDelete(BookDeleteMessage deleteMessage)
{
_proxy.SendNotification(BOOK_DELETED_TITLE, deleteMessage.Message, Settings);
}
public override void OnBookFileDelete(BookFileDeleteMessage deleteMessage)
{
_proxy.SendNotification(BOOK_FILE_DELETED_TITLE, deleteMessage.Message, Settings);
}
public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck)
{
_proxy.SendNotification(HEALTH_ISSUE_TITLE_BRANDED, healthCheck.Message, Settings);
}
public override ValidationResult Test()
{
var failures = new List<ValidationFailure>();
failures.AddIfNotNull(_proxy.Test(Settings));
return new ValidationResult(failures);
}
}
}

View File

@@ -0,0 +1,18 @@
using System;
using NzbDrone.Common.Exceptions;
namespace NzbDrone.Core.Notifications.Ntfy
{
public class NtfyException : NzbDroneException
{
public NtfyException(string message)
: base(message)
{
}
public NtfyException(string message, Exception innerException, params object[] args)
: base(message, innerException, args)
{
}
}
}

View File

@@ -0,0 +1,11 @@
namespace NzbDrone.Core.Notifications.Ntfy
{
public enum NtfyPriority
{
Min = 1,
Low = 2,
Default = 3,
High = 4,
Max = 5
}
}

View File

@@ -0,0 +1,137 @@
using System;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
namespace NzbDrone.Core.Notifications.Ntfy
{
public interface INtfyProxy
{
void SendNotification(string title, string message, NtfySettings settings);
ValidationFailure Test(NtfySettings settings);
}
public class NtfyProxy : INtfyProxy
{
private const string DEFAULT_PUSH_URL = "https://ntfy.sh";
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
public NtfyProxy(IHttpClient httpClient, Logger logger)
{
_httpClient = httpClient;
_logger = logger;
}
public void SendNotification(string title, string message, NtfySettings settings)
{
var error = false;
var serverUrl = settings.ServerUrl.IsNullOrWhiteSpace() ? NtfyProxy.DEFAULT_PUSH_URL : settings.ServerUrl;
foreach (var topic in settings.Topics)
{
var request = BuildTopicRequest(serverUrl, topic);
try
{
SendNotification(title, message, request, settings);
}
catch (NtfyException ex)
{
_logger.Error(ex, "Unable to send test message to {0}", topic);
error = true;
}
}
if (error)
{
throw new NtfyException("Unable to send Ntfy notifications to all topics");
}
}
private HttpRequestBuilder BuildTopicRequest(string serverUrl, string topic)
{
var trimServerUrl = serverUrl.TrimEnd('/');
var requestBuilder = new HttpRequestBuilder($"{trimServerUrl}/{topic}").Post();
return requestBuilder;
}
public ValidationFailure Test(NtfySettings settings)
{
try
{
const string title = "Radarr - Test Notification";
const string body = "This is a test message from Readarr";
SendNotification(title, body, settings);
}
catch (HttpException ex)
{
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized || ex.Response.StatusCode == HttpStatusCode.Forbidden)
{
_logger.Error(ex, "Authorization is required");
return new ValidationFailure("UserName", "Authorization is required");
}
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("ServerUrl", "Unable to send test message");
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("", "Unable to send test message");
}
return null;
}
private void SendNotification(string title, string message, HttpRequestBuilder requestBuilder, NtfySettings settings)
{
try
{
requestBuilder.Headers.Add("X-Title", title);
requestBuilder.Headers.Add("X-Message", message);
requestBuilder.Headers.Add("X-Priority", settings.Priority.ToString());
if (settings.Tags.Any())
{
requestBuilder.Headers.Add("X-Tags", settings.Tags.Join(","));
}
if (!settings.ClickUrl.IsNullOrWhiteSpace())
{
requestBuilder.Headers.Add("X-Click", settings.ClickUrl);
}
var request = requestBuilder.Build();
if (!settings.UserName.IsNullOrWhiteSpace() && !settings.Password.IsNullOrWhiteSpace())
{
request.Credentials = new BasicNetworkCredential(settings.UserName, settings.Password);
}
_httpClient.Execute(request);
}
catch (HttpException ex)
{
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized || ex.Response.StatusCode == HttpStatusCode.Forbidden)
{
_logger.Error(ex, "Authorization is required");
throw;
}
throw new NtfyException("Unable to send text message: {0}", ex, ex.Message);
}
}
}
}

View File

@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Notifications.Ntfy
{
public class NtfySettingsValidator : AbstractValidator<NtfySettings>
{
public NtfySettingsValidator()
{
RuleFor(c => c.Topics).NotEmpty();
RuleFor(c => c.Priority).InclusiveBetween(1, 5);
RuleFor(c => c.ServerUrl).IsValidUrl().When(c => !c.ServerUrl.IsNullOrWhiteSpace());
RuleFor(c => c.ClickUrl).IsValidUrl().When(c => !c.ClickUrl.IsNullOrWhiteSpace());
RuleFor(c => c.UserName).NotEmpty().When(c => !c.Password.IsNullOrWhiteSpace());
RuleFor(c => c.Password).NotEmpty().When(c => !c.UserName.IsNullOrWhiteSpace());
RuleForEach(c => c.Topics).NotEmpty().Matches("[a-zA-Z0-9_-]+").Must(c => !InvalidTopics.Contains(c)).WithMessage("Invalid topic");
}
private static List<string> InvalidTopics => new List<string> { "announcements", "app", "docs", "settings", "stats", "mytopic-rw", "mytopic-ro", "mytopic-wo" };
}
public class NtfySettings : IProviderConfig
{
private static readonly NtfySettingsValidator Validator = new NtfySettingsValidator();
public NtfySettings()
{
Topics = Array.Empty<string>();
Priority = 3;
}
[FieldDefinition(0, Label = "Server Url", Type = FieldType.Url, HelpLink = "https://ntfy.sh/docs/install/", HelpText = "Leave blank to use public server (https://ntfy.sh)")]
public string ServerUrl { get; set; }
[FieldDefinition(1, Label = "User Name", HelpText = "Optional Authorization", Privacy = PrivacyLevel.UserName)]
public string UserName { get; set; }
[FieldDefinition(2, Label = "Password", Type = FieldType.Password, HelpText = "Optional Password", Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(3, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(NtfyPriority))]
public int Priority { get; set; }
[FieldDefinition(4, Label = "Topics", HelpText = "List of Topics to send notifications to", Type = FieldType.Tag)]
public IEnumerable<string> Topics { get; set; }
[FieldDefinition(5, Label = "Ntfy Tags and Emojis", Type = FieldType.Tag, HelpText = "Optional list of tags or emojis to use", HelpLink = "https://ntfy.sh/docs/emojis/")]
public IEnumerable<string> Tags { get; set; }
[FieldDefinition(6, Label = "Click Url", Type = FieldType.Url, HelpText = "Optional link when user clicks notification")]
public string ClickUrl { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -0,0 +1,58 @@
using System.Collections.Generic;
using FluentValidation.Results;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Notifications.Simplepush
{
public class Simplepush : NotificationBase<SimplepushSettings>
{
private readonly ISimplepushProxy _proxy;
public Simplepush(ISimplepushProxy proxy)
{
_proxy = proxy;
}
public override string Name => "Simplepush";
public override string Link => "https://simplepush.io/";
public override void OnGrab(GrabMessage grabMessage)
{
_proxy.SendNotification(EPISODE_GRABBED_TITLE, grabMessage.Message, Settings);
}
public override void OnDownload(DownloadMessage message)
{
_proxy.SendNotification(EPISODE_DOWNLOADED_TITLE, message.Message, Settings);
}
public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage)
{
_proxy.SendNotification(EPISODE_DELETED_TITLE, deleteMessage.Message, Settings);
}
public override void OnSeriesDelete(SeriesDeleteMessage deleteMessage)
{
_proxy.SendNotification(SERIES_DELETED_TITLE, deleteMessage.Message, Settings);
}
public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck)
{
_proxy.SendNotification(HEALTH_ISSUE_TITLE, healthCheck.Message, Settings);
}
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
{
_proxy.SendNotification(APPLICATION_UPDATE_TITLE, updateMessage.Message, Settings);
}
public override ValidationResult Test()
{
var failures = new List<ValidationFailure>();
failures.AddIfNotNull(_proxy.Test(Settings));
return new ValidationResult(failures);
}
}
}

View File

@@ -0,0 +1,58 @@
using System;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Http;
namespace NzbDrone.Core.Notifications.Simplepush
{
public interface ISimplepushProxy
{
void SendNotification(string title, string message, SimplepushSettings settings);
ValidationFailure Test(SimplepushSettings settings);
}
public class SimplepushProxy : ISimplepushProxy
{
private const string URL = "https://api.simplepush.io/send";
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
public SimplepushProxy(IHttpClient httpClient, Logger logger)
{
_httpClient = httpClient;
_logger = logger;
}
public void SendNotification(string title, string message, SimplepushSettings settings)
{
var requestBuilder = new HttpRequestBuilder(URL).Post();
requestBuilder.AddFormParameter("key", settings.Key)
.AddFormParameter("event", settings.Event)
.AddFormParameter("title", title)
.AddFormParameter("msg", message);
var request = requestBuilder.Build();
_httpClient.Post(request);
}
public ValidationFailure Test(SimplepushSettings settings)
{
try
{
const string title = "Test Notification";
const string body = "This is a test message from Sonarr";
SendNotification(title, body, settings);
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("ApiKey", "Unable to send test message");
}
return null;
}
}
}

View File

@@ -0,0 +1,33 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Notifications.Simplepush
{
public class SimplepushSettingsValidator : AbstractValidator<SimplepushSettings>
{
public SimplepushSettingsValidator()
{
RuleFor(c => c.Key).NotEmpty();
}
}
public class SimplepushSettings : IProviderConfig
{
private static readonly SimplepushSettingsValidator Validator = new SimplepushSettingsValidator();
[FieldDefinition(0, Label = "Key", Privacy = PrivacyLevel.ApiKey, HelpLink = "https://simplepush.io/features")]
public string Key { get; set; }
[FieldDefinition(1, Label = "Event", HelpText = "Customize the behavior of push notifications", HelpLink = "https://simplepush.io/features")]
public string Event { get; set; }
public bool IsValid => !string.IsNullOrWhiteSpace(Key);
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -1,6 +1,10 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using FluentValidation; using FluentValidation;
using FluentValidation.Validators; using FluentValidation.Validators;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Organizer namespace NzbDrone.Core.Organizer
{ {
@@ -12,12 +16,16 @@ namespace NzbDrone.Core.Organizer
public static IRuleBuilderOptions<T, string> ValidBookFormat<T>(this IRuleBuilder<T, string> ruleBuilder) public static IRuleBuilderOptions<T, string> ValidBookFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
{ {
ruleBuilder.SetValidator(new NotEmptyValidator(null)); ruleBuilder.SetValidator(new NotEmptyValidator(null));
ruleBuilder.SetValidator(new IllegalCharactersValidator());
return ruleBuilder.SetValidator(new ValidStandardTrackFormatValidator()); return ruleBuilder.SetValidator(new ValidStandardTrackFormatValidator());
} }
public static IRuleBuilderOptions<T, string> ValidAuthorFolderFormat<T>(this IRuleBuilder<T, string> ruleBuilder) public static IRuleBuilderOptions<T, string> ValidAuthorFolderFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
{ {
ruleBuilder.SetValidator(new NotEmptyValidator(null)); ruleBuilder.SetValidator(new NotEmptyValidator(null));
ruleBuilder.SetValidator(new IllegalCharactersValidator());
return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.AuthorNameRegex)).WithMessage("Must contain Author name"); return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.AuthorNameRegex)).WithMessage("Must contain Author name");
} }
} }
@@ -42,4 +50,41 @@ namespace NzbDrone.Core.Organizer
return true; return true;
} }
} }
public class IllegalCharactersValidator : PropertyValidator
{
private readonly char[] _invalidPathChars = Path.GetInvalidPathChars();
public IllegalCharactersValidator()
: base("Contains illegal characters: {InvalidCharacters}")
{
}
protected override bool IsValid(PropertyValidatorContext context)
{
var value = context.PropertyValue as string;
var invalidCharacters = new List<char>();
if (value.IsNullOrWhiteSpace())
{
return true;
}
foreach (var i in _invalidPathChars)
{
if (value.IndexOf(i) >= 0)
{
invalidCharacters.Add(i);
}
}
if (invalidCharacters.Any())
{
context.MessageFormatter.AppendArgument("InvalidCharacters", string.Join("", invalidCharacters));
return false;
}
return true;
}
}
} }

View File

@@ -9,7 +9,7 @@ namespace NzbDrone.Core.Validation.Paths
private readonly IAppFolderInfo _appFolderInfo; private readonly IAppFolderInfo _appFolderInfo;
public StartupFolderValidator(IAppFolderInfo appFolderInfo) public StartupFolderValidator(IAppFolderInfo appFolderInfo)
: base("Path cannot be an ancestor of the start up folder") : base("Path cannot be {relationship} the start up folder")
{ {
_appFolderInfo = appFolderInfo; _appFolderInfo = appFolderInfo;
} }
@@ -21,7 +21,24 @@ namespace NzbDrone.Core.Validation.Paths
return true; return true;
} }
return !_appFolderInfo.StartUpFolder.IsParentPath(context.PropertyValue.ToString()); var startupFolder = _appFolderInfo.StartUpFolder;
var folder = context.PropertyValue.ToString();
if (startupFolder.PathEquals(folder))
{
context.MessageFormatter.AppendArgument("relationship", "set to");
return false;
}
if (startupFolder.IsParentPath(folder))
{
context.MessageFormatter.AppendArgument("relationship", "child of");
return false;
}
return true;
} }
} }
} }

View File

@@ -17,6 +17,8 @@ using NzbDrone.Common.Serializer;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.Instrumentation; using NzbDrone.Core.Instrumentation;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Host.AccessControl; using NzbDrone.Host.AccessControl;
using NzbDrone.Http.Authentication; using NzbDrone.Http.Authentication;
using NzbDrone.SignalR; using NzbDrone.SignalR;
@@ -132,6 +134,7 @@ namespace NzbDrone.Host
IConfigFileProvider configFileProvider, IConfigFileProvider configFileProvider,
IRuntimeInfo runtimeInfo, IRuntimeInfo runtimeInfo,
IFirewallAdapter firewallAdapter, IFirewallAdapter firewallAdapter,
IEventAggregator eventAggregator,
ReadarrErrorPipeline errorHandler) ReadarrErrorPipeline errorHandler)
{ {
initializeLogger.Initialize(); initializeLogger.Initialize();
@@ -154,6 +157,8 @@ namespace NzbDrone.Host
Console.CancelKeyPress += (sender, eventArgs) => NLog.LogManager.Configuration = null; Console.CancelKeyPress += (sender, eventArgs) => NLog.LogManager.Configuration = null;
} }
eventAggregator.PublishEvent(new ApplicationStartingEvent());
if (OsInfo.IsWindows && runtimeInfo.IsAdmin) if (OsInfo.IsWindows && runtimeInfo.IsAdmin)
{ {
firewallAdapter.MakeAccessible(); firewallAdapter.MakeAccessible();

View File

@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net6.0</TargetFrameworks>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="NLog" /> <PackageReference Include="NLog" />

View File

@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net6.0</TargetFrameworks>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="NLog" /> <PackageReference Include="NLog" />

View File

@@ -26,6 +26,7 @@ namespace Readarr.Api.V1.Config
public string SslCertPassword { get; set; } public string SslCertPassword { get; set; }
public string UrlBase { get; set; } public string UrlBase { get; set; }
public string InstanceName { get; set; } public string InstanceName { get; set; }
public string ApplicationUrl { get; set; }
public bool UpdateAutomatically { get; set; } public bool UpdateAutomatically { get; set; }
public UpdateMechanism UpdateMechanism { get; set; } public UpdateMechanism UpdateMechanism { get; set; }
public string UpdateScriptPath { get; set; } public string UpdateScriptPath { get; set; }
@@ -82,7 +83,8 @@ namespace Readarr.Api.V1.Config
CertificateValidation = configService.CertificateValidation, CertificateValidation = configService.CertificateValidation,
BackupFolder = configService.BackupFolder, BackupFolder = configService.BackupFolder,
BackupInterval = configService.BackupInterval, BackupInterval = configService.BackupInterval,
BackupRetention = configService.BackupRetention BackupRetention = configService.BackupRetention,
ApplicationUrl = configService.ApplicationUrl
}; };
} }
} }

View File

@@ -12,15 +12,5 @@ namespace Readarr.Api.V1.DownloadClient
: base(downloadClientFactory, "downloadclient", ResourceMapper) : base(downloadClientFactory, "downloadclient", ResourceMapper)
{ {
} }
protected override void Validate(DownloadClientDefinition definition, bool includeWarnings)
{
if (!definition.Enable)
{
return;
}
base.Validate(definition, includeWarnings);
}
} }
} }

View File

@@ -22,15 +22,5 @@ namespace Readarr.Api.V1.ImportLists
SharedValidator.RuleFor(c => c.QualityProfileId).SetValidator(qualityProfileExistsValidator); SharedValidator.RuleFor(c => c.QualityProfileId).SetValidator(qualityProfileExistsValidator);
SharedValidator.RuleFor(c => c.MetadataProfileId).SetValidator(metadataProfileExistsValidator); SharedValidator.RuleFor(c => c.MetadataProfileId).SetValidator(metadataProfileExistsValidator);
} }
protected override void Validate(ImportListDefinition definition, bool includeWarnings)
{
if (!definition.Enable)
{
return;
}
base.Validate(definition, includeWarnings);
}
} }
} }

View File

@@ -12,15 +12,5 @@ namespace Readarr.Api.V1.Indexers
: base(indexerFactory, "indexer", ResourceMapper) : base(indexerFactory, "indexer", ResourceMapper)
{ {
} }
protected override void Validate(IndexerDefinition definition, bool includeWarnings)
{
if (!definition.Enable)
{
return;
}
base.Validate(definition, includeWarnings);
}
} }
} }

View File

@@ -24,7 +24,7 @@ namespace Readarr.Api.V1.Indexers
public class ReleaseController : ReleaseControllerBase public class ReleaseController : ReleaseControllerBase
{ {
private readonly IFetchAndParseRss _rssFetcherAndParser; private readonly IFetchAndParseRss _rssFetcherAndParser;
private readonly ISearchForNzb _nzbSearchService; private readonly ISearchForReleases _releaseSearchService;
private readonly IMakeDownloadDecision _downloadDecisionMaker; private readonly IMakeDownloadDecision _downloadDecisionMaker;
private readonly IPrioritizeDownloadDecision _prioritizeDownloadDecision; private readonly IPrioritizeDownloadDecision _prioritizeDownloadDecision;
private readonly IDownloadService _downloadService; private readonly IDownloadService _downloadService;
@@ -36,7 +36,7 @@ namespace Readarr.Api.V1.Indexers
private readonly ICached<RemoteBook> _remoteBookCache; private readonly ICached<RemoteBook> _remoteBookCache;
public ReleaseController(IFetchAndParseRss rssFetcherAndParser, public ReleaseController(IFetchAndParseRss rssFetcherAndParser,
ISearchForNzb nzbSearchService, ISearchForReleases releaseSearchService,
IMakeDownloadDecision downloadDecisionMaker, IMakeDownloadDecision downloadDecisionMaker,
IPrioritizeDownloadDecision prioritizeDownloadDecision, IPrioritizeDownloadDecision prioritizeDownloadDecision,
IDownloadService downloadService, IDownloadService downloadService,
@@ -47,7 +47,7 @@ namespace Readarr.Api.V1.Indexers
Logger logger) Logger logger)
{ {
_rssFetcherAndParser = rssFetcherAndParser; _rssFetcherAndParser = rssFetcherAndParser;
_nzbSearchService = nzbSearchService; _releaseSearchService = releaseSearchService;
_downloadDecisionMaker = downloadDecisionMaker; _downloadDecisionMaker = downloadDecisionMaker;
_prioritizeDownloadDecision = prioritizeDownloadDecision; _prioritizeDownloadDecision = prioritizeDownloadDecision;
_downloadService = downloadService; _downloadService = downloadService;
@@ -155,7 +155,7 @@ namespace Readarr.Api.V1.Indexers
{ {
try try
{ {
var decisions = _nzbSearchService.BookSearch(bookId, true, true, true); var decisions = _releaseSearchService.BookSearch(bookId, true, true, true);
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions); var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions);
return MapDecisions(prioritizedDecisions); return MapDecisions(prioritizedDecisions);
@@ -171,7 +171,7 @@ namespace Readarr.Api.V1.Indexers
{ {
try try
{ {
var decisions = _nzbSearchService.AuthorSearch(authorId, false, true, true); var decisions = _releaseSearchService.AuthorSearch(authorId, false, true, true);
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions); var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions);
return MapDecisions(prioritizedDecisions); return MapDecisions(prioritizedDecisions);

View File

@@ -12,15 +12,5 @@ namespace Readarr.Api.V1.Metadata
: base(metadataFactory, "metadata", ResourceMapper) : base(metadataFactory, "metadata", ResourceMapper)
{ {
} }
protected override void Validate(MetadataDefinition definition, bool includeWarnings)
{
if (!definition.Enable)
{
return;
}
base.Validate(definition, includeWarnings);
}
} }
} }

View File

@@ -12,15 +12,5 @@ namespace Readarr.Api.V1.Notifications
: base(notificationFactory, "notification", ResourceMapper) : base(notificationFactory, "notification", ResourceMapper)
{ {
} }
protected override void Validate(NotificationDefinition definition, bool includeWarnings)
{
if (!definition.Enable)
{
return;
}
base.Validate(definition, includeWarnings);
}
} }
} }

View File

@@ -7,6 +7,7 @@ using NzbDrone.Common.Serializer;
using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation; using NzbDrone.Core.Validation;
using NzbDrone.Http.REST.Attributes; using NzbDrone.Http.REST.Attributes;
using Readarr.Http.Extensions;
using Readarr.Http.REST; using Readarr.Http.REST;
namespace Readarr.Api.V1 namespace Readarr.Api.V1
@@ -60,7 +61,7 @@ namespace Readarr.Api.V1
[RestPostById] [RestPostById]
public ActionResult<TProviderResource> CreateProvider(TProviderResource providerResource) public ActionResult<TProviderResource> CreateProvider(TProviderResource providerResource)
{ {
var providerDefinition = GetDefinition(providerResource, false); var providerDefinition = GetDefinition(providerResource, true, false, false);
if (providerDefinition.Enable) if (providerDefinition.Enable)
{ {
@@ -75,11 +76,11 @@ namespace Readarr.Api.V1
[RestPutById] [RestPutById]
public ActionResult<TProviderResource> UpdateProvider(TProviderResource providerResource) public ActionResult<TProviderResource> UpdateProvider(TProviderResource providerResource)
{ {
var providerDefinition = GetDefinition(providerResource, false); var providerDefinition = GetDefinition(providerResource, true, false, false);
var existingDefinition = _providerFactory.Get(providerDefinition.Id); var forceSave = Request.GetBooleanQueryParameter("forceSave");
// Only test existing definitions if it was previously disabled // Only test existing definitions if it is enabled and forceSave isn't set.
if (providerDefinition.Enable && !existingDefinition.Enable) if (providerDefinition.Enable && !forceSave)
{ {
Test(providerDefinition, false); Test(providerDefinition, false);
} }
@@ -89,11 +90,11 @@ namespace Readarr.Api.V1
return Accepted(providerResource.Id); return Accepted(providerResource.Id);
} }
private TProviderDefinition GetDefinition(TProviderResource providerResource, bool includeWarnings = false, bool validate = true) private TProviderDefinition GetDefinition(TProviderResource providerResource, bool validate, bool includeWarnings, bool forceValidate)
{ {
var definition = _resourceMapper.ToModel(providerResource); var definition = _resourceMapper.ToModel(providerResource);
if (validate) if (validate && (definition.Enable || forceValidate))
{ {
Validate(definition, includeWarnings); Validate(definition, includeWarnings);
} }
@@ -113,7 +114,7 @@ namespace Readarr.Api.V1
{ {
var defaultDefinitions = _providerFactory.GetDefaultDefinitions().OrderBy(p => p.ImplementationName).ToList(); var defaultDefinitions = _providerFactory.GetDefaultDefinitions().OrderBy(p => p.ImplementationName).ToList();
var result = new List<TProviderResource>(defaultDefinitions.Count()); var result = new List<TProviderResource>(defaultDefinitions.Count);
foreach (var providerDefinition in defaultDefinitions) foreach (var providerDefinition in defaultDefinitions)
{ {
@@ -134,7 +135,7 @@ namespace Readarr.Api.V1
[HttpPost("test")] [HttpPost("test")]
public object Test([FromBody] TProviderResource providerResource) public object Test([FromBody] TProviderResource providerResource)
{ {
var providerDefinition = GetDefinition(providerResource, true); var providerDefinition = GetDefinition(providerResource, true, true, true);
Test(providerDefinition, true); Test(providerDefinition, true);
@@ -167,7 +168,7 @@ namespace Readarr.Api.V1
[HttpPost("action/{name}")] [HttpPost("action/{name}")]
public IActionResult RequestAction(string name, [FromBody] TProviderResource resource) public IActionResult RequestAction(string name, [FromBody] TProviderResource resource)
{ {
var providerDefinition = GetDefinition(resource, true, false); var providerDefinition = GetDefinition(resource, false, false, false);
var query = Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString()); var query = Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString());
@@ -176,7 +177,7 @@ namespace Readarr.Api.V1
return Content(data.ToJson(), "application/json"); return Content(data.ToJson(), "application/json");
} }
protected virtual void Validate(TProviderDefinition definition, bool includeWarnings) private void Validate(TProviderDefinition definition, bool includeWarnings)
{ {
var validationResult = definition.Settings.Validate(); var validationResult = definition.Settings.Validate();

View File

@@ -176,7 +176,7 @@ namespace Readarr.Api.V1.Queue
case "status": case "status":
return q => q.Status; return q => q.Status;
case "authors.sortName": case "authors.sortName":
return q => q.Author?.Metadata.Value.SortName ?? string.Empty; return q => q.Author?.Metadata.Value.SortName ?? q.Title;
case "authors.sortNameLastFirst": case "authors.sortNameLastFirst":
return q => q.Author?.Metadata.Value.SortNameLastFirst ?? string.Empty; return q => q.Author?.Metadata.Value.SortNameLastFirst ?? string.Empty;
case "title": case "title":

View File

@@ -0,0 +1,23 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
namespace Readarr.Http
{
public class ApiInfoController : Controller
{
public ApiInfoController()
{
}
[HttpGet("/api")]
[Produces("application/json")]
public ApiInfoResource GetApiInfo()
{
return new ApiInfoResource
{
Current = "v1",
Deprecated = new List<string>()
};
}
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Readarr.Http
{
public class ApiInfoResource
{
public string Current { get; set; }
public List<string> Deprecated { get; set; }
}
}

View File

@@ -3277,6 +3277,11 @@ eslint-plugin-import@2.23.4:
resolve "^1.20.0" resolve "^1.20.0"
tsconfig-paths "^3.9.0" tsconfig-paths "^3.9.0"
eslint-plugin-react-hooks@4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3"
integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==
eslint-plugin-react@7.24.0: eslint-plugin-react@7.24.0:
version "7.24.0" version "7.24.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.24.0.tgz#eadedfa351a6f36b490aa17f4fa9b14e842b9eb4" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.24.0.tgz#eadedfa351a6f36b490aa17f4fa9b14e842b9eb4"
@@ -6051,10 +6056,10 @@ react-clientside-effect@^1.2.5:
dependencies: dependencies:
"@babel/runtime" "^7.12.13" "@babel/runtime" "^7.12.13"
react-custom-scrollbars@4.2.1: react-custom-scrollbars-2@4.5.0:
version "4.2.1" version "4.5.0"
resolved "https://registry.yarnpkg.com/react-custom-scrollbars/-/react-custom-scrollbars-4.2.1.tgz#830fd9502927e97e8a78c2086813899b2a8b66db" resolved "https://registry.yarnpkg.com/react-custom-scrollbars-2/-/react-custom-scrollbars-2-4.5.0.tgz#cff18e7368bce9d570aea0be780045eda392c745"
integrity sha1-gw/ZUCkn6X6KeMIIaBOJmyqLZts= integrity sha512-/z0nWAeXfMDr4+OXReTpYd1Atq9kkn4oI3qxq3iMXGQx1EEfwETSqB8HTAvg1X7dEqcCachbny1DRNGlqX5bDQ==
dependencies: dependencies:
dom-css "^2.0.0" dom-css "^2.0.0"
prop-types "^15.5.10" prop-types "^15.5.10"