mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-03-05 13:40:08 -05:00
Compare commits
104 Commits
v1.5.0.339
...
v1.6.1.356
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a891d07cf | ||
|
|
40a932cd28 | ||
|
|
4a81630073 | ||
|
|
0ff0fe2e68 | ||
|
|
51e33740b0 | ||
|
|
119164f729 | ||
|
|
ef0f8e25fd | ||
|
|
d21debe77f | ||
|
|
a3ccc3d0cf | ||
|
|
46d930e903 | ||
|
|
4561859c2b | ||
|
|
83166fb0b5 | ||
|
|
b98f9a945d | ||
|
|
e658e3fe48 | ||
|
|
9042525f22 | ||
|
|
7b551a0af1 | ||
|
|
31c2917bad | ||
|
|
419cce53f7 | ||
|
|
48cd1d9f6b | ||
|
|
8bd6a313b7 | ||
|
|
7cb465787e | ||
|
|
0b610ff9c8 | ||
|
|
5187460298 | ||
|
|
f0d9b43480 | ||
|
|
a1081cc554 | ||
|
|
c4bb1ba69a | ||
|
|
3a4c8db98c | ||
|
|
a522796798 | ||
|
|
e012eda0cf | ||
|
|
72ab2b34c4 | ||
|
|
aaba5b7499 | ||
|
|
455b76c45c | ||
|
|
596d3297da | ||
|
|
d05128ca33 | ||
|
|
f5b57db753 | ||
|
|
f7d7cca982 | ||
|
|
7c5409383e | ||
|
|
98db8f8bf8 | ||
|
|
88e793d76d | ||
|
|
0f31af6b89 | ||
|
|
65adf30f59 | ||
|
|
da75519524 | ||
|
|
ed1fb58242 | ||
|
|
d5daf6791c | ||
|
|
1f1a345d25 | ||
|
|
76a2f51533 | ||
|
|
8c0bc9ab4e | ||
|
|
b0c2b9119b | ||
|
|
87fdf17926 | ||
|
|
0f1b466a19 | ||
|
|
ea635e685b | ||
|
|
73f23d56dc | ||
|
|
f14ccebf3a | ||
|
|
9539e4d481 | ||
|
|
e40ccc49ad | ||
|
|
9fd3eb4d6b | ||
|
|
78aab80703 | ||
|
|
868394d588 | ||
|
|
d5e5697db8 | ||
|
|
d1e39f206a | ||
|
|
b59d89f308 | ||
|
|
bf5855beb4 | ||
|
|
2d36adf865 | ||
|
|
ef1ad59f59 | ||
|
|
59b6e8af27 | ||
|
|
3ae1917d3b | ||
|
|
5864a090e4 | ||
|
|
fcfec1b859 | ||
|
|
65541017dd | ||
|
|
7fe9942c28 | ||
|
|
360827708f | ||
|
|
0509335387 | ||
|
|
f54212a809 | ||
|
|
ea0eb2efa7 | ||
|
|
ce430433e5 | ||
|
|
5437aac346 | ||
|
|
b02188acf4 | ||
|
|
6897ed0b3f | ||
|
|
b3ddf2f9cd | ||
|
|
d9ce9eb0b2 | ||
|
|
29ab1801db | ||
|
|
19ff73dad0 | ||
|
|
c455f1a113 | ||
|
|
b8793d8783 | ||
|
|
ce34940287 | ||
|
|
dcb19a66b0 | ||
|
|
b3bc92e60e | ||
|
|
1b17d38564 | ||
|
|
d8c7361205 | ||
|
|
7a0dd0bc0d | ||
|
|
c02bfb5930 | ||
|
|
d0fbb1f49a | ||
|
|
aafdefe2f0 | ||
|
|
96234c0fe1 | ||
|
|
8b5648d7bd | ||
|
|
1fc79f9e9b | ||
|
|
ec40761757 | ||
|
|
0a8e4eb092 | ||
|
|
ade961fad5 | ||
|
|
81b1c0e445 | ||
|
|
0fe54ed36a | ||
|
|
337828ff9c | ||
|
|
fb34294d2e | ||
|
|
931e3cf42d |
@@ -36,12 +36,18 @@ dotnet_naming_style.instance_field_style.capitalization = camel_case
|
||||
dotnet_naming_style.instance_field_style.required_prefix = _
|
||||
|
||||
# Prefer "var" everywhere
|
||||
csharp_style_var_for_built_in_types = true:suggestion
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
csharp_style_var_for_built_in_types = true
|
||||
csharp_style_var_when_type_is_apparent = true
|
||||
csharp_style_var_elsewhere = true
|
||||
# Prefer "out" variables to be declared inline
|
||||
csharp_style_inlined_variable_declaration = true
|
||||
|
||||
# Using directive is unnecessary.
|
||||
dotnet_diagnostic.IDE0005.severity = error
|
||||
# Use var instead of explicit type
|
||||
dotnet_diagnostic.IDE0007.severity = error
|
||||
# Inline variable declaration
|
||||
dotnet_diagnostic.IDE0018.severity = error
|
||||
|
||||
# Stylecop Rules
|
||||
dotnet_diagnostic.SA0001.severity = none
|
||||
|
||||
@@ -9,7 +9,7 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '1.5.0'
|
||||
majorVersion: '1.6.1'
|
||||
minorVersion: $[counter('minorVersion', 1)]
|
||||
prowlarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
|
||||
|
||||
@@ -1,58 +1,28 @@
|
||||
import { cloneDeep } from 'lodash';
|
||||
import React, { useEffect } from 'react';
|
||||
import areAllSelected from 'Utilities/Table/areAllSelected';
|
||||
import selectAll from 'Utilities/Table/selectAll';
|
||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import useSelectState, { SelectState } from 'Helpers/Hooks/useSelectState';
|
||||
import ModelBase from './ModelBase';
|
||||
|
||||
export enum SelectActionType {
|
||||
Reset,
|
||||
SelectAll,
|
||||
UnselectAll,
|
||||
ToggleSelected,
|
||||
RemoveItem,
|
||||
UpdateItems,
|
||||
}
|
||||
|
||||
type SelectedState = Record<number, boolean>;
|
||||
|
||||
interface SelectState {
|
||||
selectedState: SelectedState;
|
||||
lastToggled: number | null;
|
||||
allSelected: boolean;
|
||||
allUnselected: boolean;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
items: any[];
|
||||
}
|
||||
|
||||
type SelectAction =
|
||||
| { type: SelectActionType.Reset }
|
||||
| { type: SelectActionType.SelectAll }
|
||||
| { type: SelectActionType.UnselectAll }
|
||||
export type SelectContextAction =
|
||||
| { type: 'reset' }
|
||||
| { type: 'selectAll' }
|
||||
| { type: 'unselectAll' }
|
||||
| {
|
||||
type: SelectActionType.ToggleSelected;
|
||||
type: 'toggleSelected';
|
||||
id: number;
|
||||
isSelected: boolean;
|
||||
shiftKey: boolean;
|
||||
}
|
||||
| {
|
||||
type: SelectActionType.RemoveItem;
|
||||
type: 'removeItem';
|
||||
id: number;
|
||||
}
|
||||
| {
|
||||
type: SelectActionType.UpdateItems;
|
||||
type: 'updateItems';
|
||||
items: ModelBase[];
|
||||
};
|
||||
|
||||
type Dispatch = (action: SelectAction) => void;
|
||||
|
||||
const initialState = {
|
||||
selectedState: {},
|
||||
lastToggled: null,
|
||||
allSelected: false,
|
||||
allUnselected: true,
|
||||
items: [],
|
||||
};
|
||||
export type SelectDispatch = (action: SelectContextAction) => void;
|
||||
|
||||
interface SelectProviderOptions<T extends ModelBase> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -60,90 +30,40 @@ interface SelectProviderOptions<T extends ModelBase> {
|
||||
items: Array<T>;
|
||||
}
|
||||
|
||||
function getSelectedState(items: ModelBase[], existingState: SelectedState) {
|
||||
return items.reduce((acc: SelectedState, item) => {
|
||||
const id = item.id;
|
||||
|
||||
acc[id] = existingState[id] ?? false;
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
// TODO: Can this be reused?
|
||||
|
||||
const SelectContext = React.createContext<[SelectState, Dispatch] | undefined>(
|
||||
cloneDeep(undefined)
|
||||
);
|
||||
|
||||
function selectReducer(state: SelectState, action: SelectAction): SelectState {
|
||||
const { items, selectedState } = state;
|
||||
|
||||
switch (action.type) {
|
||||
case SelectActionType.Reset: {
|
||||
return cloneDeep(initialState);
|
||||
}
|
||||
case SelectActionType.SelectAll: {
|
||||
return {
|
||||
items,
|
||||
...selectAll(selectedState, true),
|
||||
};
|
||||
}
|
||||
case SelectActionType.UnselectAll: {
|
||||
return {
|
||||
items,
|
||||
...selectAll(selectedState, false),
|
||||
};
|
||||
}
|
||||
case SelectActionType.ToggleSelected: {
|
||||
const result = {
|
||||
items,
|
||||
...toggleSelected(
|
||||
state,
|
||||
items,
|
||||
action.id,
|
||||
action.isSelected,
|
||||
action.shiftKey
|
||||
),
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
case SelectActionType.UpdateItems: {
|
||||
const nextSelectedState = getSelectedState(action.items, selectedState);
|
||||
|
||||
return {
|
||||
...state,
|
||||
...areAllSelected(nextSelectedState),
|
||||
selectedState: nextSelectedState,
|
||||
items: action.items,
|
||||
};
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unhandled action type: ${action.type}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
const SelectContext = React.createContext<
|
||||
[SelectState, SelectDispatch] | undefined
|
||||
>(cloneDeep(undefined));
|
||||
|
||||
export function SelectProvider<T extends ModelBase>(
|
||||
props: SelectProviderOptions<T>
|
||||
) {
|
||||
const { items } = props;
|
||||
const selectedState = getSelectedState(items, {});
|
||||
const [state, dispatch] = useSelectState();
|
||||
|
||||
const [state, dispatch] = React.useReducer(selectReducer, {
|
||||
selectedState,
|
||||
lastToggled: null,
|
||||
allSelected: false,
|
||||
allUnselected: true,
|
||||
items,
|
||||
});
|
||||
const dispatchWrapper = useCallback(
|
||||
(action: SelectContextAction) => {
|
||||
switch (action.type) {
|
||||
case 'reset':
|
||||
case 'removeItem':
|
||||
dispatch(action);
|
||||
break;
|
||||
|
||||
const value: [SelectState, Dispatch] = [state, dispatch];
|
||||
default:
|
||||
dispatch({
|
||||
...action,
|
||||
items,
|
||||
});
|
||||
break;
|
||||
}
|
||||
},
|
||||
[items, dispatch]
|
||||
);
|
||||
|
||||
const value: [SelectState, SelectDispatch] = [state, dispatchWrapper];
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({ type: SelectActionType.UpdateItems, items });
|
||||
}, [items]);
|
||||
dispatch({ type: 'updateItems', items });
|
||||
}, [items, dispatch]);
|
||||
|
||||
return (
|
||||
<SelectContext.Provider value={value}>
|
||||
|
||||
@@ -578,7 +578,7 @@ EnhancedSelectInput.propTypes = {
|
||||
className: PropTypes.string,
|
||||
disabledClassName: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.number)]).isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.string), PropTypes.arrayOf(PropTypes.number)]).isRequired,
|
||||
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isDisabled: PropTypes.bool.isRequired,
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
|
||||
@@ -67,6 +67,7 @@ function ProviderFieldFormGroup(props) {
|
||||
name,
|
||||
label,
|
||||
helpText,
|
||||
helpTextWarning,
|
||||
helpLink,
|
||||
placeholder,
|
||||
value,
|
||||
@@ -100,6 +101,7 @@ function ProviderFieldFormGroup(props) {
|
||||
name={name}
|
||||
label={label}
|
||||
helpText={helpText}
|
||||
helpTextWarning={helpTextWarning}
|
||||
helpLink={helpLink}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
@@ -126,6 +128,7 @@ ProviderFieldFormGroup.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
label: PropTypes.string,
|
||||
helpText: PropTypes.string,
|
||||
helpTextWarning: PropTypes.string,
|
||||
helpLink: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
value: PropTypes.any,
|
||||
|
||||
@@ -13,7 +13,7 @@ const messages = [
|
||||
'Loading humorous message... Please Wait',
|
||||
'I could\'ve been faster in Python',
|
||||
'Don\'t forget to rewind your tracks',
|
||||
'Congratulations! you are the 1000th visitor.',
|
||||
'Congratulations! You are the 1000th visitor.',
|
||||
'HELP! I\'m being held hostage and forced to write these stupid lines!',
|
||||
'RE-calibrating the internet...',
|
||||
'I\'ll be here all week',
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
|
||||
function PageSectionContent(props) {
|
||||
const {
|
||||
@@ -17,7 +19,7 @@ function PageSectionContent(props) {
|
||||
);
|
||||
} else if (!isFetching && !!error) {
|
||||
return (
|
||||
<div>{errorMessage}</div>
|
||||
<Alert kind={kinds.DANGER}>{errorMessage}</Alert>
|
||||
);
|
||||
} else if (isPopulated && !error) {
|
||||
return (
|
||||
|
||||
@@ -16,6 +16,38 @@
|
||||
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
|
||||
color: var(--white);
|
||||
transition: width 0.6s ease;
|
||||
|
||||
&.primary {
|
||||
background-color: var(--primaryColor);
|
||||
}
|
||||
|
||||
&.danger {
|
||||
background-color: var(--dangerColor);
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(90deg, color(var(--dangerColor) shade(5%)), color(var(--dangerColor) shade(5%)) 5px, color(var(--dangerColor) shade(15%)) 5px, color(var(--dangerColor) shade(15%)) 10px);
|
||||
}
|
||||
}
|
||||
|
||||
&.success {
|
||||
background-color: var(--successColor);
|
||||
}
|
||||
|
||||
&.purple {
|
||||
background-color: var(--purple);
|
||||
}
|
||||
|
||||
&.warning {
|
||||
background-color: var(--warningColor);
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(45deg, var(--warningColor), var(--warningColor) 5px, color(var(--warningColor) tint(15%)) 5px, color(var(--warningColor) tint(15%)) 10px);
|
||||
}
|
||||
}
|
||||
|
||||
&.info {
|
||||
background-color: var(--infoColor);
|
||||
}
|
||||
}
|
||||
|
||||
.frontTextContainer {
|
||||
@@ -41,38 +73,6 @@
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.primary {
|
||||
background-color: var(--primaryColor);
|
||||
}
|
||||
|
||||
.danger {
|
||||
background-color: var(--dangerColor);
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(90deg, color(var(--dangerColor) shade(5%)), color(var(--dangerColor) shade(5%)) 5px, color(var(--dangerColor) shade(15%)) 5px, color(var(--dangerColor) shade(15%)) 10px);
|
||||
}
|
||||
}
|
||||
|
||||
.success {
|
||||
background-color: var(--successColor);
|
||||
}
|
||||
|
||||
.purple {
|
||||
background-color: var(--purple);
|
||||
}
|
||||
|
||||
.warning {
|
||||
background-color: var(--warningColor);
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(45deg, var(--warningColor), var(--warningColor) 5px, color(var(--warningColor) tint(15%)) 5px, color(var(--warningColor) tint(15%)) 10px);
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
background-color: var(--infoColor);
|
||||
}
|
||||
|
||||
.small {
|
||||
height: $progressBarSmallHeight;
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ function ProgressBar(props) {
|
||||
{
|
||||
showText && width ?
|
||||
<div
|
||||
className={styles.backTextContainer}
|
||||
className={classNames(styles.backTextContainer, styles[kind])}
|
||||
style={{ width: actualWidth }}
|
||||
>
|
||||
<div className={styles.backText}>
|
||||
@@ -67,7 +67,7 @@ function ProgressBar(props) {
|
||||
{
|
||||
showText ?
|
||||
<div
|
||||
className={styles.frontTextContainer}
|
||||
className={classNames(styles.frontTextContainer, styles[kind])}
|
||||
style={{ width: progressPercent }}
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
{
|
||||
"name": "",
|
||||
"name": "Prowlarr",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/Content/Images/Icons/android-chrome-192x192.png",
|
||||
"src": "android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/Content/Images/Icons/android-chrome-512x512.png",
|
||||
"src": "android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"start_url": "../../../../",
|
||||
"theme_color": "#3a3f51",
|
||||
"background_color": "#3a3f51",
|
||||
"display": "standalone"
|
||||
}
|
||||
}
|
||||
|
||||
113
frontend/src/Helpers/Hooks/useSelectState.tsx
Normal file
113
frontend/src/Helpers/Hooks/useSelectState.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useReducer } from 'react';
|
||||
import ModelBase from 'App/ModelBase';
|
||||
import areAllSelected from 'Utilities/Table/areAllSelected';
|
||||
import selectAll from 'Utilities/Table/selectAll';
|
||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||
|
||||
export type SelectedState = Record<number, boolean>;
|
||||
|
||||
export interface SelectState {
|
||||
selectedState: SelectedState;
|
||||
lastToggled: number | null;
|
||||
allSelected: boolean;
|
||||
allUnselected: boolean;
|
||||
}
|
||||
|
||||
export type SelectAction =
|
||||
| { type: 'reset' }
|
||||
| { type: 'selectAll'; items: ModelBase[] }
|
||||
| { type: 'unselectAll'; items: ModelBase[] }
|
||||
| {
|
||||
type: 'toggleSelected';
|
||||
id: number;
|
||||
isSelected: boolean;
|
||||
shiftKey: boolean;
|
||||
items: ModelBase[];
|
||||
}
|
||||
| {
|
||||
type: 'removeItem';
|
||||
id: number;
|
||||
}
|
||||
| {
|
||||
type: 'updateItems';
|
||||
items: ModelBase[];
|
||||
};
|
||||
|
||||
export type Dispatch = (action: SelectAction) => void;
|
||||
|
||||
const initialState = {
|
||||
selectedState: {},
|
||||
lastToggled: null,
|
||||
allSelected: false,
|
||||
allUnselected: true,
|
||||
items: [],
|
||||
};
|
||||
|
||||
function getSelectedState(items: ModelBase[], existingState: SelectedState) {
|
||||
return items.reduce((acc: SelectedState, item) => {
|
||||
const id = item.id;
|
||||
|
||||
acc[id] = existingState[id] ?? false;
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function selectReducer(state: SelectState, action: SelectAction): SelectState {
|
||||
const { selectedState } = state;
|
||||
|
||||
switch (action.type) {
|
||||
case 'reset': {
|
||||
return cloneDeep(initialState);
|
||||
}
|
||||
case 'selectAll': {
|
||||
return {
|
||||
...selectAll(selectedState, true),
|
||||
};
|
||||
}
|
||||
case 'unselectAll': {
|
||||
return {
|
||||
...selectAll(selectedState, false),
|
||||
};
|
||||
}
|
||||
case 'toggleSelected': {
|
||||
const result = {
|
||||
...toggleSelected(
|
||||
state,
|
||||
action.items,
|
||||
action.id,
|
||||
action.isSelected,
|
||||
action.shiftKey
|
||||
),
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
case 'updateItems': {
|
||||
const nextSelectedState = getSelectedState(action.items, selectedState);
|
||||
|
||||
return {
|
||||
...state,
|
||||
...areAllSelected(nextSelectedState),
|
||||
selectedState: nextSelectedState,
|
||||
};
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unhandled action type: ${action.type}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function useSelectState(): [SelectState, Dispatch] {
|
||||
const selectedState = getSelectedState([], {});
|
||||
|
||||
const [state, dispatch] = useReducer(selectReducer, {
|
||||
selectedState,
|
||||
lastToggled: null,
|
||||
allSelected: false,
|
||||
allUnselected: true,
|
||||
});
|
||||
|
||||
return [state, dispatch];
|
||||
}
|
||||
@@ -1,14 +1,18 @@
|
||||
import * as filterTypes from './filterTypes';
|
||||
|
||||
export const ARRAY = 'array';
|
||||
export const CONTAINS = 'contains';
|
||||
export const DATE = 'date';
|
||||
export const EQUAL = 'equal';
|
||||
export const EXACT = 'exact';
|
||||
export const NUMBER = 'number';
|
||||
export const STRING = 'string';
|
||||
|
||||
export const all = [
|
||||
ARRAY,
|
||||
CONTAINS,
|
||||
DATE,
|
||||
EQUAL,
|
||||
EXACT,
|
||||
NUMBER,
|
||||
STRING
|
||||
@@ -20,6 +24,10 @@ export const possibleFilterTypes = {
|
||||
{ key: filterTypes.NOT_CONTAINS, value: 'does not contain' }
|
||||
],
|
||||
|
||||
[CONTAINS]: [
|
||||
{ key: filterTypes.CONTAINS, value: 'contains' }
|
||||
],
|
||||
|
||||
[DATE]: [
|
||||
{ key: filterTypes.LESS_THAN, value: 'is before' },
|
||||
{ key: filterTypes.GREATER_THAN, value: 'is after' },
|
||||
@@ -29,6 +37,10 @@ export const possibleFilterTypes = {
|
||||
{ key: filterTypes.NOT_IN_NEXT, value: 'not in the next' }
|
||||
],
|
||||
|
||||
[EQUAL]: [
|
||||
{ key: filterTypes.EQUAL, value: 'is' }
|
||||
],
|
||||
|
||||
[EXACT]: [
|
||||
{ key: filterTypes.EQUAL, value: 'is' },
|
||||
{ key: filterTypes.NOT_EQUAL, value: 'is not' }
|
||||
@@ -47,6 +59,10 @@ export const possibleFilterTypes = {
|
||||
{ key: filterTypes.CONTAINS, value: 'contains' },
|
||||
{ key: filterTypes.NOT_CONTAINS, value: 'does not contain' },
|
||||
{ key: filterTypes.EQUAL, value: 'equal' },
|
||||
{ key: filterTypes.NOT_EQUAL, value: 'not equal' }
|
||||
{ key: filterTypes.NOT_EQUAL, value: 'not equal' },
|
||||
{ key: filterTypes.STARTS_WITH, value: 'starts with' },
|
||||
{ key: filterTypes.NOT_STARTS_WITH, value: 'does not start with' },
|
||||
{ key: filterTypes.ENDS_WITH, value: 'ends with' },
|
||||
{ key: filterTypes.NOT_ENDS_WITH, value: 'does not end with' }
|
||||
]
|
||||
};
|
||||
|
||||
@@ -39,6 +39,22 @@ const filterTypePredicates = {
|
||||
|
||||
[filterTypes.NOT_EQUAL]: function(itemValue, filterValue) {
|
||||
return itemValue !== filterValue;
|
||||
},
|
||||
|
||||
[filterTypes.STARTS_WITH]: function(itemValue, filterValue) {
|
||||
return itemValue.toLowerCase().startsWith(filterValue.toLowerCase());
|
||||
},
|
||||
|
||||
[filterTypes.NOT_STARTS_WITH]: function(itemValue, filterValue) {
|
||||
return !itemValue.toLowerCase().startsWith(filterValue.toLowerCase());
|
||||
},
|
||||
|
||||
[filterTypes.ENDS_WITH]: function(itemValue, filterValue) {
|
||||
return itemValue.toLowerCase().endsWith(filterValue.toLowerCase());
|
||||
},
|
||||
|
||||
[filterTypes.NOT_ENDS_WITH]: function(itemValue, filterValue) {
|
||||
return !itemValue.toLowerCase().endsWith(filterValue.toLowerCase());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@ export const LESS_THAN = 'lessThan';
|
||||
export const LESS_THAN_OR_EQUAL = 'lessThanOrEqual';
|
||||
export const NOT_CONTAINS = 'notContains';
|
||||
export const NOT_EQUAL = 'notEqual';
|
||||
export const STARTS_WITH = 'startsWith';
|
||||
export const NOT_STARTS_WITH = 'notStartsWith';
|
||||
export const ENDS_WITH = 'endsWith';
|
||||
export const NOT_ENDS_WITH = 'notEndsWith';
|
||||
|
||||
export const all = [
|
||||
CONTAINS,
|
||||
@@ -23,5 +27,9 @@ export const all = [
|
||||
IN_LAST,
|
||||
NOT_IN_LAST,
|
||||
IN_NEXT,
|
||||
NOT_IN_NEXT
|
||||
NOT_IN_NEXT,
|
||||
STARTS_WITH,
|
||||
NOT_STARTS_WITH,
|
||||
ENDS_WITH,
|
||||
NOT_ENDS_WITH
|
||||
];
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
@@ -121,9 +122,9 @@ class History extends Component {
|
||||
|
||||
{
|
||||
!isFetchingAny && hasError &&
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadHistory')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
@@ -131,9 +132,9 @@ class History extends Component {
|
||||
// wait for the episodes to populate because they are never coming.
|
||||
|
||||
isPopulated && !hasError && !items.length &&
|
||||
<div>
|
||||
No history found
|
||||
</div>
|
||||
<Alert kind={kinds.INFO}>
|
||||
{translate('NoHistoryFound')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -26,7 +26,7 @@ const columns = [
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
name: 'sortName',
|
||||
label: translate('Name'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
@@ -226,7 +226,7 @@ class AddIndexerModalContent extends Component {
|
||||
{
|
||||
filteredIndexers.map((indexer) => (
|
||||
<SelectIndexerRowConnector
|
||||
key={indexer.name}
|
||||
key={`${indexer.implementation}-${indexer.name}`}
|
||||
implementation={indexer.implementation}
|
||||
{...indexer}
|
||||
onIndexerSelect={onIndexerSelect}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { SelectActionType, useSelect } from 'App/SelectContext';
|
||||
import { useSelect } from 'App/SelectContext';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
@@ -25,9 +25,7 @@ function IndexerIndexSelectAllButton(props: IndexerIndexSelectAllButtonProps) {
|
||||
|
||||
const onPress = useCallback(() => {
|
||||
selectDispatch({
|
||||
type: allSelected
|
||||
? SelectActionType.UnselectAll
|
||||
: SelectActionType.SelectAll,
|
||||
type: allSelected ? 'unselectAll' : 'selectAll',
|
||||
});
|
||||
}, [allSelected, selectDispatch]);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { SelectActionType, useSelect } from 'App/SelectContext';
|
||||
import { useSelect } from 'App/SelectContext';
|
||||
import PageToolbarOverflowMenuItem from 'Components/Page/Toolbar/PageToolbarOverflowMenuItem';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
@@ -26,9 +26,7 @@ function IndexerIndexSelectAllMenuItem(
|
||||
|
||||
const onPressWrapper = useCallback(() => {
|
||||
selectDispatch({
|
||||
type: allSelected
|
||||
? SelectActionType.UnselectAll
|
||||
: SelectActionType.SelectAll,
|
||||
type: allSelected ? 'unselectAll' : 'selectAll',
|
||||
});
|
||||
}, [allSelected, selectDispatch]);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { SelectActionType, useSelect } from 'App/SelectContext';
|
||||
import { useSelect } from 'App/SelectContext';
|
||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||
import PageContentFooter from 'Components/Page/PageContentFooter';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
@@ -111,7 +111,7 @@ function IndexerIndexSelectFooter() {
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDeleting && !deleteError) {
|
||||
selectDispatch({ type: SelectActionType.UnselectAll });
|
||||
selectDispatch({ type: 'unselectAll' });
|
||||
}
|
||||
}, [isDeleting, deleteError, selectDispatch]);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { IconDefinition } from '@fortawesome/fontawesome-common-types';
|
||||
import React, { useCallback } from 'react';
|
||||
import { SelectActionType, useSelect } from 'App/SelectContext';
|
||||
import { useSelect } from 'App/SelectContext';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
|
||||
interface IndexerIndexSelectModeButtonProps {
|
||||
@@ -20,7 +20,7 @@ function IndexerIndexSelectModeButton(
|
||||
const onPressWrapper = useCallback(() => {
|
||||
if (isSelectMode) {
|
||||
selectDispatch({
|
||||
type: SelectActionType.Reset,
|
||||
type: 'reset',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { IconDefinition } from '@fortawesome/fontawesome-common-types';
|
||||
import React, { useCallback } from 'react';
|
||||
import { SelectActionType, useSelect } from 'App/SelectContext';
|
||||
import { useSelect } from 'App/SelectContext';
|
||||
import PageToolbarOverflowMenuItem from 'Components/Page/Toolbar/PageToolbarOverflowMenuItem';
|
||||
|
||||
interface IndexerIndexSelectModeMenuItemProps {
|
||||
@@ -19,7 +19,7 @@ function IndexerIndexSelectModeMenuItem(
|
||||
const onPressWrapper = useCallback(() => {
|
||||
if (isSelectMode) {
|
||||
selectDispatch({
|
||||
type: SelectActionType.Reset,
|
||||
type: 'reset',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { SelectActionType, useSelect } from 'App/SelectContext';
|
||||
import { useSelect } from 'App/SelectContext';
|
||||
import Label from 'Components/Label';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||
@@ -90,7 +90,7 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
|
||||
const onSelectedChange = useCallback(
|
||||
({ id, value, shiftKey }) => {
|
||||
selectDispatch({
|
||||
type: SelectActionType.ToggleSelected,
|
||||
type: 'toggleSelected',
|
||||
id,
|
||||
isSelected: value,
|
||||
shiftKey,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { SelectActionType, useSelect } from 'App/SelectContext';
|
||||
import { useSelect } from 'App/SelectContext';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import Column from 'Components/Table/Column';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
@@ -47,7 +47,7 @@ function IndexerIndexTableHeader(props: IndexerIndexTableHeaderProps) {
|
||||
const onSelectAllChange = useCallback(
|
||||
({ value }) => {
|
||||
selectDispatch({
|
||||
type: value ? SelectActionType.SelectAll : SelectActionType.UnselectAll,
|
||||
type: value ? 'selectAll' : 'unselectAll',
|
||||
});
|
||||
},
|
||||
[selectDispatch]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import BarChart from 'Components/Chart/BarChart';
|
||||
import DoughnutChart from 'Components/Chart/DoughnutChart';
|
||||
import StackedBarChart from 'Components/Chart/StackedBarChart';
|
||||
@@ -178,9 +179,9 @@ function Stats(props) {
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div className={styles.errorMessage}>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{getErrorMessage(error, 'Failed to load indexer stats from API')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
@@ -10,7 +11,7 @@ import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import { align, icons, sortDirections } from 'Helpers/Props';
|
||||
import { align, icons, kinds, sortDirections } from 'Helpers/Props';
|
||||
import NoIndexer from 'Indexer/NoIndexer';
|
||||
import * as keyCodes from 'Utilities/Constants/keyCodes';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
@@ -309,9 +310,9 @@ class SearchIndex extends Component {
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div className={styles.errorMessage}>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{getErrorMessage(error, 'Failed to load search results from API')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Label from 'Components/Label';
|
||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||
import { kinds, tooltipPositions } from 'Helpers/Props';
|
||||
import Tooltip from '../../Components/Tooltip/Tooltip';
|
||||
|
||||
function CategoryLabel({ categories }) {
|
||||
const sortedCategories = categories.filter((cat) => cat.name !== undefined).sort((c) => c.id);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
@@ -8,7 +9,7 @@ import FormLabel from 'Components/Form/FormLabel';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
@@ -49,9 +50,9 @@ class DevelopmentSettings extends Component {
|
||||
|
||||
{
|
||||
!isFetching && error &&
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadDevelopmentSettings')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import Form from 'Components/Form/Form';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
@@ -123,9 +124,9 @@ class GeneralSettings extends Component {
|
||||
|
||||
{
|
||||
!isFetching && error &&
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadGeneralSettings')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
@@ -8,7 +9,7 @@ import FormLabel from 'Components/Form/FormLabel';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
|
||||
import themes from 'Styles/Themes';
|
||||
import titleCase from 'Utilities/String/titleCase';
|
||||
@@ -80,9 +81,9 @@ class UISettings extends Component {
|
||||
|
||||
{
|
||||
!isFetching && error &&
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadUISettings')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -32,9 +32,9 @@ function createSaveProviderHandler(section, url, options = {}) {
|
||||
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.
|
||||
// force it to be saved.
|
||||
|
||||
if (id && _.isEqual(saveData, lastSaveData)) {
|
||||
if (_.isEqual(saveData, lastSaveData)) {
|
||||
params.forceSave = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ export const defaultState = {
|
||||
isFetching: false,
|
||||
isPopulated: false,
|
||||
error: null,
|
||||
sortKey: 'name',
|
||||
sortKey: 'sortName',
|
||||
sortDirection: sortDirections.ASCENDING,
|
||||
items: []
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
@@ -8,7 +9,7 @@ import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import BackupRow from './BackupRow';
|
||||
import RestoreBackupModalConnector from './RestoreBackupModalConnector';
|
||||
@@ -107,16 +108,16 @@ class Backups extends Component {
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadBackups')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
noBackups &&
|
||||
<div>
|
||||
<Alert kind={kinds.INFO}>
|
||||
{translate('NoBackupsAreAvailable')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
@@ -11,7 +12,7 @@ import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import { align, icons } from 'Helpers/Props';
|
||||
import { align, icons, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import LogsTableRow from './LogsTableRow';
|
||||
|
||||
@@ -82,9 +83,9 @@ function LogsTable(props) {
|
||||
|
||||
{
|
||||
isPopulated && !error && !items.length &&
|
||||
<div>
|
||||
<Alert kind={kinds.INFO}>
|
||||
No events found
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -96,7 +96,14 @@ class LogsTableConnector extends Component {
|
||||
};
|
||||
|
||||
onClearLogsPress = () => {
|
||||
this.props.executeCommand({ name: commandNames.CLEAR_LOGS });
|
||||
this.props.executeCommand({
|
||||
name: commandNames.CLEAR_LOGS,
|
||||
commandFinished: this.onCommandFinished
|
||||
});
|
||||
};
|
||||
|
||||
onCommandFinished = () => {
|
||||
this.props.gotoLogsFirstPage();
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
@@ -11,7 +11,7 @@ import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import LogsNavMenu from '../LogsNavMenu';
|
||||
import LogFilesTableRow from './LogFilesTableRow';
|
||||
@@ -118,9 +118,9 @@ class LogFiles extends Component {
|
||||
|
||||
{
|
||||
!isFetching && !items.length &&
|
||||
<div>
|
||||
<Alert kind={kinds.INFO}>
|
||||
{translate('NoLogFiles')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
</PageContentBody>
|
||||
</PageContent>
|
||||
|
||||
@@ -50,12 +50,6 @@ class LogFilesConnector extends Component {
|
||||
this.props.fetchLogFiles();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.deleteFilesExecuting && !this.props.deleteFilesExecuting) {
|
||||
this.props.fetchLogFiles();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
@@ -64,7 +58,14 @@ class LogFilesConnector extends Component {
|
||||
};
|
||||
|
||||
onDeleteFilesPress = () => {
|
||||
this.props.executeCommand({ name: commandNames.DELETE_LOG_FILES });
|
||||
this.props.executeCommand({
|
||||
name: commandNames.DELETE_LOG_FILES,
|
||||
commandFinished: this.onCommandFinished
|
||||
});
|
||||
};
|
||||
|
||||
onCommandFinished = () => {
|
||||
this.props.fetchLogFiles();
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import Icon from 'Components/Icon';
|
||||
import Label from 'Components/Label';
|
||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||
@@ -61,9 +62,9 @@ class Updates extends Component {
|
||||
|
||||
{
|
||||
noUpdates &&
|
||||
<div>
|
||||
<Alert kind={kinds.INFO}>
|
||||
{translate('NoUpdatesAreAvailable')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -30,6 +30,13 @@
|
||||
<!-- A test project gets the test sdk packages automatically added -->
|
||||
<TestProject>false</TestProject>
|
||||
<TestProject Condition="$(MSBuildProjectName.EndsWith('.Test'))">true</TestProject>
|
||||
|
||||
<!-- XML documentation comments are needed to enforce rule IDE0005 on build -->
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<!--
|
||||
CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member'
|
||||
-->
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
@@ -102,7 +109,7 @@
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||
<PackageReference Include="NunitXml.TestLogger" Version="3.0.117" />
|
||||
<PackageReference Include="NunitXml.TestLogger" Version="3.0.131" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace NzbDrone.Automation.Test
|
||||
{
|
||||
try
|
||||
{
|
||||
Screenshot image = ((ITakesScreenshot)driver).GetScreenshot();
|
||||
var image = ((ITakesScreenshot)driver).GetScreenshot();
|
||||
image.SaveAsFile($"./{name}_test_screenshot.png", ScreenshotImageFormat.Png);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace NzbDrone.Automation.Test.PageModel
|
||||
{
|
||||
try
|
||||
{
|
||||
IWebElement element = d.FindElement(By.ClassName("followingBalls"));
|
||||
var element = d.FindElement(By.ClassName("followingBalls"));
|
||||
return !element.Displayed;
|
||||
}
|
||||
catch (NoSuchElementException)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
@@ -65,9 +65,9 @@ namespace NzbDrone.Common.Test.CacheTests
|
||||
[Test]
|
||||
public void should_store_null()
|
||||
{
|
||||
int hitCount = 0;
|
||||
var hitCount = 0;
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
_cachedString.Get("key", () =>
|
||||
{
|
||||
@@ -83,10 +83,10 @@ namespace NzbDrone.Common.Test.CacheTests
|
||||
[Platform(Exclude = "MacOsX")]
|
||||
public void should_honor_ttl()
|
||||
{
|
||||
int hitCount = 0;
|
||||
var hitCount = 0;
|
||||
_cachedString = new Cached<string>();
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
_cachedString.Get("key",
|
||||
() =>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -142,7 +142,7 @@ namespace NzbDrone.Common.Test
|
||||
[Test]
|
||||
public void SaveDictionary_should_save_proper_value()
|
||||
{
|
||||
int port = 20555;
|
||||
var port = 20555;
|
||||
|
||||
var dic = Subject.GetConfigDictionary();
|
||||
dic["Port"] = 20555;
|
||||
@@ -155,9 +155,9 @@ namespace NzbDrone.Common.Test
|
||||
[Test]
|
||||
public void SaveDictionary_should_only_save_specified_values()
|
||||
{
|
||||
int port = 20555;
|
||||
int origSslPort = 20551;
|
||||
int sslPort = 20552;
|
||||
var port = 20555;
|
||||
var origSslPort = 20551;
|
||||
var sslPort = 20552;
|
||||
|
||||
var dic = Subject.GetConfigDictionary();
|
||||
dic["Port"] = port;
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
[Test]
|
||||
public void should_not_contain_recycling_bin_for_root_of_drive()
|
||||
{
|
||||
string root = @"C:\".AsOsAgnostic();
|
||||
var root = @"C:\".AsOsAgnostic();
|
||||
SetupFolders(root);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
@@ -55,7 +55,7 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
[Test]
|
||||
public void should_not_contain_system_volume_information()
|
||||
{
|
||||
string root = @"C:\".AsOsAgnostic();
|
||||
var root = @"C:\".AsOsAgnostic();
|
||||
SetupFolders(root);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
@@ -68,7 +68,7 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
[Test]
|
||||
public void should_not_contain_recycling_bin_or_system_volume_information_for_root_of_drive()
|
||||
{
|
||||
string root = @"C:\".AsOsAgnostic();
|
||||
var root = @"C:\".AsOsAgnostic();
|
||||
SetupFolders(root);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
|
||||
@@ -351,6 +351,26 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
.Verify(v => v.DeleteFile(_targetPath), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_rollback_move_on_partial_if_destination_already_exists()
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.MoveFile(_sourcePath, _targetPath, false))
|
||||
.Callback(() =>
|
||||
{
|
||||
WithExistingFile(_targetPath, true, 900);
|
||||
});
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.MoveFile(_sourcePath, _targetPath, false))
|
||||
.Throws(new FileAlreadyExistsException("File already exists", _targetPath));
|
||||
|
||||
Assert.Throws<FileAlreadyExistsException>(() => Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Move));
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.DeleteFile(_targetPath), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_log_error_if_rollback_partialmove_fails()
|
||||
{
|
||||
|
||||
@@ -788,7 +788,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
try
|
||||
{
|
||||
// the date is bad in the below - should be 13-Jul-2026
|
||||
string malformedCookie = @"__cfduid=d29e686a9d65800021c66faca0a29b4261436890790; expires=Mon, 13-Jul-26 16:19:50 GMT; path=/; HttpOnly";
|
||||
var malformedCookie = @"__cfduid=d29e686a9d65800021c66faca0a29b4261436890790; expires=Mon, 13-Jul-26 16:19:50 GMT; path=/; HttpOnly";
|
||||
var requestSet = new HttpRequestBuilder($"https://{_httpBinHost}/response-headers")
|
||||
.AddQueryParam("Set-Cookie", malformedCookie)
|
||||
.Build();
|
||||
@@ -822,7 +822,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
{
|
||||
try
|
||||
{
|
||||
string url = $"https://{_httpBinHost}/response-headers?Set-Cookie={Uri.EscapeDataString(malformedCookie)}";
|
||||
var url = $"https://{_httpBinHost}/response-headers?Set-Cookie={Uri.EscapeDataString(malformedCookie)}";
|
||||
|
||||
var requestSet = new HttpRequest(url);
|
||||
requestSet.AllowAutoRedirect = false;
|
||||
|
||||
@@ -10,7 +10,9 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
// Indexer Urls
|
||||
[TestCase(@"https://iptorrents.com/torrents/rss?u=mySecret;tp=mySecret;l5;download")]
|
||||
[TestCase(@"http://rss.torrentleech.org/mySecret")]
|
||||
[TestCase(@"http://rss.torrentleech.org/rss/download/12345/01233210/filename.torrent")]
|
||||
[TestCase(@"https://rss24h.torrentleech.org/mySecret")]
|
||||
[TestCase(@"http://rss.torrentleech.org/rss/download/12345/01233210/file.name-RLSGRP.torrent")]
|
||||
[TestCase(@"https://www.torrentleech.org/rss/download/12345/01233210/file.name-RLSGRP.torrent")]
|
||||
[TestCase(@"http://www.bitmetv.org/rss.php?uid=mySecret&passkey=mySecret")]
|
||||
[TestCase(@"https://rss.omgwtfnzbs.org/rss-search.php?catid=19,20&user=sonarr&api=mySecret&eng=1")]
|
||||
[TestCase(@"https://dognzb.cr/fetch/2b51db35e1912ffc138825a12b9933d2/2b51db35e1910123321025a12b9933d2")]
|
||||
@@ -79,6 +81,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
// Deluge
|
||||
[TestCase(@",{""download_location"": ""C:\Users\\mySecret mySecret\\Downloads""}")]
|
||||
[TestCase(@",{""download_location"": ""/home/mySecret/Downloads""}")]
|
||||
[TestCase(@",{""download_location"": ""/Users/mySecret/Downloads""}")]
|
||||
[TestCase(@"auth.login(""mySecret"")")]
|
||||
|
||||
// Download Station
|
||||
|
||||
@@ -74,17 +74,17 @@ namespace NzbDrone.Common
|
||||
continue; // Ignore directories
|
||||
}
|
||||
|
||||
string entryFileName = zipEntry.Name;
|
||||
var entryFileName = zipEntry.Name;
|
||||
|
||||
// to remove the folder from the entry:- entryFileName = Path.GetFileName(entryFileName);
|
||||
// Optionally match entrynames against a selection list here to skip as desired.
|
||||
// The unpacked length is available in the zipEntry.Size property.
|
||||
byte[] buffer = new byte[4096]; // 4K is optimum
|
||||
Stream zipStream = zipFile.GetInputStream(zipEntry);
|
||||
var buffer = new byte[4096]; // 4K is optimum
|
||||
var zipStream = zipFile.GetInputStream(zipEntry);
|
||||
|
||||
// Manipulate the output filename here as desired.
|
||||
string fullZipToPath = Path.Combine(destination, entryFileName);
|
||||
string directoryName = Path.GetDirectoryName(fullZipToPath);
|
||||
var fullZipToPath = Path.Combine(destination, entryFileName);
|
||||
var directoryName = Path.GetDirectoryName(fullZipToPath);
|
||||
if (directoryName.Length > 0)
|
||||
{
|
||||
Directory.CreateDirectory(directoryName);
|
||||
@@ -93,7 +93,7 @@ namespace NzbDrone.Common
|
||||
// Unzip file in buffered chunks. This is just as fast as unpacking to a buffer the full size
|
||||
// of the file, but does not waste memory.
|
||||
// The "using" will close the stream even if an exception occurs.
|
||||
using (FileStream streamWriter = File.Create(fullZipToPath))
|
||||
using (var streamWriter = File.Create(fullZipToPath))
|
||||
{
|
||||
StreamUtils.Copy(zipStream, streamWriter, buffer);
|
||||
}
|
||||
@@ -106,7 +106,7 @@ namespace NzbDrone.Common
|
||||
Stream inStream = File.OpenRead(compressedFile);
|
||||
Stream gzipStream = new GZipInputStream(inStream);
|
||||
|
||||
TarArchive tarArchive = TarArchive.CreateInputTarArchive(gzipStream, null);
|
||||
var tarArchive = TarArchive.CreateInputTarArchive(gzipStream, null);
|
||||
tarArchive.ExtractContents(destination);
|
||||
tarArchive.Close();
|
||||
|
||||
|
||||
@@ -47,8 +47,7 @@ namespace NzbDrone.Common.Cache
|
||||
|
||||
public T Find(string key)
|
||||
{
|
||||
CacheItem cacheItem;
|
||||
if (!_store.TryGetValue(key, out cacheItem))
|
||||
if (!_store.TryGetValue(key, out var cacheItem))
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
@@ -76,8 +75,7 @@ namespace NzbDrone.Common.Cache
|
||||
|
||||
public void Remove(string key)
|
||||
{
|
||||
CacheItem value;
|
||||
_store.TryRemove(key, out value);
|
||||
_store.TryRemove(key, out _);
|
||||
}
|
||||
|
||||
public int Count => _store.Count;
|
||||
@@ -88,9 +86,7 @@ namespace NzbDrone.Common.Cache
|
||||
|
||||
lifeTime = lifeTime ?? _defaultLifeTime;
|
||||
|
||||
CacheItem cacheItem;
|
||||
|
||||
if (_store.TryGetValue(key, out cacheItem) && !cacheItem.IsExpired())
|
||||
if (_store.TryGetValue(key, out var cacheItem) && !cacheItem.IsExpired())
|
||||
{
|
||||
if (_rollingExpiry && lifeTime.HasValue)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@@ -86,9 +86,7 @@ namespace NzbDrone.Common.Cache
|
||||
{
|
||||
RefreshIfExpired();
|
||||
|
||||
TValue result;
|
||||
|
||||
if (!_items.TryGetValue(key, out result))
|
||||
if (!_items.TryGetValue(key, out var result))
|
||||
{
|
||||
throw new KeyNotFoundException(string.Format("Item {0} not found in cache.", key));
|
||||
}
|
||||
@@ -100,9 +98,7 @@ namespace NzbDrone.Common.Cache
|
||||
{
|
||||
RefreshIfExpired();
|
||||
|
||||
TValue result;
|
||||
|
||||
_items.TryGetValue(key, out result);
|
||||
_items.TryGetValue(key, out var result);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -128,8 +124,7 @@ namespace NzbDrone.Common.Cache
|
||||
|
||||
public void Remove(string key)
|
||||
{
|
||||
TValue item;
|
||||
_items.TryRemove(key, out item);
|
||||
_items.TryRemove(key, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,6 +264,11 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
protected virtual void MoveFileInternal(string source, string destination)
|
||||
{
|
||||
if (File.Exists(destination))
|
||||
{
|
||||
throw new FileAlreadyExistsException("File already exists", destination);
|
||||
}
|
||||
|
||||
File.Move(source, destination);
|
||||
}
|
||||
|
||||
|
||||
@@ -500,9 +500,13 @@ namespace NzbDrone.Common.Disk
|
||||
throw new IOException(string.Format("File move incomplete, data loss may have occurred. [{0}] was {1} bytes long instead of the expected {2}.", targetPath, targetSize, originalSize));
|
||||
}
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
RollbackPartialMove(sourcePath, targetPath);
|
||||
if (ex is not FileAlreadyExistsException)
|
||||
{
|
||||
RollbackPartialMove(sourcePath, targetPath);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
15
src/NzbDrone.Common/Disk/FileAlreadyExistsException.cs
Normal file
15
src/NzbDrone.Common/Disk/FileAlreadyExistsException.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Common.Disk
|
||||
{
|
||||
public class FileAlreadyExistsException : Exception
|
||||
{
|
||||
public string Filename { get; set; }
|
||||
|
||||
public FileAlreadyExistsException(string message, string filename)
|
||||
: base(message)
|
||||
{
|
||||
Filename = filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -255,7 +255,7 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
var stringComparison = (Kind == OsPathKind.Windows || other.Kind == OsPathKind.Windows) ? StringComparison.InvariantCultureIgnoreCase : StringComparison.InvariantCulture;
|
||||
|
||||
for (int i = 0; i < leftFragments.Length; i++)
|
||||
for (var i = 0; i < leftFragments.Length; i++)
|
||||
{
|
||||
if (!string.Equals(leftFragments[i], rightFragments[i], stringComparison))
|
||||
{
|
||||
@@ -372,12 +372,12 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
var newFragments = new List<string>();
|
||||
|
||||
for (int j = i; j < rightFragments.Length; j++)
|
||||
for (var j = i; j < rightFragments.Length; j++)
|
||||
{
|
||||
newFragments.Add("..");
|
||||
}
|
||||
|
||||
for (int j = i; j < leftFragments.Length; j++)
|
||||
for (var j = i; j < leftFragments.Length; j++)
|
||||
{
|
||||
newFragments.Add(leftFragments[j]);
|
||||
}
|
||||
|
||||
@@ -36,14 +36,14 @@ namespace NzbDrone.Common.Extensions
|
||||
|
||||
public static bool IsValidDate(this string dateTime)
|
||||
{
|
||||
DateTime.TryParse(dateTime, out DateTime result);
|
||||
DateTime.TryParse(dateTime, out var result);
|
||||
|
||||
return !result.Equals(default(DateTime));
|
||||
}
|
||||
|
||||
public static bool IsFutureDate(this string dateTime)
|
||||
{
|
||||
DateTime.TryParse(dateTime, out DateTime result);
|
||||
DateTime.TryParse(dateTime, out var result);
|
||||
|
||||
return !result.Equals(default(DateTime)) && result.After(DateTime.Now);
|
||||
}
|
||||
|
||||
@@ -126,9 +126,9 @@ namespace NzbDrone.Common.Extensions
|
||||
|
||||
private static IEnumerable<T> InternalDropLast<T>(IEnumerable<T> source, int n)
|
||||
{
|
||||
Queue<T> buffer = new Queue<T>(n + 1);
|
||||
var buffer = new Queue<T>(n + 1);
|
||||
|
||||
foreach (T x in source)
|
||||
foreach (var x in source)
|
||||
{
|
||||
buffer.Enqueue(x);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace NzbDrone.Common.Extensions
|
||||
return text.Length * costDelete;
|
||||
}
|
||||
|
||||
int[] matrix = new int[other.Length + 1];
|
||||
var matrix = new int[other.Length + 1];
|
||||
|
||||
for (var i = 1; i < matrix.Length; i++)
|
||||
{
|
||||
@@ -30,13 +30,13 @@ namespace NzbDrone.Common.Extensions
|
||||
|
||||
for (var i = 0; i < text.Length; i++)
|
||||
{
|
||||
int topLeft = matrix[0];
|
||||
var topLeft = matrix[0];
|
||||
matrix[0] = matrix[0] + costDelete;
|
||||
|
||||
for (var j = 0; j < other.Length; j++)
|
||||
{
|
||||
int top = matrix[j];
|
||||
int left = matrix[j + 1];
|
||||
var top = matrix[j];
|
||||
var left = matrix[j + 1];
|
||||
|
||||
var sumIns = top + costInsert;
|
||||
var sumDel = left + costDelete;
|
||||
|
||||
@@ -198,13 +198,13 @@ namespace NzbDrone.Common.Extensions
|
||||
|
||||
public static string CleanFileName(this string name)
|
||||
{
|
||||
string result = name;
|
||||
var result = name;
|
||||
string[] badCharacters = { "\\", "/", "<", ">", "?", "*", ":", "|", "\"" };
|
||||
string[] goodCharacters = { "+", "+", "", "", "!", "-", "-", "", "" };
|
||||
|
||||
result = result.Replace(": ", " - ");
|
||||
|
||||
for (int i = 0; i < badCharacters.Length; i++)
|
||||
for (var i = 0; i < badCharacters.Length; i++)
|
||||
{
|
||||
result = result.Replace(badCharacters[i], goodCharacters[i]);
|
||||
}
|
||||
|
||||
@@ -6,9 +6,7 @@ namespace NzbDrone.Common.Extensions
|
||||
{
|
||||
public static int? ParseInt32(this string source)
|
||||
{
|
||||
int result;
|
||||
|
||||
if (int.TryParse(source, out result))
|
||||
if (int.TryParse(source, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
@@ -18,9 +16,7 @@ namespace NzbDrone.Common.Extensions
|
||||
|
||||
public static long? ParseInt64(this string source)
|
||||
{
|
||||
long result;
|
||||
|
||||
if (long.TryParse(source, out result))
|
||||
if (long.TryParse(source, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
@@ -30,9 +26,7 @@ namespace NzbDrone.Common.Extensions
|
||||
|
||||
public static double? ParseDouble(this string source)
|
||||
{
|
||||
double result;
|
||||
|
||||
if (double.TryParse(source.Replace(',', '.'), NumberStyles.Number, CultureInfo.InvariantCulture, out result))
|
||||
if (double.TryParse(source.Replace(',', '.'), NumberStyles.Number, CultureInfo.InvariantCulture, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@ namespace NzbDrone.Common
|
||||
{
|
||||
public static string CalculateCrc(string input)
|
||||
{
|
||||
uint mCrc = 0xffffffff;
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(input);
|
||||
foreach (byte myByte in bytes)
|
||||
var mCrc = 0xffffffff;
|
||||
var bytes = Encoding.UTF8.GetBytes(input);
|
||||
foreach (var myByte in bytes)
|
||||
{
|
||||
mCrc ^= (uint)myByte << 24;
|
||||
for (var i = 0; i < 8; i++)
|
||||
|
||||
@@ -127,7 +127,7 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
|
||||
headers.Add(responseMessage.Content.Headers.ToNameValueCollection());
|
||||
|
||||
CookieContainer responseCookies = new CookieContainer();
|
||||
var responseCookies = new CookieContainer();
|
||||
|
||||
if (responseMessage.Headers.TryGetValues("Set-Cookie", out var cookieHeaders))
|
||||
{
|
||||
|
||||
@@ -91,6 +91,7 @@ namespace NzbDrone.Common.Http
|
||||
{
|
||||
request.Method = HttpMethod.Get;
|
||||
request.ContentData = null;
|
||||
request.ContentSummary = null;
|
||||
}
|
||||
|
||||
// Save to add to final response
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
public HttpUri(string scheme, string host, int? port, string path, string query, string fragment)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
var builder = new StringBuilder();
|
||||
|
||||
if (scheme.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace NzbDrone.Common.Http.Proxy
|
||||
if (!string.IsNullOrWhiteSpace(BypassFilter))
|
||||
{
|
||||
var hostlist = BypassFilter.Split(',');
|
||||
for (int i = 0; i < hostlist.Length; i++)
|
||||
for (var i = 0; i < hostlist.Length; i++)
|
||||
{
|
||||
if (hostlist[i].StartsWith("*"))
|
||||
{
|
||||
|
||||
@@ -9,61 +9,61 @@ namespace NzbDrone.Common.Instrumentation
|
||||
{
|
||||
private static readonly Regex[] CleansingRules =
|
||||
{
|
||||
// Url
|
||||
new Regex(@"(?<=[?&: ;])(apikey|api_key|(?:(?:access|api)[-_]?)?token|pass(?:key|wd)?|auth|authkey|user|u?id|api|[a-z_]*apikey|account|pid|pwd)=(?<secret>[^&=""]+?)(?=[ ""&=]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=[?& ;])[^=]*?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"rss\.torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"rss\.torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", 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(@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=authkey = "")(?<secret>[^&=]+?)(?="")", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=beyond-hd\.[a-z]+/api/torrents/)(?<secret>[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=beyond-hd\.[a-z]+/torrent/download/[\w\d-]+[.]\d+[.])(?<secret>[a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// Url
|
||||
new (@"(?<=[?&: ;])(apikey|api_key|(?:(?:access|api)[-_]?)?token|pass(?:key|wd)?|auth|authkey|user|u?id|api|[a-z_]*apikey|account|pid|pwd)=(?<secret>[^&=""]+?)(?=[ ""&=]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"(?<=[?& ;])[^=]*?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"rss(24h)?\.torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
|
||||
new (@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"(?<=authkey = "")(?<secret>[^&=]+?)(?="")", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"(?<=beyond-hd\.[a-z]+/api/torrents/)(?<secret>[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"(?<=beyond-hd\.[a-z]+/torrent/download/[\w\d-]+[.]\d+[.])(?<secret>[a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// UNIT3D
|
||||
new Regex(@"(?<=[a-z0-9-]+\.[a-z]+/torrent/download/\d+\.)(?<secret>[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// UNIT3D
|
||||
new (@"(?<=[a-z0-9-]+\.[a-z]+/torrent/download/\d+\.)(?<secret>[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// Path
|
||||
new Regex(@"""C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"""/home/(?<secret>[^/""]+?)(/|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// Path
|
||||
new (@"""C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"""/(home|Users)/(?<secret>[^/""]+?)(/|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// 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"),
|
||||
// Trackers Announce Keys; Designed for Qbit Json; should work for all in theory
|
||||
new (@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
|
||||
|
||||
// NzbGet
|
||||
new Regex(@"""Name""\s*:\s*""[^""]*(username|password)""\s*,\s*""Value""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// NzbGet
|
||||
new (@"""Name""\s*:\s*""[^""]*(username|password)""\s*,\s*""Value""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// Sabnzbd
|
||||
new Regex(@"""[^""]*(username|password|api_?key|nzb_key)""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"""email_(account|to|from|pwd)""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// Sabnzbd
|
||||
new (@"""[^""]*(username|password|api_?key|nzb_key)""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"""email_(account|to|from|pwd)""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// uTorrent
|
||||
new Regex(@"\[""[a-z._]*(username|password)"",\d,""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"\[""(boss_key|boss_key_salt|proxy\.proxy)"",\d,""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// uTorrent
|
||||
new (@"\[""[a-z._]*(username|password)"",\d,""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"\[""(boss_key|boss_key_salt|proxy\.proxy)"",\d,""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// Deluge
|
||||
new Regex(@"auth.login\(""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// Deluge
|
||||
new (@"auth.login\(""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// BroadcastheNet (;torrent_pass|torrents_notify_ is for MTV)
|
||||
new Regex(@"""?method""?\s*:\s*""(getTorrents)"",\s*""?params""?\s*:\s*\[\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"getTorrents\(""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=\?|&|;|=)(authkey|torrent_pass|torrents_notify)[_=](?<secret>[^&=]+?)(?=""|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// BroadcastheNet (;torrent_pass|torrents_notify_ is for MTV)
|
||||
new (@"""?method""?\s*:\s*""(getTorrents)"",\s*""?params""?\s*:\s*\[\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"getTorrents\(""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"(?<=\?|&|;|=)(authkey|torrent_pass|torrents_notify)[_=](?<secret>[^&=]+?)(?=""|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// Plex
|
||||
new Regex(@"(?<=\?|&)(X-Plex-Client-Identifier|X-Plex-Token)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// Plex
|
||||
new (@"(?<=\?|&)(X-Plex-Client-Identifier|X-Plex-Token)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// Indexer Responses
|
||||
new Regex(@"(?:avistaz|exoticaz|cinemaz|privatehd)\.[a-z]{2,3}/rss/download/(?<secret>[^&=]+?)/(?<secret>[^&=]+?)\.torrent", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?:animebytes)\.[a-z]{2,3}/torrent/[0-9]+/download/(?<secret>[^&=]+?)[""]", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@",""info_hash"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"""token"":""(?<secret>[^&=]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@",""pass[- _]?key"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@",""rss[- _]?key"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// Indexer Responses
|
||||
new (@"(?:avistaz|exoticaz|cinemaz|privatehd)\.[a-z]{2,3}/rss/download/(?<secret>[^&=]+?)/(?<secret>[^&=]+?)\.torrent", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"(?:animebytes)\.[a-z]{2,3}/torrent/[0-9]+/download/(?<secret>[^&=]+?)[""]", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@",""info_hash"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"""token"":""(?<secret>[^&=]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@",""pass[- _]?key"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@",""rss[- _]?key"":""(?<secret>[^&=]+?)"",", 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 (@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);
|
||||
|
||||
public static string Cleanse(string message)
|
||||
{
|
||||
@@ -75,15 +75,15 @@ namespace NzbDrone.Common.Instrumentation
|
||||
foreach (var regex in CleansingRules)
|
||||
{
|
||||
message = regex.Replace(message, m =>
|
||||
{
|
||||
var value = m.Value;
|
||||
foreach (var capture in m.Groups["secret"].Captures.OfType<Capture>().Reverse())
|
||||
{
|
||||
var value = m.Value;
|
||||
foreach (var capture in m.Groups["secret"].Captures.OfType<Capture>().Reverse())
|
||||
{
|
||||
value = value.Replace(capture.Index - m.Index, capture.Length, "(removed)");
|
||||
}
|
||||
value = value.Replace(capture.Index - m.Index, capture.Length, "(removed)");
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
message = CleanseRemoteIPRegex.Replace(message, CleanseRemoteIP);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NzbDrone.Common.Serializer;
|
||||
|
||||
namespace NzbDrone.Common.Instrumentation
|
||||
@@ -16,7 +16,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
}
|
||||
}
|
||||
|
||||
foreach (JToken token in json)
|
||||
foreach (var token in json)
|
||||
{
|
||||
Visit(token);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
|
||||
private static void RegisterDebugger()
|
||||
{
|
||||
DebuggerTarget target = new DebuggerTarget();
|
||||
var target = new DebuggerTarget();
|
||||
target.Name = "debuggerLogger";
|
||||
target.Layout = "[${level}] [${threadid}] ${logger}: ${message} ${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
|
||||
|
||||
@@ -108,9 +108,10 @@ namespace NzbDrone.Common.Instrumentation
|
||||
{
|
||||
LogManager.Setup().LoadConfiguration(c =>
|
||||
{
|
||||
c.ForLogger("System.*").WriteToNil(LogLevel.Warn);
|
||||
c.ForLogger("Microsoft.*").WriteToNil(LogLevel.Warn);
|
||||
c.ForLogger("Microsoft.Hosting.Lifetime*").WriteToNil(LogLevel.Info);
|
||||
c.ForLogger("System*").WriteToNil(LogLevel.Warn);
|
||||
c.ForLogger("Microsoft*").WriteToNil(LogLevel.Warn);
|
||||
c.ForLogger("Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware").WriteToNil(LogLevel.Fatal);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace NzbDrone.Common.OAuth
|
||||
public virtual string Version { get; set; }
|
||||
public virtual string SessionHandle { get; set; }
|
||||
|
||||
/// <seealso cref="http://oauth.net/core/1.0#request_urls"/>
|
||||
/// <seealso href="http://oauth.net/core/1.0#request_urls"/>
|
||||
public virtual string RequestUrl { get; set; }
|
||||
|
||||
#if !WINRT
|
||||
|
||||
@@ -313,7 +313,7 @@ namespace NzbDrone.Common.Processes
|
||||
processInfo = new ProcessInfo();
|
||||
processInfo.Id = process.Id;
|
||||
processInfo.Name = process.ProcessName;
|
||||
processInfo.StartPath = GetExeFileName(process);
|
||||
processInfo.StartPath = process.MainModule.FileName;
|
||||
|
||||
if (process.Id != GetCurrentProcessId() && process.HasExited)
|
||||
{
|
||||
@@ -328,28 +328,9 @@ namespace NzbDrone.Common.Processes
|
||||
return processInfo;
|
||||
}
|
||||
|
||||
private static string GetExeFileName(Process process)
|
||||
{
|
||||
if (process.MainModule.FileName != "mono.exe")
|
||||
{
|
||||
return process.MainModule.FileName;
|
||||
}
|
||||
|
||||
return process.Modules.Cast<ProcessModule>().FirstOrDefault(module => module.ModuleName.ToLower().EndsWith(".exe")).FileName;
|
||||
}
|
||||
|
||||
private List<Process> GetProcessesByName(string name)
|
||||
{
|
||||
//TODO: move this to an OS specific class
|
||||
var monoProcesses = Process.GetProcessesByName("mono")
|
||||
.Union(Process.GetProcessesByName("mono-sgen"))
|
||||
.Where(process =>
|
||||
process.Modules.Cast<ProcessModule>()
|
||||
.Any(module =>
|
||||
module.ModuleName.ToLower() == name.ToLower() + ".exe"));
|
||||
|
||||
var processes = Process.GetProcessesByName(name)
|
||||
.Union(monoProcesses).ToList();
|
||||
var processes = Process.GetProcessesByName(name).ToList();
|
||||
|
||||
_logger.Debug("Found {0} processes with the name: {1}", processes.Count, name);
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NLog" Version="5.1.0" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.2.0" />
|
||||
<PackageReference Include="NLog" Version="5.2.0" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.0" />
|
||||
<PackageReference Include="Npgsql" Version="5.0.11" />
|
||||
<PackageReference Include="Sentry" Version="3.29.1" />
|
||||
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace NzbDrone.Common.Serializer
|
||||
{
|
||||
@@ -60,7 +60,7 @@ namespace NzbDrone.Common.Serializer
|
||||
|
||||
public virtual void Visit(JArray json)
|
||||
{
|
||||
foreach (JToken token in json)
|
||||
foreach (var token in json)
|
||||
{
|
||||
Visit(token);
|
||||
}
|
||||
@@ -72,7 +72,7 @@ namespace NzbDrone.Common.Serializer
|
||||
|
||||
public virtual void Visit(JObject json)
|
||||
{
|
||||
foreach (JProperty property in json.Properties())
|
||||
foreach (var property in json.Properties())
|
||||
{
|
||||
Visit(property);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace NzbDrone.Common.Serializer
|
||||
var enumText = value.ToString();
|
||||
var builder = new StringBuilder(enumText.Length + 4);
|
||||
builder.Append(char.ToLower(enumText[0]));
|
||||
for (int i = 1; i < enumText.Length; i++)
|
||||
for (var i = 1; i < enumText.Length; i++)
|
||||
{
|
||||
if (char.IsUpper(enumText[i]))
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace NzbDrone.Common.Serializer
|
||||
{
|
||||
try
|
||||
{
|
||||
Version v = new Version(reader.GetString());
|
||||
var v = new Version(reader.GetString());
|
||||
return v;
|
||||
}
|
||||
catch (Exception)
|
||||
|
||||
@@ -137,7 +137,7 @@ namespace NzbDrone.Common.TPL
|
||||
/// <returns>An enumerable of the tasks currently scheduled.</returns>
|
||||
protected sealed override IEnumerable<Task> GetScheduledTasks()
|
||||
{
|
||||
bool lockTaken = false;
|
||||
var lockTaken = false;
|
||||
try
|
||||
{
|
||||
Monitor.TryEnter(_tasks, ref lockTaken);
|
||||
|
||||
@@ -110,7 +110,7 @@ namespace NzbDrone.Console
|
||||
}
|
||||
|
||||
System.Console.WriteLine("Non-recoverable failure, waiting for user intervention...");
|
||||
for (int i = 0; i < 3600; i++)
|
||||
for (var i = 0; i < 3600; i++)
|
||||
{
|
||||
System.Threading.Thread.Sleep(1000);
|
||||
if (!System.Console.IsInputRedirected && System.Console.KeyAvailable)
|
||||
|
||||
@@ -197,7 +197,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||
|
||||
Subject.SetFields(_basicList, x => x.Interval);
|
||||
|
||||
for (int i = 0; i < _basicList.Count; i++)
|
||||
for (var i = 0; i < _basicList.Count; i++)
|
||||
{
|
||||
_basicList[i].LastExecution = executionBackup[i];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using FluentAssertions;
|
||||
@@ -57,7 +57,7 @@ namespace NzbDrone.Core.Test
|
||||
[Test]
|
||||
public void ToBestDateTime_DayOfWeek()
|
||||
{
|
||||
for (int i = 2; i < 7; i++)
|
||||
for (var i = 2; i < 7; i++)
|
||||
{
|
||||
var dateTime = DateTime.Today.AddDays(i);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.Newznab;
|
||||
using NzbDrone.Core.Lifecycle;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerTests
|
||||
{
|
||||
@@ -31,13 +32,15 @@ namespace NzbDrone.Core.Test.IndexerTests
|
||||
Mocker.SetConstant<IIndexerRepository>(repo);
|
||||
|
||||
var existingIndexers = Builder<IndexerDefinition>.CreateNew().BuildNew();
|
||||
existingIndexers.ConfigContract = typeof(NewznabSettings).Name;
|
||||
existingIndexers.ConfigContract = nameof(NewznabSettings);
|
||||
|
||||
repo.Insert(existingIndexers);
|
||||
|
||||
Subject.Handle(new ApplicationStartedEvent());
|
||||
|
||||
AllStoredModels.Should().NotContain(c => c.Id == existingIndexers.Id);
|
||||
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Test.IndexerTests.PTPTests
|
||||
{
|
||||
var authResponse = new PassThePopcornAuthResponse { Result = "Ok" };
|
||||
|
||||
System.IO.StringWriter authStream = new System.IO.StringWriter();
|
||||
var authStream = new System.IO.StringWriter();
|
||||
Json.Serialize(authResponse, authStream);
|
||||
var responseJson = ReadAllText(fileName);
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerTests.RarbgTests
|
||||
{
|
||||
[Obsolete("Rarbg has shutdown 2023-05-31")]
|
||||
[TestFixture]
|
||||
public class RarbgFixture : CoreTest<Rarbg>
|
||||
{
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace NzbDrone.Core.Test.InstrumentationTests
|
||||
public void write_long_log()
|
||||
{
|
||||
var message = string.Empty;
|
||||
for (int i = 0; i < 100; i++)
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
message += Guid.NewGuid();
|
||||
}
|
||||
|
||||
@@ -63,5 +63,22 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
{
|
||||
ParseUtil.GetLongFromString(original).Should().Be(parsedInt);
|
||||
}
|
||||
|
||||
[TestCase("tt0183790", "tt0183790")]
|
||||
[TestCase("0183790", "tt0183790")]
|
||||
[TestCase("183790", "tt0183790")]
|
||||
[TestCase("tt10001870", "tt10001870")]
|
||||
[TestCase("10001870", "tt10001870")]
|
||||
[TestCase("tt", null)]
|
||||
[TestCase("tt0", null)]
|
||||
[TestCase("abc", null)]
|
||||
[TestCase("abc0", null)]
|
||||
[TestCase("0", null)]
|
||||
[TestCase("", null)]
|
||||
[TestCase(null, null)]
|
||||
public void should_parse_full_imdb_id_from_string(string input, string expected)
|
||||
{
|
||||
ParseUtil.GetFullImdbId(input).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace NzbDrone.Core.Annotations
|
||||
public string Label { get; set; }
|
||||
public string Unit { get; set; }
|
||||
public string HelpText { get; set; }
|
||||
public string HelpTextWarning { get; set; }
|
||||
public string HelpLink { get; set; }
|
||||
public FieldType Type { get; set; }
|
||||
public bool Advanced { get; set; }
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace NzbDrone.Core.Applications
|
||||
yield return new ApplicationDefinition
|
||||
{
|
||||
Name = GetType().Name,
|
||||
SyncLevel = ApplicationSyncLevel.AddOnly,
|
||||
SyncLevel = ApplicationSyncLevel.FullSync,
|
||||
Implementation = GetType().Name,
|
||||
Settings = config
|
||||
};
|
||||
|
||||
@@ -6,6 +6,6 @@ namespace NzbDrone.Core.Applications
|
||||
{
|
||||
public ApplicationSyncLevel SyncLevel { get; set; }
|
||||
|
||||
public override bool Enable => SyncLevel == ApplicationSyncLevel.AddOnly || SyncLevel == ApplicationSyncLevel.FullSync;
|
||||
public override bool Enable => SyncLevel is ApplicationSyncLevel.AddOnly or ApplicationSyncLevel.FullSync;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,8 +48,7 @@ namespace NzbDrone.Core.Applications
|
||||
|
||||
foreach (var application in applications)
|
||||
{
|
||||
ApplicationStatus blockedApplicationStatus;
|
||||
if (blockedApplications.TryGetValue(application.Definition.Id, out blockedApplicationStatus))
|
||||
if (blockedApplications.TryGetValue(application.Definition.Id, out var blockedApplicationStatus))
|
||||
{
|
||||
_logger.Debug("Temporarily ignoring application {0} till {1} due to recent failures.", application.Definition.Name, blockedApplicationStatus.DisabledTill.Value.ToLocalTime());
|
||||
continue;
|
||||
|
||||
@@ -78,6 +78,14 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
|
||||
var lazyLibrarianIndexer = BuildLazyLibrarianIndexer(indexer, indexer.Protocol);
|
||||
|
||||
var remoteIndexer = _lazyLibrarianV1Proxy.AddIndexer(lazyLibrarianIndexer, Settings);
|
||||
|
||||
if (remoteIndexer == null)
|
||||
{
|
||||
_logger.Debug("Failed to add {0} [{1}]", indexer.Name, indexer.Id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_appIndexerMapService.Insert(new AppIndexerMap { AppId = Definition.Id, IndexerId = indexer.Id, RemoteIndexerName = $"{remoteIndexer.Type},{remoteIndexer.Name}" });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FluentValidation.Results;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
@@ -49,10 +48,10 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
{
|
||||
failures.AddIfNotNull(_lidarrV1Proxy.TestConnection(BuildLidarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
|
||||
}
|
||||
catch (WebException ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unable to send test message");
|
||||
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Lidarr"));
|
||||
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Lidarr. {ex.Message}"));
|
||||
}
|
||||
|
||||
return new ValidationResult(failures);
|
||||
@@ -61,7 +60,7 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
public override List<AppIndexerMap> GetIndexerMappings()
|
||||
{
|
||||
var indexers = _lidarrV1Proxy.GetIndexers(Settings)
|
||||
.Where(i => i.Implementation == "Newznab" || i.Implementation == "Torznab");
|
||||
.Where(i => i.Implementation is "Newznab" or "Torznab");
|
||||
|
||||
var mappings = new List<AppIndexerMap>();
|
||||
|
||||
@@ -96,6 +95,14 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
var lidarrIndexer = BuildLidarrIndexer(indexer, indexer.Protocol);
|
||||
|
||||
var remoteIndexer = _lidarrV1Proxy.AddIndexer(lidarrIndexer, Settings);
|
||||
|
||||
if (remoteIndexer == null)
|
||||
{
|
||||
_logger.Debug("Failed to add {0} [{1}]", indexer.Name, indexer.Id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_appIndexerMapService.Insert(new AppIndexerMap { AppId = Definition.Id, IndexerId = indexer.Id, RemoteIndexerId = remoteIndexer.Id });
|
||||
}
|
||||
|
||||
@@ -174,7 +181,13 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
{
|
||||
var cacheKey = $"{Settings.BaseUrl}";
|
||||
var schemas = _schemaCache.Get(cacheKey, () => _lidarrV1Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
|
||||
var syncFields = new[] { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.discographySeedTime" };
|
||||
var syncFields = new List<string> { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.discographySeedTime" };
|
||||
|
||||
if (id == 0)
|
||||
{
|
||||
// Ensuring backward compatibility with older versions on first sync
|
||||
syncFields.AddRange(new List<string> { "earlyReleaseLimit", "additionalParameters" });
|
||||
}
|
||||
|
||||
var newznab = schemas.First(i => i.Implementation == "Newznab");
|
||||
var torznab = schemas.First(i => i.Implementation == "Torznab");
|
||||
|
||||
@@ -23,8 +23,11 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
|
||||
public class LidarrV1Proxy : ILidarrV1Proxy
|
||||
{
|
||||
private static Version MinimumApplicationVersion => new (1, 0, 2, 0);
|
||||
|
||||
private const string AppApiRoute = "/api/v1";
|
||||
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
|
||||
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -102,7 +105,17 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
|
||||
try
|
||||
{
|
||||
Execute<LidarrIndexer>(request);
|
||||
var applicationVersion = _httpClient.Post<LidarrIndexer>(request).Headers.GetSingleValue("X-Application-Version");
|
||||
|
||||
if (applicationVersion == null)
|
||||
{
|
||||
return new ValidationFailure(string.Empty, "Failed to fetch Lidarr version");
|
||||
}
|
||||
|
||||
if (new Version(applicationVersion) < MinimumApplicationVersion)
|
||||
{
|
||||
return new ValidationFailure(string.Empty, $"Lidarr version should be at least {MinimumApplicationVersion.ToString(3)}. Version reported is {applicationVersion}", applicationVersion);
|
||||
}
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
|
||||
@@ -78,6 +78,14 @@ namespace NzbDrone.Core.Applications.Mylar
|
||||
var mylarIndexer = BuildMylarIndexer(indexer, indexer.Protocol);
|
||||
|
||||
var remoteIndexer = _mylarV3Proxy.AddIndexer(mylarIndexer, Settings);
|
||||
|
||||
if (remoteIndexer == null)
|
||||
{
|
||||
_logger.Debug("Failed to add {0} [{1}]", indexer.Name, indexer.Id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_appIndexerMapService.Insert(new AppIndexerMap { AppId = Definition.Id, IndexerId = indexer.Id, RemoteIndexerName = $"{remoteIndexer.Type},{remoteIndexer.Name}" });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FluentValidation.Results;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
@@ -49,10 +48,10 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
{
|
||||
failures.AddIfNotNull(_radarrV3Proxy.TestConnection(BuildRadarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
|
||||
}
|
||||
catch (WebException ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unable to send test message");
|
||||
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Radarr"));
|
||||
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Radarr. {ex.Message}"));
|
||||
}
|
||||
|
||||
return new ValidationResult(failures);
|
||||
@@ -61,7 +60,7 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
public override List<AppIndexerMap> GetIndexerMappings()
|
||||
{
|
||||
var indexers = _radarrV3Proxy.GetIndexers(Settings)
|
||||
.Where(i => i.Implementation == "Newznab" || i.Implementation == "Torznab");
|
||||
.Where(i => i.Implementation is "Newznab" or "Torznab");
|
||||
|
||||
var mappings = new List<AppIndexerMap>();
|
||||
|
||||
@@ -96,6 +95,14 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
var radarrIndexer = BuildRadarrIndexer(indexer, indexer.Protocol);
|
||||
|
||||
var remoteIndexer = _radarrV3Proxy.AddIndexer(radarrIndexer, Settings);
|
||||
|
||||
if (remoteIndexer == null)
|
||||
{
|
||||
_logger.Debug("Failed to add {0} [{1}]", indexer.Name, indexer.Id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_appIndexerMapService.Insert(new AppIndexerMap { AppId = Definition.Id, IndexerId = indexer.Id, RemoteIndexerId = remoteIndexer.Id });
|
||||
}
|
||||
|
||||
@@ -174,7 +181,13 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
{
|
||||
var cacheKey = $"{Settings.BaseUrl}";
|
||||
var schemas = _schemaCache.Get(cacheKey, () => _radarrV3Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
|
||||
var syncFields = new[] { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime" };
|
||||
var syncFields = new List<string> { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime" };
|
||||
|
||||
if (id == 0)
|
||||
{
|
||||
// Ensuring backward compatibility with older versions on first sync
|
||||
syncFields.AddRange(new List<string> { "multiLanguages", "removeYear", "requiredFlags", "additionalParameters" });
|
||||
}
|
||||
|
||||
var newznab = schemas.First(i => i.Implementation == "Newznab");
|
||||
var torznab = schemas.First(i => i.Implementation == "Torznab");
|
||||
|
||||
@@ -23,8 +23,12 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
|
||||
public class RadarrV3Proxy : IRadarrV3Proxy
|
||||
{
|
||||
private static Version MinimumApplicationV4Version => new (4, 0, 4, 0);
|
||||
private static Version MinimumApplicationV3Version => new (3, 1, 1, 0);
|
||||
|
||||
private const string AppApiRoute = "/api/v3";
|
||||
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
|
||||
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -102,7 +106,29 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
|
||||
try
|
||||
{
|
||||
Execute<RadarrIndexer>(request);
|
||||
var applicationVersion = _httpClient.Post<RadarrIndexer>(request).Headers.GetSingleValue("X-Application-Version");
|
||||
|
||||
if (applicationVersion == null)
|
||||
{
|
||||
return new ValidationFailure(string.Empty, "Failed to fetch Radarr version");
|
||||
}
|
||||
|
||||
var version = new Version(applicationVersion);
|
||||
|
||||
if (version.Major == 3)
|
||||
{
|
||||
if (version < MinimumApplicationV3Version)
|
||||
{
|
||||
return new ValidationFailure(string.Empty, $"Radarr version should be at least {MinimumApplicationV3Version.ToString(3)}. Version reported is {applicationVersion}", applicationVersion);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (version < MinimumApplicationV4Version)
|
||||
{
|
||||
return new ValidationFailure(string.Empty, $"Radarr version should be at least {MinimumApplicationV4Version.ToString(3)}. Version reported is {applicationVersion}", applicationVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
|
||||
@@ -96,6 +96,14 @@ namespace NzbDrone.Core.Applications.Readarr
|
||||
var readarrIndexer = BuildReadarrIndexer(indexer, indexer.Protocol);
|
||||
|
||||
var remoteIndexer = _readarrV1Proxy.AddIndexer(readarrIndexer, Settings);
|
||||
|
||||
if (remoteIndexer == null)
|
||||
{
|
||||
_logger.Debug("Failed to add {0} [{1}]", indexer.Name, indexer.Id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_appIndexerMapService.Insert(new AppIndexerMap { AppId = Definition.Id, IndexerId = indexer.Id, RemoteIndexerId = remoteIndexer.Id });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FluentValidation.Results;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
@@ -49,10 +48,10 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
{
|
||||
failures.AddIfNotNull(_sonarrV3Proxy.TestConnection(BuildSonarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
|
||||
}
|
||||
catch (WebException ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unable to send test message");
|
||||
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Sonarr"));
|
||||
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Sonarr. {ex.Message}"));
|
||||
}
|
||||
|
||||
return new ValidationResult(failures);
|
||||
@@ -61,7 +60,7 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
public override List<AppIndexerMap> GetIndexerMappings()
|
||||
{
|
||||
var indexers = _sonarrV3Proxy.GetIndexers(Settings)
|
||||
.Where(i => i.Implementation == "Newznab" || i.Implementation == "Torznab");
|
||||
.Where(i => i.Implementation is "Newznab" or "Torznab");
|
||||
|
||||
var mappings = new List<AppIndexerMap>();
|
||||
|
||||
@@ -97,6 +96,14 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
var sonarrIndexer = BuildSonarrIndexer(indexer, indexer.Protocol);
|
||||
|
||||
var remoteIndexer = _sonarrV3Proxy.AddIndexer(sonarrIndexer, Settings);
|
||||
|
||||
if (remoteIndexer == null)
|
||||
{
|
||||
_logger.Debug("Failed to add {0} [{1}]", indexer.Name, indexer.Id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_appIndexerMapService.Insert(new AppIndexerMap { AppId = Definition.Id, IndexerId = indexer.Id, RemoteIndexerId = remoteIndexer.Id });
|
||||
}
|
||||
|
||||
@@ -176,7 +183,13 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
{
|
||||
var cacheKey = $"{Settings.BaseUrl}";
|
||||
var schemas = _schemaCache.Get(cacheKey, () => _sonarrV3Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
|
||||
var syncFields = new[] { "baseUrl", "apiPath", "apiKey", "categories", "animeCategories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.seasonPackSeedTime" };
|
||||
var syncFields = new List<string> { "baseUrl", "apiPath", "apiKey", "categories", "animeCategories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.seasonPackSeedTime" };
|
||||
|
||||
if (id == 0)
|
||||
{
|
||||
// Ensuring backward compatibility with older versions on first sync
|
||||
syncFields.AddRange(new List<string> { "animeStandardFormatSearch", "additionalParameters" });
|
||||
}
|
||||
|
||||
var newznab = schemas.First(i => i.Implementation == "Newznab");
|
||||
var torznab = schemas.First(i => i.Implementation == "Torznab");
|
||||
|
||||
@@ -23,8 +23,11 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
|
||||
public class SonarrV3Proxy : ISonarrV3Proxy
|
||||
{
|
||||
private static Version MinimumApplicationVersion => new (3, 0, 5, 0);
|
||||
|
||||
private const string AppApiRoute = "/api/v3";
|
||||
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
|
||||
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -102,7 +105,17 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
|
||||
try
|
||||
{
|
||||
Execute<SonarrIndexer>(request);
|
||||
var applicationVersion = _httpClient.Post<SonarrIndexer>(request).Headers.GetSingleValue("X-Application-Version");
|
||||
|
||||
if (applicationVersion == null)
|
||||
{
|
||||
return new ValidationFailure(string.Empty, "Failed to fetch Sonarr version");
|
||||
}
|
||||
|
||||
if (new Version(applicationVersion) < MinimumApplicationVersion)
|
||||
{
|
||||
return new ValidationFailure(string.Empty, $"Sonarr version should be at least {MinimumApplicationVersion.ToString(3)}. Version reported is {applicationVersion}", applicationVersion);
|
||||
}
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
|
||||
@@ -96,6 +96,14 @@ namespace NzbDrone.Core.Applications.Whisparr
|
||||
var whisparrIndexer = BuildWhisparrIndexer(indexer, indexer.Protocol);
|
||||
|
||||
var remoteIndexer = _whisparrV3Proxy.AddIndexer(whisparrIndexer, Settings);
|
||||
|
||||
if (remoteIndexer == null)
|
||||
{
|
||||
_logger.Debug("Failed to add {0} [{1}]", indexer.Name, indexer.Id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_appIndexerMapService.Insert(new AppIndexerMap { AppId = Definition.Id, IndexerId = indexer.Id, RemoteIndexerId = remoteIndexer.Id });
|
||||
}
|
||||
|
||||
|
||||
@@ -121,8 +121,7 @@ namespace NzbDrone.Core.Configuration
|
||||
continue;
|
||||
}
|
||||
|
||||
object currentValue;
|
||||
allWithDefaults.TryGetValue(configValue.Key, out currentValue);
|
||||
allWithDefaults.TryGetValue(configValue.Key, out var currentValue);
|
||||
if (currentValue == null)
|
||||
{
|
||||
continue;
|
||||
@@ -145,7 +144,7 @@ namespace NzbDrone.Core.Configuration
|
||||
{
|
||||
const string defaultValue = "*";
|
||||
|
||||
string bindAddress = GetValue("BindAddress", defaultValue);
|
||||
var bindAddress = GetValue("BindAddress", defaultValue);
|
||||
if (string.IsNullOrWhiteSpace(bindAddress))
|
||||
{
|
||||
return defaultValue;
|
||||
|
||||
@@ -53,8 +53,7 @@ namespace NzbDrone.Core.Configuration
|
||||
|
||||
foreach (var configValue in configValues)
|
||||
{
|
||||
object currentValue;
|
||||
allWithDefaults.TryGetValue(configValue.Key, out currentValue);
|
||||
allWithDefaults.TryGetValue(configValue.Key, out var currentValue);
|
||||
if (currentValue == null || configValue.Value == null)
|
||||
{
|
||||
continue;
|
||||
@@ -211,9 +210,7 @@ namespace NzbDrone.Core.Configuration
|
||||
|
||||
EnsureCache();
|
||||
|
||||
string dbValue;
|
||||
|
||||
if (_cache.TryGetValue(key, out dbValue) && dbValue != null && !string.IsNullOrEmpty(dbValue))
|
||||
if (_cache.TryGetValue(key, out var dbValue) && dbValue != null && !string.IsNullOrEmpty(dbValue))
|
||||
{
|
||||
return dbValue;
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
using (var conn = _database.OpenConnection())
|
||||
{
|
||||
using (IDbTransaction tran = conn.BeginTransaction(IsolationLevel.ReadCommitted))
|
||||
using (var tran = conn.BeginTransaction(IsolationLevel.ReadCommitted))
|
||||
{
|
||||
foreach (var model in models)
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace NzbDrone.Core.Datastore.Converters
|
||||
}
|
||||
|
||||
string contract;
|
||||
using (JsonDocument body = JsonDocument.Parse(stringValue))
|
||||
using (var body = JsonDocument.Parse(stringValue))
|
||||
{
|
||||
contract = body.RootElement.GetProperty("name").GetString();
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||
|
||||
public virtual IList<TableDefinition> ReadDbSchema()
|
||||
{
|
||||
IList<TableDefinition> tables = ReadTables();
|
||||
var tables = ReadTables();
|
||||
foreach (var table in tables)
|
||||
{
|
||||
table.Indexes = ReadIndexes(table.SchemaName, table.Name);
|
||||
@@ -264,7 +264,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||
protected virtual IList<IndexDefinition> ReadIndexes(string schemaName, string tableName)
|
||||
{
|
||||
var sqlCommand = string.Format(@"SELECT type, name, sql FROM sqlite_master WHERE tbl_name = '{0}' AND type = 'index' AND name NOT LIKE 'sqlite_auto%';", tableName);
|
||||
DataTable table = Read(sqlCommand).Tables[0];
|
||||
var table = Read(sqlCommand).Tables[0];
|
||||
|
||||
IList<IndexDefinition> indexes = new List<IndexDefinition>();
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user