Compare commits

..

2 Commits

Author SHA1 Message Date
Mark McDowall aa8b83c1e3 fixup! New: Return downloading magnets from Transmission 2024-07-29 22:00:51 -07:00
Mark McDowall bfbfcd1d0a New: Return downloading magnets from Transmission
Closes #7029
2024-07-29 21:07:14 -07:00
131 changed files with 401 additions and 943 deletions

View File

@ -59,7 +59,7 @@ app_guid=$(echo "$app_guid" | tr -d ' ')
app_guid=${app_guid:-media}
echo "This will install [${app^}] to [$bindir] and use [$datadir] for the AppData Directory"
echo "${app^} will run as the user [$app_uid] and group [$app_guid]. By continuing, you've confirmed that the selected user and group will have READ and WRITE access to your Media Library and Download Client Completed Download directories"
echo "${app^} will run as the user [$app_uid] and group [$app_guid]. By continuing, you've confirmed that that user and group will have READ and WRITE access to your Media Library and Download Client Completed Download directories"
read -n 1 -r -s -p $'Press enter to continue or ctrl+c to exit...\n' < /dev/tty
# Create User / Group as needed
@ -114,7 +114,7 @@ case "$ARCH" in
esac
echo ""
echo "Removing previous tarballs"
# -f to Force so we fail if it doesn't exist
# -f to Force so we fail if it doesnt exist
rm -f "${app^}".*.tar.gz
echo ""
echo "Downloading..."

View File

@ -359,16 +359,11 @@ module.exports = {
],
rules: Object.assign(typescriptEslintRecommended.rules, {
'@typescript-eslint/no-unused-vars': [
'error',
{
args: 'after-used',
argsIgnorePattern: '^_',
ignoreRestSiblings: true
}
],
'@typescript-eslint/explicit-function-return-type': 'off',
'no-shadow': 'off',
// These should be enabled after cleaning things up
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/explicit-function-return-type': 'off',
'react/prop-types': 'off',
'prettier/prettier': 'error',
'simple-import-sort/imports': [
'error',
@ -381,41 +376,7 @@ module.exports = {
['^@?\\w', `^(${dirs})(/.*|$)`, '^\\.', '^\\..*css$']
]
}
],
// React Hooks
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'error',
// React
'react/function-component-definition': 'error',
'react/hook-use-state': 'error',
'react/jsx-boolean-value': ['error', 'always'],
'react/jsx-curly-brace-presence': [
'error',
{ props: 'never', children: 'never' }
],
'react/jsx-fragments': 'error',
'react/jsx-handler-names': [
'error',
{
eventHandlerPrefix: 'on',
eventHandlerPropPrefix: 'on'
}
],
'react/jsx-no-bind': ['error', { ignoreRefs: true }],
'react/jsx-no-useless-fragment': ['error', { allowExpressions: true }],
'react/jsx-pascal-case': ['error', { allowAllCaps: true }],
'react/jsx-sort-props': [
'error',
{
callbacksLast: true,
noSortAlphabetically: true,
reservedFirst: true
}
],
'react/prop-types': 'off',
'react/self-closing-comp': 'error'
]
})
},
{

View File

@ -59,7 +59,6 @@ function Blocklist() {
sortKey,
sortDirection,
page,
pageSize,
totalPages,
totalRecords,
isRemoving,
@ -224,7 +223,6 @@ function Blocklist() {
<PageToolbarSection alignContent={align.RIGHT}>
<TableOptionsModalWrapper
columns={columns}
pageSize={pageSize}
onTableOptionChange={handleTableOptionChange}
>
<PageToolbarButton
@ -266,7 +264,6 @@ function Blocklist() {
allSelected={allSelected}
allUnselected={allUnselected}
columns={columns}
pageSize={pageSize}
sortKey={sortKey}
sortDirection={sortDirection}
onTableOptionChange={handleTableOptionChange}

View File

@ -53,7 +53,6 @@ function History() {
sortKey,
sortDirection,
page,
pageSize,
totalPages,
totalRecords,
} = useSelector((state: AppState) => state.history);
@ -155,7 +154,6 @@ function History() {
<PageToolbarSection alignContent={align.RIGHT}>
<TableOptionsModalWrapper
columns={columns}
pageSize={pageSize}
onTableOptionChange={handleTableOptionChange}
>
<PageToolbarButton
@ -195,7 +193,6 @@ function History() {
<div>
<Table
columns={columns}
pageSize={pageSize}
sortKey={sortKey}
sortDirection={sortDirection}
onTableOptionChange={handleTableOptionChange}

View File

@ -73,7 +73,6 @@ function Queue() {
sortKey,
sortDirection,
page,
pageSize,
totalPages,
totalRecords,
isGrabbing,
@ -270,10 +269,8 @@ function Queue() {
allSelected={allSelected}
allUnselected={allUnselected}
columns={columns}
pageSize={pageSize}
sortKey={sortKey}
sortDirection={sortDirection}
optionsComponent={QueueOptions}
onTableOptionChange={handleTableOptionChange}
onSelectAllChange={handleSelectAllChange}
onSortPress={handleSortPress}
@ -347,7 +344,6 @@ function Queue() {
<PageToolbarSection alignContent={align.RIGHT}>
<TableOptionsModalWrapper
columns={columns}
pageSize={pageSize}
maxPageSize={200}
optionsComponent={QueueOptions}
onTableOptionChange={handleTableOptionChange}

View File

@ -1,11 +1,11 @@
import React, { useCallback } from 'react';
import React, { Fragment, useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import AppState from 'App/State/AppState';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import { inputTypes } from 'Helpers/Props';
import { gotoQueuePage, setQueueOption } from 'Store/Actions/queueActions';
import { setQueueOption } from 'Store/Actions/queueActions';
import { CheckInputChanged } from 'typings/inputs';
import translate from 'Utilities/String/translate';
@ -22,26 +22,24 @@ function QueueOptions() {
[name]: value,
})
);
if (name === 'includeUnknownSeriesItems') {
dispatch(gotoQueuePage({ page: 1 }));
}
},
[dispatch]
);
return (
<FormGroup>
<FormLabel>{translate('ShowUnknownSeriesItems')}</FormLabel>
<Fragment>
<FormGroup>
<FormLabel>{translate('ShowUnknownSeriesItems')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="includeUnknownSeriesItems"
value={includeUnknownSeriesItems}
helpText={translate('ShowUnknownSeriesItemsHelpText')}
onChange={handleOptionChange}
/>
</FormGroup>
<FormInputGroup
type={inputTypes.CHECK}
name="includeUnknownSeriesItems"
value={includeUnknownSeriesItems}
helpText={translate('ShowUnknownSeriesItemsHelpText')}
onChange={handleOptionChange}
/>
</FormGroup>
</Fragment>
);
}

View File

@ -26,5 +26,4 @@
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 70px;
text-align: right;
}

View File

@ -35,10 +35,6 @@ import getPathWithUrlBase from 'Utilities/getPathWithUrlBase';
import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector';
import MissingConnector from 'Wanted/Missing/MissingConnector';
function RedirectWithUrlBase() {
return <Redirect to={getPathWithUrlBase('/')} />;
}
function AppRoutes() {
return (
<Switch>
@ -55,7 +51,9 @@ function AppRoutes() {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
addUrlBase={false}
render={RedirectWithUrlBase}
render={() => {
return <Redirect to={getPathWithUrlBase('/')} />;
}}
/>
)}
@ -63,9 +61,21 @@ function AppRoutes() {
<Route path="/add/import" component={ImportSeries} />
<Route path="/serieseditor" exact={true} render={RedirectWithUrlBase} />
<Route
path="/serieseditor"
exact={true}
render={() => {
return <Redirect to={getPathWithUrlBase('/')} />;
}}
/>
<Route path="/seasonpass" exact={true} render={RedirectWithUrlBase} />
<Route
path="/seasonpass"
exact={true}
render={() => {
return <Redirect to={getPathWithUrlBase('/')} />;
}}
/>
<Route path="/series/:titleSlug" component={SeriesDetailsPageConnector} />

View File

@ -64,7 +64,7 @@ function ErrorBoundaryError(props: ErrorBoundaryErrorProps) {
<div>{info.componentStack}</div>
)}
<div className={styles.version}>Version: {window.Sonarr.version}</div>
{<div className={styles.version}>Version: {window.Sonarr.version}</div>}
</details>
</div>
);

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { Fragment } from 'react';
import Icon from 'Components/Icon';
import Popover from 'Components/Tooltip/Popover';
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
@ -82,7 +82,9 @@ function EpisodeNumber(props: EpisodeNumberProps) {
<Popover
anchor={
<span>
{showSeasonNumber && seasonNumber != null && <>{seasonNumber}x</>}
{showSeasonNumber && seasonNumber != null && (
<Fragment>{seasonNumber}x</Fragment>
)}
{showSeasonNumber ? padNumber(episodeNumber, 2) : episodeNumber}
@ -109,7 +111,9 @@ function EpisodeNumber(props: EpisodeNumberProps) {
/>
) : (
<span>
{showSeasonNumber && seasonNumber != null && <>{seasonNumber}x</>}
{showSeasonNumber && seasonNumber != null && (
<Fragment>{seasonNumber}x</Fragment>
)}
{showSeasonNumber ? padNumber(episodeNumber, 2) : episodeNumber}

View File

@ -3,15 +3,15 @@ import { useCallback, useState } from 'react';
export default function useModalOpenState(
initialState: boolean
): [boolean, () => void, () => void] {
const [isOpen, setIsOpen] = useState(initialState);
const [isOpen, setOpen] = useState(initialState);
const setModalOpen = useCallback(() => {
setIsOpen(true);
}, [setIsOpen]);
setOpen(true);
}, [setOpen]);
const setModalClosed = useCallback(() => {
setIsOpen(false);
}, [setIsOpen]);
setOpen(false);
}, [setOpen]);
return [isOpen, setModalOpen, setModalClosed];
}

View File

@ -857,7 +857,7 @@ function InteractiveImportModalContent(
<MenuContent>
<SelectedMenuItem
name="all"
name={'all'}
isSelected={!filterExistingFiles}
onPress={onFilterExistingFilesChange}
>
@ -865,7 +865,7 @@ function InteractiveImportModalContent(
</SelectedMenuItem>
<SelectedMenuItem
name="new"
name={'new'}
isSelected={filterExistingFiles}
onPress={onFilterExistingFilesChange}
>
@ -945,7 +945,7 @@ function InteractiveImportModalContent(
<SelectInput
className={styles.bulkSelect}
name="select"
value="select"
value={'select'}
values={bulkSelectOptions}
isDisabled={!selectedIds.length}
onChange={onSelectModalSelect}

View File

@ -17,7 +17,7 @@ function SelectLanguageModal(props: SelectLanguageModalProps) {
props;
return (
<Modal isOpen={isOpen} size={sizes.MEDIUM} onModalClose={onModalClose}>
<Modal isOpen={isOpen} onModalClose={onModalClose} size={sizes.MEDIUM}>
<SelectLanguageModalContent
languageIds={languageIds}
modalTitle={modalTitle}

View File

@ -64,20 +64,19 @@ interface RowItemData {
onSeriesSelect(seriesId: number): void;
}
function Row({ index, style, data }: ListChildComponentProps<RowItemData>) {
const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
index,
style,
data,
}) => {
const { items, columns, onSeriesSelect } = data;
const series = index >= items.length ? null : items[index];
const handlePress = useCallback(() => {
if (series?.id) {
onSeriesSelect(series.id);
}
}, [series?.id, onSeriesSelect]);
if (series == null) {
if (index >= items.length) {
return null;
}
const series = items[index];
return (
<VirtualTableRowButton
style={{
@ -85,7 +84,7 @@ function Row({ index, style, data }: ListChildComponentProps<RowItemData>) {
justifyContent: 'space-between',
...style,
}}
onPress={handlePress}
onPress={() => onSeriesSelect(series.id)}
>
<SelectSeriesRow
key={series.id}
@ -99,7 +98,7 @@ function Row({ index, style, data }: ListChildComponentProps<RowItemData>) {
/>
</VirtualTableRowButton>
);
}
};
function SelectSeriesModalContent(props: SelectSeriesModalContentProps) {
const { modalTitle, onSeriesSelect, onModalClose } = props;
@ -198,9 +197,9 @@ function SelectSeriesModalContent(props: SelectSeriesModalContentProps) {
/>
<Scroller
ref={scrollerRef}
className={styles.scroller}
autoFocus={false}
ref={scrollerRef}
>
<SelectSeriesModalTableHeader columns={columns} />
<List<RowItemData>

View File

@ -17,7 +17,7 @@ function SelectDownloadClientModal(props: SelectDownloadClientModalProps) {
props;
return (
<Modal isOpen={isOpen} size={sizes.MEDIUM} onModalClose={onModalClose}>
<Modal isOpen={isOpen} onModalClose={onModalClose} size={sizes.MEDIUM}>
<SelectDownloadClientModalContent
protocol={protocol}
modalTitle={modalTitle}

View File

@ -1,4 +1,4 @@
import React, { useCallback, useState } from 'react';
import React, { Fragment, useCallback, useState } from 'react';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import { icons } from 'Helpers/Props';
import ParseModal from 'Parse/ParseModal';
@ -16,7 +16,7 @@ function ParseToolbarButton() {
}, [setIsParseModalOpen]);
return (
<>
<Fragment>
<PageToolbarButton
label={translate('TestParsing')}
iconName={icons.PARSE}
@ -24,7 +24,7 @@ function ParseToolbarButton() {
/>
<ParseModal isOpen={isParseModalOpen} onModalClose={onParseModalClose} />
</>
</Fragment>
);
}

View File

@ -21,7 +21,7 @@ interface RootFolderRowProps {
}
function RootFolderRow(props: RootFolderRowProps) {
const { id, path, accessible, freeSpace = 0, unmappedFolders = [] } = props;
const { id, path, accessible, freeSpace, unmappedFolders = [] } = props;
const isUnavailable = !accessible;

View File

@ -1,3 +1,4 @@
import PropTypes from 'prop-types';
import React from 'react';
import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
@ -5,19 +6,14 @@ import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import styles from './SeasonInfo.css';
interface SeasonInfoProps {
totalEpisodeCount: number;
monitoredEpisodeCount: number;
episodeFileCount: number;
sizeOnDisk: number;
}
function SeasonInfo(props) {
const {
totalEpisodeCount,
monitoredEpisodeCount,
episodeFileCount,
sizeOnDisk
} = props;
function SeasonInfo({
totalEpisodeCount,
monitoredEpisodeCount,
episodeFileCount,
sizeOnDisk,
}: SeasonInfoProps) {
return (
<DescriptionList>
<DescriptionListItem
@ -51,4 +47,11 @@ function SeasonInfo({
);
}
SeasonInfo.propTypes = {
totalEpisodeCount: PropTypes.number.isRequired,
monitoredEpisodeCount: PropTypes.number.isRequired,
episodeFileCount: PropTypes.number.isRequired,
sizeOnDisk: PropTypes.number.isRequired
};
export default SeasonInfo;

View File

@ -212,8 +212,8 @@ class SeriesDetails extends Component {
} = this.props;
const {
episodeFileCount = 0,
sizeOnDisk = 0
episodeFileCount,
sizeOnDisk
} = statistics;
const {
@ -454,9 +454,10 @@ class SeriesDetails extends Component {
name={icons.DRIVE}
size={17}
/>
<span className={styles.sizeOnDisk}>
{formatBytes(sizeOnDisk)}
{
formatBytes(sizeOnDisk || 0)
}
</span>
</div>
</Label>

View File

@ -194,12 +194,10 @@ function getInfoRowProps(
}
if (name === 'sizeOnDisk') {
const { sizeOnDisk = 0 } = props;
return {
title: translate('SizeOnDisk'),
iconName: icons.DRIVE,
label: formatBytes(sizeOnDisk),
label: formatBytes(props.sizeOnDisk),
};
}

View File

@ -42,7 +42,11 @@ interface SeriesIndexOverviewsProps {
isSmallScreen: boolean;
}
function Row({ index, style, data }: ListChildComponentProps<RowItemData>) {
const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
index,
style,
data,
}) => {
const { items, ...otherData } = data;
if (index >= items.length) {
@ -56,7 +60,7 @@ function Row({ index, style, data }: ListChildComponentProps<RowItemData>) {
<SeriesIndexOverview seriesId={series.id} {...otherData} />
</div>
);
}
};
function getWindowScrollTopPosition() {
return document.documentElement.scrollTop || document.body.scrollTop || 0;

View File

@ -37,7 +37,7 @@ function SeriesIndexPosterInfo(props: SeriesIndexPosterInfoProps) {
added,
seasonCount,
path,
sizeOnDisk = 0,
sizeOnDisk,
tags,
sortKey,
showRelativeDates,

View File

@ -60,12 +60,12 @@ const seriesIndexSelector = createSelector(
}
);
function Cell({
const Cell: React.FC<GridChildComponentProps<CellItemData>> = ({
columnIndex,
rowIndex,
style,
data,
}: GridChildComponentProps<CellItemData>) {
}) => {
const { layout, items, sortKey, isSelectMode } = data;
const { columnCount, padding, posterWidth, posterHeight } = layout;
const index = rowIndex * columnCount + columnIndex;
@ -92,7 +92,7 @@ function Cell({
/>
</div>
);
}
};
function getWindowScrollTopPosition() {
return document.documentElement.scrollTop || document.body.scrollTop || 0;

View File

@ -45,7 +45,11 @@ const columnsSelector = createSelector(
(columns) => columns
);
function Row({ index, style, data }: ListChildComponentProps<RowItemData>) {
const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
index,
style,
data,
}) => {
const { items, sortKey, columns, isSelectMode } = data;
if (index >= items.length) {
@ -71,7 +75,7 @@ function Row({ index, style, data }: ListChildComponentProps<RowItemData>) {
/>
</div>
);
}
};
function getWindowScrollTopPosition() {
return document.documentElement.scrollTop || document.body.scrollTop || 0;

View File

@ -1,4 +1,4 @@
import React, { useCallback } from 'react';
import React, { Fragment, useCallback } from 'react';
import { useSelector } from 'react-redux';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@ -32,7 +32,7 @@ function SeriesIndexTableOptions(props: SeriesIndexTableOptionsProps) {
);
return (
<>
<Fragment>
<FormGroup>
<FormLabel>{translate('ShowBanners')}</FormLabel>
@ -56,7 +56,7 @@ function SeriesIndexTableOptions(props: SeriesIndexTableOptionsProps) {
onChange={onTableOptionChangeWrapper}
/>
</FormGroup>
</>
</Fragment>
);
}

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { Fragment } from 'react';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import PageContent from 'Components/Page/PageContent';
@ -17,11 +17,11 @@ function CustomFormatSettingsPage() {
// @ts-ignore
showSave={false}
additionalButtons={
<>
<Fragment>
<PageToolbarSeparator />
<ParseToolbarButton />
</>
</Fragment>
}
/>

View File

@ -231,9 +231,9 @@ function ManageDownloadClientsModalContent(
selectAll={true}
allSelected={allSelected}
allUnselected={allUnselected}
onSelectAllChange={onSelectAllChange}
sortKey={sortKey}
sortDirection={sortDirection}
onSelectAllChange={onSelectAllChange}
onSortPress={onSortPress}
>
<TableBody>
@ -286,9 +286,9 @@ function ManageDownloadClientsModalContent(
<ManageDownloadClientsEditModal
isOpen={isEditModalOpen}
downloadClientIds={selectedIds}
onModalClose={onEditModalClose}
onSavePress={onSavePress}
downloadClientIds={selectedIds}
/>
<TagsModal

View File

@ -32,7 +32,7 @@ function EditImportListExclusionModal(
<Modal size={sizes.MEDIUM} isOpen={isOpen} onModalClose={onModalClosePress}>
<EditImportListExclusionModalContent
{...otherProps}
onModalClose={onModalClosePress}
onModalClose={onModalClose}
/>
</Modal>
);

View File

@ -261,9 +261,9 @@ function ManageImportListsModalContent(
<ManageImportListsEditModal
isOpen={isEditModalOpen}
importListIds={selectedIds}
onModalClose={onEditModalClose}
onSavePress={onSavePress}
importListIds={selectedIds}
/>
<TagsModal

View File

@ -226,9 +226,9 @@ function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {
selectAll={true}
allSelected={allSelected}
allUnselected={allUnselected}
onSelectAllChange={onSelectAllChange}
sortKey={sortKey}
sortDirection={sortDirection}
onSelectAllChange={onSelectAllChange}
onSortPress={onSortPress}
>
<TableBody>
@ -281,9 +281,9 @@ function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {
<ManageIndexersEditModal
isOpen={isEditModalOpen}
indexerIds={selectedIds}
onModalClose={onEditModalClose}
onSavePress={onSavePress}
indexerIds={selectedIds}
/>
<TagsModal

View File

@ -7,7 +7,7 @@ function createRemoveItemHandler(section, url) {
return function(getState, payload, dispatch) {
const {
id,
queryParams
...queryParams
} = payload;
dispatch(set({ section, isDeleting: true }));

View File

@ -251,11 +251,6 @@ export const filterBuilderProps = [
type: filterBuilderTypes.EXACT,
valueType: filterBuilderValueTypes.SERIES_TYPES
},
{
name: 'title',
label: () => translate('Title'),
type: filterBuilderTypes.STRING
},
{
name: 'network',
label: () => translate('Network'),

View File

@ -66,7 +66,7 @@ function About() {
) : null}
{isDocker ? (
<DescriptionListItem title={translate('Docker')} data="Yes" />
<DescriptionListItem title={translate('Docker')} data={'Yes'} />
) : null}
<DescriptionListItem

View File

@ -1,4 +1,10 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, {
Fragment,
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
@ -152,7 +158,7 @@ function Updates() {
{translate('InstallLatest')}
</SpinnerButton>
) : (
<>
<Fragment>
<Icon name={icons.WARNING} kind={kinds.WARNING} size={30} />
<div className={styles.message}>
@ -165,7 +171,7 @@ function Updates() {
}
/>
</div>
</>
</Fragment>
)}
{isFetching ? (

View File

@ -1,6 +1,10 @@
import { filesize } from 'filesize';
function formatBytes(input: string | number) {
function formatBytes(input?: string | number) {
if (!input) {
return '';
}
const size = Number(input);
if (isNaN(size)) {

View File

@ -1,11 +1,11 @@
using FluentAssertions;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Test.ExtensionTests
{
[TestFixture]
public class NumberExtensionFixture
public class Int64ExtensionFixture
{
[TestCase(0, "0 B")]
[TestCase(1000, "1,000.0 B")]

View File

@ -380,17 +380,8 @@ namespace NzbDrone.Common.Test
[TestCase(@" C:\Test\TV\")]
[TestCase(@" C:\Test\TV")]
public void IsPathValid_should_be_false_on_windows(string path)
public void IsPathValid_should_be_false(string path)
{
WindowsOnly();
path.IsPathValid(PathValidationType.CurrentOs).Should().BeFalse();
}
[TestCase(@"")]
[TestCase(@"relative/path")]
public void IsPathValid_should_be_false_on_unix(string path)
{
PosixOnly();
path.AsOsAgnostic().IsPathValid(PathValidationType.CurrentOs).Should().BeFalse();
}
}

View File

@ -1,9 +1,9 @@
using System;
using System;
using System.Globalization;
namespace NzbDrone.Common.Extensions
{
public static class NumberExtensions
public static class Int64Extensions
{
private static readonly string[] SizeSuffixes = { "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
@ -26,25 +26,5 @@ namespace NzbDrone.Common.Extensions
return string.Format(CultureInfo.InvariantCulture, "{0:n1} {1}", adjustedSize, SizeSuffixes[mag]);
}
public static long Megabytes(this int megabytes)
{
return Convert.ToInt64(megabytes * 1024L * 1024L);
}
public static long Gigabytes(this int gigabytes)
{
return Convert.ToInt64(gigabytes * 1024L * 1024L * 1024L);
}
public static long Megabytes(this double megabytes)
{
return Convert.ToInt64(megabytes * 1024L * 1024L);
}
public static long Gigabytes(this double gigabytes)
{
return Convert.ToInt64(gigabytes * 1024L * 1024L * 1024L);
}
}
}

View File

@ -152,20 +152,16 @@ namespace NzbDrone.Common.Extensions
return false;
}
// Only check for leading or trailing spaces for path when running on Windows.
if (OsInfo.IsWindows)
var directoryInfo = new DirectoryInfo(path);
while (directoryInfo != null)
{
var directoryInfo = new DirectoryInfo(path);
while (directoryInfo != null)
if (directoryInfo.Name.Trim() != directoryInfo.Name)
{
if (directoryInfo.Name.Trim() != directoryInfo.Name)
{
return false;
}
directoryInfo = directoryInfo.Parent;
return false;
}
directoryInfo = directoryInfo.Parent;
}
if (validationType == PathValidationType.AnyOs)

View File

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

View File

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

View File

@ -3,7 +3,6 @@ using System.Diagnostics;
using System.IO;
using NLog;
using NLog.Config;
using NLog.Layouts.ClefJsonLayout;
using NLog.Targets;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
@ -14,8 +13,6 @@ namespace NzbDrone.Common.Instrumentation
public static class NzbDroneLogger
{
private const string FILE_LOG_LAYOUT = @"${date:format=yyyy-MM-dd HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}}";
public const string ConsoleLogLayout = "[${level}] ${logger}: ${message} ${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}}";
public static CompactJsonLayout ClefLogLayout = new CompactJsonLayout();
private static bool _isConfigured;
@ -37,8 +34,6 @@ namespace NzbDrone.Common.Instrumentation
var appFolderInfo = new AppFolderInfo(startupContext);
RegisterGlobalFilters();
if (Debugger.IsAttached)
{
RegisterDebugger();
@ -127,16 +122,7 @@ namespace NzbDrone.Common.Instrumentation
var coloredConsoleTarget = new ColoredConsoleTarget();
coloredConsoleTarget.Name = "consoleLogger";
var logFormat = Enum.TryParse<ConsoleLogFormat>(Environment.GetEnvironmentVariable("SONARR__LOG__CONSOLEFORMAT"), out var formatEnumValue)
? formatEnumValue
: ConsoleLogFormat.Standard;
coloredConsoleTarget.Layout = logFormat switch
{
ConsoleLogFormat.Clef => ClefLogLayout,
_ => ConsoleLogLayout
};
coloredConsoleTarget.Layout = "[${level}] ${logger}: ${message} ${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}}";
var loggingRule = new LoggingRule("*", level, coloredConsoleTarget);
@ -162,7 +148,7 @@ namespace NzbDrone.Common.Instrumentation
fileTarget.ConcurrentWrites = false;
fileTarget.ConcurrentWriteAttemptDelay = 50;
fileTarget.ConcurrentWriteAttempts = 10;
fileTarget.ArchiveAboveSize = 1.Megabytes();
fileTarget.ArchiveAboveSize = 1024000;
fileTarget.MaxArchiveFiles = maxArchiveFiles;
fileTarget.EnableFileDelete = true;
fileTarget.ArchiveNumbering = ArchiveNumberingMode.Rolling;
@ -210,17 +196,6 @@ namespace NzbDrone.Common.Instrumentation
LogManager.Configuration.LoggingRules.Insert(0, rule);
}
private static void RegisterGlobalFilters()
{
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("Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware").WriteToNil(LogLevel.Fatal);
});
}
public static Logger GetLogger(Type obj)
{
return LogManager.GetLogger(obj.Name.Replace("NzbDrone.", ""));
@ -231,10 +206,4 @@ namespace NzbDrone.Common.Instrumentation
return GetLogger(obj.GetType());
}
}
public enum ConsoleLogFormat
{
Standard,
Clef
}
}

View File

@ -5,10 +5,8 @@ public class LogOptions
public string Level { get; set; }
public bool? FilterSentryEvents { get; set; }
public int? Rotate { get; set; }
public int? SizeLimit { get; set; }
public bool? Sql { get; set; }
public string ConsoleLevel { get; set; }
public string ConsoleFormat { get; set; }
public bool? AnalyticsEnabled { get; set; }
public string SyslogServer { get; set; }
public int? SyslogPort { get; set; }

View File

@ -8,10 +8,9 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.3.2" />
<PackageReference Include="NLog.Layouts.ClefJsonLayout" Version="1.0.0" />
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.11" />
<PackageReference Include="NLog" Version="4.7.14" />
<PackageReference Include="NLog.Targets.Syslog" Version="6.0.3" />
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.4" />
<PackageReference Include="Sentry" Version="4.0.2" />
<PackageReference Include="SharpZipLib" Version="1.4.2" />
<PackageReference Include="System.Text.Json" Version="6.0.9" />

View File

@ -2,7 +2,6 @@ using System.Collections.Generic;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;

View File

@ -3,7 +3,6 @@ using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;

View File

@ -2,7 +2,6 @@ using System.Collections.Generic;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;

View File

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration;
using NzbDrone.Core.MediaFiles.MediaInfo;

View File

@ -4,7 +4,6 @@ using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;

View File

@ -64,7 +64,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
};
Mocker.GetMock<IParsingService>()
.Setup(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), It.IsAny<SearchCriteriaBase>()))
.Setup(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<SearchCriteriaBase>()))
.Returns(_remoteEpisode);
}
@ -154,7 +154,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Subject.GetRssDecision(_reports).ToList();
Mocker.GetMock<IParsingService>().Verify(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), It.IsAny<SearchCriteriaBase>()), Times.Never());
Mocker.GetMock<IParsingService>().Verify(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<SearchCriteriaBase>()), Times.Never());
_pass1.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null), Times.Never());
_pass2.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null), Times.Never());
@ -169,7 +169,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
var results = Subject.GetRssDecision(_reports).ToList();
Mocker.GetMock<IParsingService>().Verify(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), It.IsAny<SearchCriteriaBase>()), Times.Never());
Mocker.GetMock<IParsingService>().Verify(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<SearchCriteriaBase>()), Times.Never());
_pass1.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null), Times.Never());
_pass2.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null), Times.Never());
@ -186,7 +186,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Subject.GetSearchDecision(_reports, new SingleEpisodeSearchCriteria()).ToList();
Mocker.GetMock<IParsingService>().Verify(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), It.IsAny<SearchCriteriaBase>()), Times.Never());
Mocker.GetMock<IParsingService>().Verify(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<SearchCriteriaBase>()), Times.Never());
_pass1.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null), Times.Never());
_pass2.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null), Times.Never());
@ -212,7 +212,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
GivenSpecifications(_pass1);
Mocker.GetMock<IParsingService>().Setup(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), It.IsAny<SearchCriteriaBase>()))
Mocker.GetMock<IParsingService>().Setup(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<SearchCriteriaBase>()))
.Throws<TestException>();
_reports = new List<ReleaseInfo>
@ -224,7 +224,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Subject.GetRssDecision(_reports);
Mocker.GetMock<IParsingService>().Verify(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), It.IsAny<SearchCriteriaBase>()), Times.Exactly(_reports.Count));
Mocker.GetMock<IParsingService>().Verify(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<SearchCriteriaBase>()), Times.Exactly(_reports.Count));
ExceptionVerification.ExpectedErrors(3);
}
@ -263,8 +263,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
}).ToList();
Mocker.GetMock<IParsingService>()
.Setup(v => v.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), It.IsAny<SearchCriteriaBase>()))
.Returns<ParsedEpisodeInfo, int, int, string, SearchCriteriaBase>((p, _, _, _, _) =>
.Setup(v => v.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<SearchCriteriaBase>()))
.Returns<ParsedEpisodeInfo, int, int, SearchCriteriaBase>((p, tvdbid, tvrageid, c) =>
new RemoteEpisode
{
DownloadAllowed = true,
@ -318,7 +318,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
GivenSpecifications(_pass1);
Mocker.GetMock<IParsingService>().Setup(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), It.IsAny<SearchCriteriaBase>()))
Mocker.GetMock<IParsingService>().Setup(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<SearchCriteriaBase>()))
.Throws<TestException>();
_reports = new List<ReleaseInfo>

View File

@ -4,8 +4,6 @@ using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Download.Aggregation.Aggregators;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.TorrentRss;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
@ -64,63 +62,6 @@ namespace NzbDrone.Core.Test.Download.Aggregation.Aggregators
Subject.Aggregate(_remoteEpisode).Languages.Should().Equal(_remoteEpisode.ParsedEpisodeInfo.Languages);
}
[Test]
public void should_return_multi_languages_when_indexer_has_multi_languages_configuration()
{
var releaseTitle = "Series.Title.S01E01.MULTi.1080p.WEB.H265-RlsGroup";
var indexerDefinition = new IndexerDefinition
{
Settings = new TorrentRssIndexerSettings { MultiLanguages = new List<int> { Language.Original.Id, Language.French.Id } }
};
Mocker.GetMock<IIndexerFactory>()
.Setup(v => v.Get(1))
.Returns(indexerDefinition);
_remoteEpisode.ParsedEpisodeInfo = GetParsedEpisodeInfo(new List<Language> { }, releaseTitle);
_remoteEpisode.Release.IndexerId = 1;
_remoteEpisode.Release.Title = releaseTitle;
Subject.Aggregate(_remoteEpisode).Languages.Should().BeEquivalentTo(new List<Language> { _series.OriginalLanguage, Language.French });
}
[Test]
public void should_return_multi_languages_when_release_as_unknown_as_default_language_and_indexer_has_multi_languages_configuration()
{
var releaseTitle = "Series.Title.S01E01.MULTi.1080p.WEB.H265-RlsGroup";
var indexerDefinition = new IndexerDefinition
{
Settings = new TorrentRssIndexerSettings { MultiLanguages = new List<int> { Language.Original.Id, Language.French.Id } }
};
Mocker.GetMock<IIndexerFactory>()
.Setup(v => v.Get(1))
.Returns(indexerDefinition);
_remoteEpisode.ParsedEpisodeInfo = GetParsedEpisodeInfo(new List<Language> { Language.Unknown }, releaseTitle);
_remoteEpisode.Release.IndexerId = 1;
_remoteEpisode.Release.Title = releaseTitle;
Subject.Aggregate(_remoteEpisode).Languages.Should().BeEquivalentTo(new List<Language> { _series.OriginalLanguage, Language.French });
}
[Test]
public void should_return_original_when_indexer_has_no_multi_languages_configuration()
{
var releaseTitle = "Series.Title.S01E01.MULTi.1080p.WEB.H265-RlsGroup";
var indexerDefinition = new IndexerDefinition
{
Settings = new TorrentRssIndexerSettings { }
};
Mocker.GetMock<IIndexerFactory>()
.Setup(v => v.Get(1))
.Returns(indexerDefinition);
_remoteEpisode.ParsedEpisodeInfo = GetParsedEpisodeInfo(new List<Language> { }, releaseTitle);
_remoteEpisode.Release.IndexerId = 1;
_remoteEpisode.Release.Title = releaseTitle;
Subject.Aggregate(_remoteEpisode).Languages.Should().BeEquivalentTo(new List<Language> { _series.OriginalLanguage });
}
[Test]
public void should_exclude_language_that_is_part_of_episode_title_when_release_tokens_contains_episode_title()
{

View File

@ -10,6 +10,7 @@ using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
@ -34,7 +35,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
.Returns(30);
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), null))
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), (SearchCriteriaBase)null))
.Returns(() => CreateRemoteEpisode());
Mocker.GetMock<IHttpClient>()

View File

@ -118,7 +118,7 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
.Returns(remoteEpisode);
Mocker.GetMock<IParsingService>()
.Setup(s => s.ParseSpecialEpisodeTitle(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), null))
.Setup(s => s.ParseSpecialEpisodeTitle(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>(), null))
.Returns(remoteEpisode.ParsedEpisodeInfo);
var client = new DownloadClientDefinition()
@ -169,7 +169,7 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
};
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), null))
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), null))
.Returns(remoteEpisode);
Mocker.GetMock<IHistoryService>()
@ -199,7 +199,7 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
Subject.GetTrackedDownloads().Should().HaveCount(1);
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), null))
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), null))
.Returns(default(RemoteEpisode));
Subject.Handle(new EpisodeInfoRefreshedEvent(remoteEpisode.Series, new List<Episode>(), new List<Episode>(), remoteEpisode.Episodes));
@ -228,7 +228,7 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
};
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), null))
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), null))
.Returns(default(RemoteEpisode));
Mocker.GetMock<IHistoryService>()
@ -258,7 +258,7 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
Subject.GetTrackedDownloads().Should().HaveCount(1);
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), null))
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), null))
.Returns(default(RemoteEpisode));
Subject.Handle(new EpisodeInfoRefreshedEvent(remoteEpisode.Series, new List<Episode>(), new List<Episode>(), remoteEpisode.Episodes));
@ -287,7 +287,7 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
};
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), null))
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), null))
.Returns(default(RemoteEpisode));
Mocker.GetMock<IHistoryService>()
@ -317,7 +317,7 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
Subject.GetTrackedDownloads().Should().HaveCount(1);
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), null))
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), null))
.Returns(default(RemoteEpisode));
Subject.Handle(new SeriesDeletedEvent(new List<Series> { remoteEpisode.Series }, true, true));

View File

@ -15,10 +15,6 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
[TestCase("Franklin & Bash", "Franklin+and+Bash")]
[TestCase("Chicago P.D.", "Chicago+PD")]
[TestCase("Kourtney And Khlo\u00E9 Take The Hamptons", "Kourtney+And+Khloe+Take+The+Hamptons")]
[TestCase("Betty White`s Off Their Rockers", "Betty+Whites+Off+Their+Rockers")]
[TestCase("Betty White\u00b4s Off Their Rockers", "Betty+Whites+Off+Their+Rockers")]
[TestCase("Betty Whites Off Their Rockers", "Betty+Whites+Off+Their+Rockers")]
[TestCase("Betty Whites Off Their Rockers", "Betty+Whites+Off+Their+Rockers")]
public void should_replace_some_special_characters(string input, string expected)
{
Subject.SceneTitles = new List<string> { input };

View File

@ -7,7 +7,6 @@ using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.MediaFiles;

View File

@ -6,7 +6,6 @@ using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.History;
@ -75,8 +74,8 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
.Returns(new List<EpisodeHistory>());
_downloadClientItem = Builder<DownloadClientItem>.CreateNew()
.With(d => d.OutputPath = new OsPath(outputPath))
.Build();
.With(d => d.OutputPath = new OsPath(outputPath))
.Build();
}
private void GivenNewDownload()

View File

@ -1,11 +1,10 @@
using System.IO;
using System.IO;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications;
using NzbDrone.Core.Parser.Model;

View File

@ -233,11 +233,11 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
GivenEpisodes(actualInfo, actualInfo.EpisodeNumbers);
Mocker.GetMock<IParsingService>()
.Setup(v => v.ParseSpecialEpisodeTitle(fileInfo, It.IsAny<string>(), 0, 0, null, null))
.Setup(v => v.ParseSpecialEpisodeTitle(fileInfo, It.IsAny<string>(), 0, 0, null))
.Returns(actualInfo);
Mocker.GetMock<IParsingService>()
.Setup(v => v.ParseSpecialEpisodeTitle(folderInfo, It.IsAny<string>(), 0, 0, null, null))
.Setup(v => v.ParseSpecialEpisodeTitle(folderInfo, It.IsAny<string>(), 0, 0, null))
.Returns(actualInfo);
Subject.IsSatisfiedBy(localEpisode, null).Accepted.Should().BeTrue();

View File

@ -91,7 +91,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
GivenDailySeries();
GivenDailyParseResult();
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId);
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId);
Mocker.GetMock<IEpisodeService>()
.Verify(v => v.FindEpisode(It.IsAny<int>(), It.IsAny<string>(), null), Times.Once());
@ -103,7 +103,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
GivenDailySeries();
GivenDailyParseResult();
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId, _singleEpisodeSearchCriteria);
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _singleEpisodeSearchCriteria);
Mocker.GetMock<IEpisodeService>()
.Verify(v => v.FindEpisode(It.IsAny<int>(), It.IsAny<string>(), null), Times.Never());
@ -115,7 +115,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
GivenDailySeries();
_parsedEpisodeInfo.AirDate = DateTime.Today.AddDays(-5).ToString(Episode.AIR_DATE_FORMAT);
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId, _singleEpisodeSearchCriteria);
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _singleEpisodeSearchCriteria);
Mocker.GetMock<IEpisodeService>()
.Verify(v => v.FindEpisode(It.IsAny<int>(), It.IsAny<string>(), null), Times.Once());
@ -128,7 +128,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
GivenDailyParseResult();
_parsedEpisodeInfo.DailyPart = 1;
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId);
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId);
Mocker.GetMock<IEpisodeService>()
.Verify(v => v.FindEpisode(It.IsAny<int>(), It.IsAny<string>(), 1), Times.Once());
@ -143,7 +143,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
.Setup(s => s.FindEpisodesBySceneNumbering(It.IsAny<int>(), It.IsAny<int>()))
.Returns(new List<Episode>());
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId, _singleEpisodeSearchCriteria);
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _singleEpisodeSearchCriteria);
Mocker.GetMock<IEpisodeService>()
.Verify(v => v.FindEpisode(It.IsAny<int>(), It.IsAny<string>(), null), Times.Never());
@ -154,7 +154,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
{
GivenSceneNumberingSeries();
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId);
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId);
Mocker.GetMock<IEpisodeService>()
.Verify(v => v.FindEpisodesBySceneNumbering(It.IsAny<int>(), It.IsAny<int>(), It.IsAny<int>()), Times.Once());
@ -165,7 +165,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
{
GivenSceneNumberingSeries();
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId, _singleEpisodeSearchCriteria);
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _singleEpisodeSearchCriteria);
Mocker.GetMock<IEpisodeService>()
.Verify(v => v.FindEpisodesBySceneNumbering(It.IsAny<int>(), It.IsAny<int>(), It.IsAny<int>()), Times.Never());
@ -177,7 +177,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
GivenSceneNumberingSeries();
_episodes.First().SceneEpisodeNumber = 10;
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId, _singleEpisodeSearchCriteria);
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _singleEpisodeSearchCriteria);
Mocker.GetMock<IEpisodeService>()
.Verify(v => v.FindEpisodesBySceneNumbering(It.IsAny<int>(), It.IsAny<int>(), It.IsAny<int>()), Times.Once());
@ -186,7 +186,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
[Test]
public void should_find_episode()
{
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId);
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId);
Mocker.GetMock<IEpisodeService>()
.Verify(v => v.FindEpisode(It.IsAny<int>(), It.IsAny<int>(), It.IsAny<int>()), Times.Once());
@ -195,7 +195,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
[Test]
public void should_match_episode_with_search_criteria()
{
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId, _singleEpisodeSearchCriteria);
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _singleEpisodeSearchCriteria);
Mocker.GetMock<IEpisodeService>()
.Verify(v => v.FindEpisode(It.IsAny<int>(), It.IsAny<int>(), It.IsAny<int>()), Times.Never());
@ -206,7 +206,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
{
_episodes.First().EpisodeNumber = 10;
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId, _singleEpisodeSearchCriteria);
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _singleEpisodeSearchCriteria);
Mocker.GetMock<IEpisodeService>()
.Verify(v => v.FindEpisode(It.IsAny<int>(), It.IsAny<int>(), It.IsAny<int>()), Times.Once());
@ -537,7 +537,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
.With(e => e.EpisodeNumber = 1)
.Build());
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId);
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId);
Mocker.GetMock<IEpisodeService>()
.Verify(v => v.FindEpisode(_series.TvdbId, 0, 1), Times.Once());
@ -555,7 +555,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
.Setup(s => s.FindEpisodeByTitle(_series.TvdbId, 0, _parsedEpisodeInfo.ReleaseTitle))
.Returns((Episode)null);
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId);
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId);
Mocker.GetMock<IEpisodeService>()
.Verify(v => v.FindEpisode(_series.TvdbId, _parsedEpisodeInfo.SeasonNumber, _parsedEpisodeInfo.EpisodeNumbers.First()), Times.Once());

View File

@ -86,7 +86,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
{
GivenMatchBySeriesTitle();
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId);
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId);
Mocker.GetMock<ISeriesService>()
.Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Once());
@ -97,7 +97,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
{
GivenMatchByTvdbId();
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId);
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId);
Mocker.GetMock<ISeriesService>()
.Verify(v => v.FindByTvdbId(It.IsAny<int>()), Times.Once());
@ -108,7 +108,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
{
GivenMatchByTvRageId();
Subject.Map(_parsedEpisodeInfo, 0, _series.TvRageId, null);
Subject.Map(_parsedEpisodeInfo, 0, _series.TvRageId);
Mocker.GetMock<ISeriesService>()
.Verify(v => v.FindByTvRageId(It.IsAny<int>()), Times.Once());
@ -123,7 +123,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
.Setup(v => v.FindSceneMapping(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()))
.Returns(new SceneMapping { TvdbId = 10 });
var result = Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId);
var result = Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId);
Mocker.GetMock<ISeriesService>()
.Verify(v => v.FindByTvRageId(It.IsAny<int>()), Times.Never());
@ -136,7 +136,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
{
GivenMatchBySeriesTitle();
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId, _singleEpisodeSearchCriteria);
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _singleEpisodeSearchCriteria);
Mocker.GetMock<ISeriesService>()
.Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Never());
@ -147,7 +147,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
{
GivenParseResultSeriesDoesntMatchSearchCriteria();
Subject.Map(_parsedEpisodeInfo, 10, 10, null, _singleEpisodeSearchCriteria);
Subject.Map(_parsedEpisodeInfo, 10, 10, _singleEpisodeSearchCriteria);
Mocker.GetMock<ISeriesService>()
.Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Once());
@ -169,7 +169,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
.Setup(s => s.FindByTitle(_parsedEpisodeInfo.SeriesTitleInfo.TitleWithoutYear, _parsedEpisodeInfo.SeriesTitleInfo.Year))
.Returns(_series);
Subject.Map(_parsedEpisodeInfo, 10, 10, null, _singleEpisodeSearchCriteria);
Subject.Map(_parsedEpisodeInfo, 10, 10, _singleEpisodeSearchCriteria);
Mocker.GetMock<ISeriesService>()
.Verify(v => v.FindByTitle(It.IsAny<string>(), It.IsAny<int>()), Times.Once());
@ -180,7 +180,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
{
GivenParseResultSeriesDoesntMatchSearchCriteria();
Subject.Map(_parsedEpisodeInfo, 10, 10, null, _singleEpisodeSearchCriteria);
Subject.Map(_parsedEpisodeInfo, 10, 10, _singleEpisodeSearchCriteria);
Mocker.GetMock<ISeriesService>()
.Verify(v => v.FindByTvdbId(It.IsAny<int>()), Times.Once());
@ -191,7 +191,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
{
GivenParseResultSeriesDoesntMatchSearchCriteria();
Subject.Map(_parsedEpisodeInfo, 0, 10, null, _singleEpisodeSearchCriteria);
Subject.Map(_parsedEpisodeInfo, 0, 10, _singleEpisodeSearchCriteria);
Mocker.GetMock<ISeriesService>()
.Verify(v => v.FindByTvRageId(It.IsAny<int>()), Times.Once());
@ -202,34 +202,12 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
{
GivenParseResultSeriesDoesntMatchSearchCriteria();
Subject.Map(_parsedEpisodeInfo, 10, 10, null, _singleEpisodeSearchCriteria);
Subject.Map(_parsedEpisodeInfo, 10, 10, _singleEpisodeSearchCriteria);
Mocker.GetMock<ISeriesService>()
.Verify(v => v.FindByTvRageId(It.IsAny<int>()), Times.Never());
}
[Test]
public void should_FindByImdbId_when_search_criteria_and_FindByTitle_matching_fails()
{
GivenParseResultSeriesDoesntMatchSearchCriteria();
Subject.Map(_parsedEpisodeInfo, 0, 0, "tt12345", _singleEpisodeSearchCriteria);
Mocker.GetMock<ISeriesService>()
.Verify(v => v.FindByImdbId(It.IsAny<string>()), Times.Once());
}
[Test]
public void should_not_FindByImdbId_when_search_criteria_and_FindByTitle_matching_fails_and_tvdb_id_is_specified()
{
GivenParseResultSeriesDoesntMatchSearchCriteria();
Subject.Map(_parsedEpisodeInfo, 10, 10, "tt12345", _singleEpisodeSearchCriteria);
Mocker.GetMock<ISeriesService>()
.Verify(v => v.FindByImdbId(It.IsAny<string>()), Times.Never());
}
[Test]
public void should_use_tvdbid_matching_when_alias_is_found()
{
@ -237,7 +215,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
.Setup(s => s.FindTvdbId(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()))
.Returns(_series.TvdbId);
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId, _singleEpisodeSearchCriteria);
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _singleEpisodeSearchCriteria);
Mocker.GetMock<ISeriesService>()
.Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Never());
@ -248,7 +226,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
{
GivenParseResultSeriesDoesntMatchSearchCriteria();
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId, _singleEpisodeSearchCriteria);
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _singleEpisodeSearchCriteria);
Mocker.GetMock<ISeriesService>()
.Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Never());

View File

@ -185,10 +185,8 @@ namespace NzbDrone.Core.Blocklisting
Indexer = message.Data.GetValueOrDefault("indexer"),
Protocol = (DownloadProtocol)Convert.ToInt32(message.Data.GetValueOrDefault("protocol")),
Message = message.Message,
Languages = message.Languages,
TorrentInfoHash = message.TrackedDownload?.Protocol == DownloadProtocol.Torrent
? message.TrackedDownload.DownloadItem.DownloadId
: message.Data.GetValueOrDefault("torrentInfoHash", null)
TorrentInfoHash = message.Data.GetValueOrDefault("torrentInfoHash"),
Languages = message.Languages
};
if (Enum.TryParse(message.Data.GetValueOrDefault("indexerFlags"), true, out IndexerFlags flags))

View File

@ -10,7 +10,6 @@ using NzbDrone.Common.Cache;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation;
using NzbDrone.Common.Options;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration.Events;
@ -39,10 +38,8 @@ namespace NzbDrone.Core.Configuration
bool AnalyticsEnabled { get; }
string LogLevel { get; }
string ConsoleLogLevel { get; }
ConsoleLogFormat ConsoleLogFormat { get; }
bool LogSql { get; }
int LogRotate { get; }
int LogSizeLimit { get; }
bool FilterSentryEvents { get; }
string Branch { get; }
string ApiKey { get; }
@ -223,14 +220,9 @@ namespace NzbDrone.Core.Configuration
public string Branch => _updateOptions.Branch ?? GetValue("Branch", "main").ToLowerInvariant();
public string LogLevel => _logOptions.Level ?? GetValue("LogLevel", "debug").ToLowerInvariant();
public string LogLevel => _logOptions.Level ?? GetValue("LogLevel", "info").ToLowerInvariant();
public string ConsoleLogLevel => _logOptions.ConsoleLevel ?? GetValue("ConsoleLogLevel", string.Empty, persist: false);
public ConsoleLogFormat ConsoleLogFormat =>
Enum.TryParse<ConsoleLogFormat>(_logOptions.ConsoleFormat, out var enumValue)
? enumValue
: GetValueEnum("ConsoleLogFormat", ConsoleLogFormat.Standard, false);
public string Theme => _appOptions.Theme ?? GetValue("Theme", "auto", persist: false);
public string PostgresHost => _postgresOptions?.Host ?? GetValue("PostgresHost", string.Empty, persist: false);
@ -242,7 +234,6 @@ namespace NzbDrone.Core.Configuration
public bool LogDbEnabled => _logOptions.DbEnabled ?? GetValueBoolean("LogDbEnabled", true, persist: false);
public bool LogSql => _logOptions.Sql ?? GetValueBoolean("LogSql", false, persist: false);
public int LogRotate => _logOptions.Rotate ?? GetValueInt("LogRotate", 50, persist: false);
public int LogSizeLimit => Math.Min(Math.Max(_logOptions.SizeLimit ?? GetValueInt("LogSizeLimit", 1, persist: false), 0), 10);
public bool FilterSentryEvents => _logOptions.FilterSentryEvents ?? GetValueBoolean("FilterSentryEvents", true, persist: false);
public string SslCertPath => _serverOptions.SslCertPath ?? GetValue("SslCertPath", "");
public string SslCertPassword => _serverOptions.SslCertPassword ?? GetValue("SslCertPassword", "");

View File

@ -1,5 +1,4 @@
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Validation;

View File

@ -63,9 +63,7 @@ namespace NzbDrone.Core.DataAugmentation.Scene
sceneSeasonNumbers.Contains(n.SceneSeasonNumber ?? -1) ||
((n.SeasonNumber ?? -1) == -1 && (n.SceneSeasonNumber ?? -1) == -1 && n.SceneOrigin != "tvdb"))
.Where(n => IsEnglish(n.SearchTerm))
.Select(n => n.SearchTerm)
.Distinct(StringComparer.InvariantCultureIgnoreCase)
.ToList();
.Select(n => n.SearchTerm).Distinct().ToList();
return names;
}

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model;

View File

@ -80,7 +80,7 @@ namespace NzbDrone.Core.DecisionEngine
if (parsedEpisodeInfo == null || parsedEpisodeInfo.IsPossibleSpecialEpisode)
{
var specialEpisodeInfo = _parsingService.ParseSpecialEpisodeTitle(parsedEpisodeInfo, report.Title, report.TvdbId, report.TvRageId, report.ImdbId, searchCriteria);
var specialEpisodeInfo = _parsingService.ParseSpecialEpisodeTitle(parsedEpisodeInfo, report.Title, report.TvdbId, report.TvRageId, searchCriteria);
if (specialEpisodeInfo != null)
{
@ -90,7 +90,7 @@ namespace NzbDrone.Core.DecisionEngine
if (parsedEpisodeInfo != null && !parsedEpisodeInfo.SeriesTitle.IsNullOrWhiteSpace())
{
var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, report.TvdbId, report.TvRageId, report.ImdbId, searchCriteria);
var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, report.TvdbId, report.TvRageId, searchCriteria);
remoteEpisode.Release = report;
if (remoteEpisode.Series == null)

View File

@ -1,5 +1,4 @@
using NLog;
using NzbDrone.Common.Extensions;
using NLog;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;

View File

@ -2,8 +2,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
@ -12,13 +10,10 @@ namespace NzbDrone.Core.Download.Aggregation.Aggregators
{
public class AggregateLanguages : IAggregateRemoteEpisode
{
private readonly IIndexerFactory _indexerFactory;
private readonly Logger _logger;
public AggregateLanguages(IIndexerFactory indexerFactory,
Logger logger)
public AggregateLanguages(Logger logger)
{
_indexerFactory = indexerFactory;
_logger = logger;
}
@ -76,17 +71,6 @@ namespace NzbDrone.Core.Download.Aggregation.Aggregators
languages = languages.Except(languagesToRemove).ToList();
}
if ((languages.Count == 0 || (languages.Count == 1 && languages.First() == Language.Unknown)) && releaseInfo is { IndexerId: > 0 } && releaseInfo.Title.IsNotNullOrWhiteSpace())
{
var indexer = _indexerFactory.Get(releaseInfo.IndexerId);
if (indexer?.Settings is IIndexerSettings settings && settings.MultiLanguages.Any() && Parser.Parser.HasMultipleLanguages(releaseInfo.Title))
{
// Use indexer setting for Multi-languages
languages = settings.MultiLanguages.Select(i => (Language)i).ToList();
}
}
// Use series language as fallback if we couldn't parse a language
if (languages.Count == 0 || (languages.Count == 1 && languages.First() == Language.Unknown))
{

View File

@ -129,8 +129,10 @@ namespace NzbDrone.Core.Download.Clients.Aria2
var outputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(GetOutputPath(torrent)));
var queueItem = new DownloadClientItem
yield return new DownloadClientItem
{
CanMoveFiles = false,
CanBeRemoved = torrent.Status == "complete",
Category = null,
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this, false),
DownloadId = torrent.InfoHash?.ToUpper(),
@ -144,12 +146,7 @@ namespace NzbDrone.Core.Download.Clients.Aria2
Status = status,
Title = title,
TotalSize = totalLength,
CanMoveFiles = false
};
queueItem.CanBeRemoved = queueItem.DownloadClientInfo.RemoveCompletedDownloads && torrent.Status == "complete";
yield return queueItem;
}
}

View File

@ -89,7 +89,7 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
{
foreach (var item in _scanWatchFolder.GetItems(Settings.WatchFolder, ScanGracePeriod))
{
var queueItem = new DownloadClientItem
yield return new DownloadClientItem
{
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this, false),
DownloadId = Definition.Name + "_" + item.DownloadId,
@ -101,14 +101,11 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
OutputPath = item.OutputPath,
Status = item.Status
Status = item.Status,
CanMoveFiles = !Settings.ReadOnly,
CanBeRemoved = !Settings.ReadOnly
};
queueItem.CanMoveFiles = queueItem.CanBeRemoved =
queueItem.DownloadClientInfo.RemoveCompletedDownloads &&
!Settings.ReadOnly;
yield return queueItem;
}
}

View File

@ -190,7 +190,6 @@ namespace NzbDrone.Core.Download.Clients.Deluge
// Here we detect if Deluge is managing the torrent and whether the seed criteria has been met.
// This allows Sonarr to delete the torrent as appropriate.
item.CanMoveFiles = item.CanBeRemoved =
item.DownloadClientInfo.RemoveCompletedDownloads &&
torrent.IsAutoManaged &&
torrent.StopAtRatio &&
torrent.Ratio >= torrent.StopRatio &&
@ -201,7 +200,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
if (ignoredCount > 0)
{
_logger.Warn("{0} torrent(s) were ignored because they did not have a title. Check Deluge and remove any invalid torrents");
_logger.Warn("{0} torrent(s) were ignored becuase they did not have a title, check Deluge and remove any invalid torrents");
}
return items;

View File

@ -88,7 +88,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
}
}
var item = new DownloadClientItem
var item = new DownloadClientItem()
{
Category = Settings.TvCategory,
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this, false),
@ -99,11 +99,11 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
RemainingTime = GetRemainingTime(torrent),
SeedRatio = GetSeedRatio(torrent),
Status = GetStatus(torrent),
Message = GetMessage(torrent)
Message = GetMessage(torrent),
CanMoveFiles = IsFinished(torrent),
CanBeRemoved = IsFinished(torrent)
};
item.CanMoveFiles = item.CanBeRemoved = item.DownloadClientInfo.RemoveCompletedDownloads && IsFinished(torrent);
if (item.Status == DownloadItemStatus.Completed || item.Status == DownloadItemStatus.Failed)
{
item.OutputPath = GetOutputPath(outputPath, torrent, serialNumber);

View File

@ -153,7 +153,7 @@ namespace NzbDrone.Core.Download.Clients.Flood
item.Status = DownloadItemStatus.Downloading;
}
if (item.DownloadClientInfo.RemoveCompletedDownloads && item.Status == DownloadItemStatus.Completed)
if (item.Status == DownloadItemStatus.Completed)
{
// Grab cached seedConfig
var seedConfig = _downloadSeedConfigProvider.GetSeedConfiguration(item.DownloadId);
@ -165,7 +165,7 @@ namespace NzbDrone.Core.Download.Clients.Flood
// Check if seed ratio reached
item.CanMoveFiles = item.CanBeRemoved = true;
}
else if (properties.DateFinished is > 0)
else if (properties.DateFinished != null && properties.DateFinished > 0)
{
// Check if seed time reached
if ((DateTimeOffset.Now - DateTimeOffset.FromUnixTimeSeconds((long)properties.DateFinished)) >= seedConfig.SeedTime)

View File

@ -119,7 +119,7 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
break;
}
item.CanBeRemoved = item.CanMoveFiles = item.DownloadClientInfo.RemoveCompletedDownloads && torrent.Status == FreeboxDownloadTaskStatus.Done;
item.CanBeRemoved = item.CanMoveFiles = torrent.Status == FreeboxDownloadTaskStatus.Done;
queueItems.Add(item);
}

View File

@ -92,10 +92,7 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
item.Status = DownloadItemStatus.Downloading;
}
item.CanMoveFiles = item.CanBeRemoved =
item.DownloadClientInfo.RemoveCompletedDownloads &&
torrent.IsFinished &&
torrent.State == HadoukenTorrentState.Paused;
item.CanMoveFiles = item.CanBeRemoved = torrent.IsFinished && torrent.State == HadoukenTorrentState.Paused;
items.Add(item);
}

View File

@ -225,7 +225,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
foreach (var torrent in torrents)
{
var item = new DownloadClientItem
var item = new DownloadClientItem()
{
DownloadId = torrent.Hash.ToUpper(),
Category = torrent.Category.IsNotNullOrWhiteSpace() ? torrent.Category : torrent.Label,
@ -239,10 +239,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
// Avoid removing torrents that haven't reached the global max ratio.
// Removal also requires the torrent to be paused, in case a higher max ratio was set on the torrent itself (which is not exposed by the api).
item.CanMoveFiles = item.CanBeRemoved =
item.DownloadClientInfo.RemoveCompletedDownloads &&
torrent.State is "pausedUP" or "stoppedUP" &&
HasReachedSeedLimit(torrent, config);
item.CanMoveFiles = item.CanBeRemoved = torrent.State is "pausedUP" or "stoppedUP" && HasReachedSeedLimit(torrent, config);
switch (torrent.State)
{

View File

@ -117,7 +117,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
item.Status = DownloadItemStatus.Downloading;
}
item.CanBeRemoved = item.DownloadClientInfo.RemoveCompletedDownloads && HasReachedSeedLimit(torrent, item.SeedRatio, configFunc);
item.CanBeRemoved = HasReachedSeedLimit(torrent, item.SeedRatio, configFunc);
item.CanMoveFiles = item.CanBeRemoved && torrent.Status == TransmissionTorrentStatus.Stopped;
items.Add(item);

View File

@ -185,7 +185,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
// Grab cached seedConfig
var seedConfig = _downloadSeedConfigProvider.GetSeedConfiguration(torrent.Hash);
if (item.DownloadClientInfo.RemoveCompletedDownloads && torrent.IsFinished && seedConfig != null)
if (torrent.IsFinished && seedConfig != null)
{
var canRemove = false;

View File

@ -167,7 +167,6 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
// 'Started' without 'Queued' is when the torrent is 'forced seeding'
item.CanMoveFiles = item.CanBeRemoved =
item.DownloadClientInfo.RemoveCompletedDownloads &&
!torrent.Status.HasFlag(UTorrentTorrentStatus.Queued) &&
!torrent.Status.HasFlag(UTorrentTorrentStatus.Started);

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
using NLog.Fluent;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation.Extensions;
@ -246,14 +247,14 @@ namespace NzbDrone.Core.Download
}
else
{
_logger.ForDebugEvent()
_logger.Debug()
.Message("No Episodes were just imported, but all episodes were previously imported, possible issue with download history.")
.Property("SeriesId", trackedDownload.RemoteEpisode.Series.Id)
.Property("DownloadId", trackedDownload.DownloadItem.DownloadId)
.Property("Title", trackedDownload.DownloadItem.Title)
.Property("Path", trackedDownload.ImportItem.OutputPath.ToString())
.WriteSentryWarn("DownloadHistoryIncomplete")
.Log();
.Write();
}
var episodes = _episodeService.GetEpisodes(trackedDownload.RemoteEpisode.Episodes.Select(e => e.Id));

View File

@ -37,7 +37,6 @@ namespace NzbDrone.Core.Download
public string Type { get; set; }
public int Id { get; set; }
public string Name { get; set; }
public bool RemoveCompletedDownloads { get; set; }
public bool HasPostImportCategory { get; set; }
public static DownloadClientItemClientInfo FromDownloadClient<TSettings>(
@ -50,7 +49,6 @@ namespace NzbDrone.Core.Download
Type = downloadClient.Name,
Id = downloadClient.Definition.Id,
Name = downloadClient.Definition.Name,
RemoveCompletedDownloads = downloadClient.Definition is DownloadClientDefinition { RemoveCompletedDownloads: true },
HasPostImportCategory = hasPostImportCategory
};
}

View File

@ -12,7 +12,7 @@ namespace NzbDrone.Core.Download
public interface IFailedDownloadService
{
void MarkAsFailed(int historyId, bool skipRedownload = false);
void MarkAsFailed(TrackedDownload trackedDownload, bool skipRedownload = false);
void MarkAsFailed(string downloadId, bool skipRedownload = false);
void Check(TrackedDownload trackedDownload);
void ProcessFailed(TrackedDownload trackedDownload);
}
@ -20,6 +20,7 @@ namespace NzbDrone.Core.Download
public class FailedDownloadService : IFailedDownloadService
{
private readonly IHistoryService _historyService;
private readonly ITrackedDownloadService _trackedDownloadService;
private readonly IEventAggregator _eventAggregator;
public FailedDownloadService(IHistoryService historyService,
@ -27,6 +28,7 @@ namespace NzbDrone.Core.Download
IEventAggregator eventAggregator)
{
_historyService = historyService;
_trackedDownloadService = trackedDownloadService;
_eventAggregator = eventAggregator;
}
@ -35,10 +37,9 @@ namespace NzbDrone.Core.Download
var history = _historyService.Get(historyId);
var downloadId = history.DownloadId;
if (downloadId.IsNullOrWhiteSpace())
{
PublishDownloadFailedEvent(history, new List<int> { history.EpisodeId }, "Manually marked as failed", skipRedownload: skipRedownload);
PublishDownloadFailedEvent(new List<EpisodeHistory> { history }, "Manually marked as failed", skipRedownload: skipRedownload);
return;
}
@ -52,19 +53,21 @@ namespace NzbDrone.Core.Download
}
// Add any other history items for the download ID then filter out any duplicate history items.
grabbedHistory.AddRange(GetGrabbedHistory(downloadId));
grabbedHistory.AddRange(_historyService.Find(downloadId, EpisodeHistoryEventType.Grabbed));
grabbedHistory = grabbedHistory.DistinctBy(h => h.Id).ToList();
PublishDownloadFailedEvent(history, GetEpisodeIds(grabbedHistory), "Manually marked as failed");
PublishDownloadFailedEvent(grabbedHistory, "Manually marked as failed");
}
public void MarkAsFailed(TrackedDownload trackedDownload, bool skipRedownload = false)
public void MarkAsFailed(string downloadId, bool skipRedownload = false)
{
var history = GetGrabbedHistory(trackedDownload.DownloadItem.DownloadId);
var history = _historyService.Find(downloadId, EpisodeHistoryEventType.Grabbed);
if (history.Any())
{
PublishDownloadFailedEvent(history.First(), GetEpisodeIds(history), "Manually marked as failed", trackedDownload, skipRedownload: skipRedownload);
var trackedDownload = _trackedDownloadService.Find(downloadId);
PublishDownloadFailedEvent(history, "Manually marked as failed", trackedDownload, skipRedownload: skipRedownload);
}
}
@ -79,7 +82,9 @@ namespace NzbDrone.Core.Download
if (trackedDownload.DownloadItem.IsEncrypted ||
trackedDownload.DownloadItem.Status == DownloadItemStatus.Failed)
{
var grabbedItems = GetGrabbedHistory(trackedDownload.DownloadItem.DownloadId);
var grabbedItems = _historyService
.Find(trackedDownload.DownloadItem.DownloadId, EpisodeHistoryEventType.Grabbed)
.ToList();
if (grabbedItems.Empty())
{
@ -98,7 +103,9 @@ namespace NzbDrone.Core.Download
return;
}
var grabbedItems = GetGrabbedHistory(trackedDownload.DownloadItem.DownloadId);
var grabbedItems = _historyService
.Find(trackedDownload.DownloadItem.DownloadId, EpisodeHistoryEventType.Grabbed)
.ToList();
if (grabbedItems.Empty())
{
@ -117,17 +124,18 @@ namespace NzbDrone.Core.Download
}
trackedDownload.State = TrackedDownloadState.Failed;
PublishDownloadFailedEvent(grabbedItems.First(), GetEpisodeIds(grabbedItems), failure, trackedDownload);
PublishDownloadFailedEvent(grabbedItems, failure, trackedDownload);
}
private void PublishDownloadFailedEvent(EpisodeHistory historyItem, List<int> episodeIds, string message, TrackedDownload trackedDownload = null, bool skipRedownload = false)
private void PublishDownloadFailedEvent(List<EpisodeHistory> historyItems, string message, TrackedDownload trackedDownload = null, bool skipRedownload = false)
{
var historyItem = historyItems.Last();
Enum.TryParse(historyItem.Data.GetValueOrDefault(EpisodeHistory.RELEASE_SOURCE, ReleaseSourceType.Unknown.ToString()), out ReleaseSourceType releaseSource);
var downloadFailedEvent = new DownloadFailedEvent
{
SeriesId = historyItem.SeriesId,
EpisodeIds = episodeIds,
EpisodeIds = historyItems.Select(h => h.EpisodeId).Distinct().ToList(),
Quality = historyItem.Quality,
SourceTitle = historyItem.SourceTitle,
DownloadClient = historyItem.Data.GetValueOrDefault(EpisodeHistory.DOWNLOAD_CLIENT),
@ -137,23 +145,10 @@ namespace NzbDrone.Core.Download
TrackedDownload = trackedDownload,
Languages = historyItem.Languages,
SkipRedownload = skipRedownload,
ReleaseSource = releaseSource,
ReleaseSource = releaseSource
};
_eventAggregator.PublishEvent(downloadFailedEvent);
}
private List<int> GetEpisodeIds(List<EpisodeHistory> historyItems)
{
return historyItems.Select(h => h.EpisodeId).Distinct().ToList();
}
private List<EpisodeHistory> GetGrabbedHistory(string downloadId)
{
// Sort by date so items are always in the same order
return _historyService.Find(downloadId, EpisodeHistoryEventType.Grabbed)
.OrderByDescending(h => h.Date)
.ToList();
}
}
}

View File

@ -119,7 +119,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
if (parsedEpisodeInfo != null)
{
trackedDownload.RemoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0, 0, null);
trackedDownload.RemoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0, 0);
_aggregationService.Augment(trackedDownload.RemoteEpisode);
}
@ -147,7 +147,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
// Try parsing the original source title and if that fails, try parsing it as a special
// TODO: Pass the TVDB ID and TVRage IDs in as well so we have a better chance for finding the item
parsedEpisodeInfo = Parser.Parser.ParseTitle(firstHistoryItem.SourceTitle) ??
_parsingService.ParseSpecialEpisodeTitle(parsedEpisodeInfo, firstHistoryItem.SourceTitle, 0, 0, null);
_parsingService.ParseSpecialEpisodeTitle(parsedEpisodeInfo, firstHistoryItem.SourceTitle, 0, 0);
if (parsedEpisodeInfo != null)
{
@ -234,7 +234,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
{
var parsedEpisodeInfo = Parser.Parser.ParseTitle(trackedDownload.DownloadItem.Title);
trackedDownload.RemoteEpisode = parsedEpisodeInfo == null ? null : _parsingService.Map(parsedEpisodeInfo, 0, 0, null);
trackedDownload.RemoteEpisode = parsedEpisodeInfo == null ? null : _parsingService.Map(parsedEpisodeInfo, 0, 0);
_aggregationService.Augment(trackedDownload.RemoteEpisode);
}

View File

@ -1,6 +1,5 @@
using System.Text.RegularExpressions;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
{

View File

@ -20,6 +20,26 @@ namespace NzbDrone.Core
return actual;
}
public static long Megabytes(this int megabytes)
{
return Convert.ToInt64(megabytes * 1024L * 1024L);
}
public static long Gigabytes(this int gigabytes)
{
return Convert.ToInt64(gigabytes * 1024L * 1024L * 1024L);
}
public static long Megabytes(this double megabytes)
{
return Convert.ToInt64(megabytes * 1024L * 1024L);
}
public static long Gigabytes(this double gigabytes)
{
return Convert.ToInt64(gigabytes * 1024L * 1024L * 1024L);
}
public static long Round(this long number, long level)
{
return Convert.ToInt64(Math.Floor((decimal)number / level) * level);

View File

@ -165,7 +165,6 @@ namespace NzbDrone.Core.History
history.Data.Add("Guid", message.Episode.Release.Guid);
history.Data.Add("TvdbId", message.Episode.Release.TvdbId.ToString());
history.Data.Add("TvRageId", message.Episode.Release.TvRageId.ToString());
history.Data.Add("ImdbId", message.Episode.Release.ImdbId);
history.Data.Add("Protocol", ((int)message.Episode.Release.DownloadProtocol).ToString());
history.Data.Add("CustomFormatScore", message.Episode.CustomFormatScore.ToString());
history.Data.Add("SeriesMatchType", message.Episode.SeriesMatchType.ToString());

View File

@ -10,7 +10,7 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
{
public abstract class SearchCriteriaBase
{
private static readonly Regex SpecialCharacter = new Regex(@"['.\u0060\u00B4\u2018\u2019]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex SpecialCharacter = new Regex(@"[`'.]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex NonWord = new Regex(@"[\W]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex BeginningThe = new Regex(@"^the\s", RegexOptions.IgnoreCase | RegexOptions.Compiled);

View File

@ -193,7 +193,7 @@ namespace NzbDrone.Core.IndexerSearch
foreach (var item in dict)
{
item.Value.Episodes = item.Value.Episodes.Distinct().ToList();
item.Value.SceneTitles = item.Value.SceneTitles.Distinct(StringComparer.InvariantCultureIgnoreCase).ToList();
item.Value.SceneTitles = item.Value.SceneTitles.Distinct().ToList();
}
return dict.Values.ToList();
@ -221,7 +221,7 @@ namespace NzbDrone.Core.IndexerSearch
foreach (var item in dict)
{
item.Value.SceneTitles = item.Value.SceneTitles.Distinct(StringComparer.InvariantCultureIgnoreCase).ToList();
item.Value.SceneTitles = item.Value.SceneTitles.Distinct().ToList();
}
return dict.Values.ToList();
@ -463,7 +463,7 @@ namespace NzbDrone.Core.IndexerSearch
spec.UserInvokedSearch = userInvokedSearch;
spec.InteractiveSearch = interactiveSearch;
if (!spec.SceneTitles.Contains(series.Title, StringComparer.InvariantCultureIgnoreCase))
if (!spec.SceneTitles.Contains(series.Title))
{
spec.SceneTitles.Add(series.Title);
}

View File

@ -95,11 +95,6 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
torrentInfo.TvRageId = torrent.TvrageID.Value;
}
if (torrent.ImdbID.IsNotNullOrWhiteSpace() && int.TryParse(torrent.ImdbID, out var imdbId) && imdbId > 0)
{
torrentInfo.ImdbId = $"tt{imdbId:D7}";
}
results.Add(torrentInfo);
}

View File

@ -38,7 +38,7 @@ namespace NzbDrone.Core.Indexers.FileList
{
var id = result.Id;
var torrentInfo = new TorrentInfo
torrentInfos.Add(new TorrentInfo
{
Guid = $"FileList-{id}",
Title = result.Name,
@ -48,15 +48,9 @@ namespace NzbDrone.Core.Indexers.FileList
Seeders = result.Seeders,
Peers = result.Leechers + result.Seeders,
PublishDate = result.UploadDate.ToUniversalTime(),
ImdbId = result.ImdbId,
IndexerFlags = GetIndexerFlags(result)
};
if (result.ImdbId is { Length: > 2 } && int.TryParse(result.ImdbId.TrimStart('t'), out var imdbId) && imdbId > 0)
{
torrentInfo.ImdbId = $"tt{imdbId:D7}";
}
torrentInfos.Add(torrentInfo);
});
}
return torrentInfos.ToArray();

View File

@ -61,7 +61,6 @@ namespace NzbDrone.Core.Indexers.HDBits
Seeders = result.Seeders,
Peers = result.Leechers + result.Seeders,
PublishDate = result.Added.ToUniversalTime(),
TvdbId = result.TvdbInfo?.Id ?? 0,
IndexerFlags = GetIndexerFlags(result)
});
}

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using FluentValidation.Results;
using NLog;
@ -19,6 +20,8 @@ namespace NzbDrone.Core.Indexers
public abstract class IndexerBase<TSettings> : IIndexer
where TSettings : IIndexerSettings, new()
{
private static readonly Regex MultiRegex = new (@"[_. ](?<multi>multi)[_. ]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
protected readonly IIndexerStatusService _indexerStatusService;
protected readonly IConfigService _configService;
protected readonly IParsingService _parsingService;
@ -91,7 +94,7 @@ namespace NzbDrone.Core.Indexers
result.ForEach(c =>
{
// Use multi languages from setting if ReleaseInfo languages is empty
if (c.Languages.Empty() && settings.MultiLanguages.Any() && Parser.Parser.HasMultipleLanguages(c.Title))
if (c.Languages.Empty() && MultiRegex.IsMatch(c.Title) && settings.MultiLanguages.Any())
{
c.Languages = settings.MultiLanguages.Select(i => (Language)i).ToList();
}

View File

@ -90,7 +90,6 @@ namespace NzbDrone.Core.Indexers.Newznab
releaseInfo.TvdbId = GetTvdbId(item);
releaseInfo.TvRageId = GetTvRageId(item);
releaseInfo.ImdbId = GetImdbId(item);
return releaseInfo;
}
@ -183,18 +182,6 @@ namespace NzbDrone.Core.Indexers.Newznab
return 0;
}
protected virtual string GetImdbId(XElement item)
{
var imdbIdString = TryGetNewznabAttribute(item, "imdb");
if (!imdbIdString.IsNullOrWhiteSpace() && int.TryParse(imdbIdString, out var imdbId) && imdbId > 0)
{
return $"tt{imdbId:D7}";
}
return null;
}
protected string TryGetNewznabAttribute(XElement item, string key, string defaultValue = "")
{
var attrElement = item.Elements(ns + "attr").FirstOrDefault(e => e.Attribute("name").Value.Equals(key, StringComparison.OrdinalIgnoreCase));

View File

@ -83,7 +83,6 @@ namespace NzbDrone.Core.Indexers.Torznab
{
torrentInfo.TvdbId = GetTvdbId(item);
torrentInfo.TvRageId = GetTvRageId(item);
releaseInfo.ImdbId = GetImdbId(item);
torrentInfo.IndexerFlags = GetFlags(item);
}
@ -178,18 +177,6 @@ namespace NzbDrone.Core.Indexers.Torznab
return 0;
}
protected virtual string GetImdbId(XElement item)
{
var imdbIdString = TryGetTorznabAttribute(item, "imdb");
if (!imdbIdString.IsNullOrWhiteSpace() && int.TryParse(imdbIdString, out var imdbId) && imdbId > 0)
{
return $"tt{imdbId:D7}";
}
return null;
}
protected override string GetInfoHash(XElement item)
{
return TryGetTorznabAttribute(item, "infohash");

View File

@ -33,25 +33,22 @@ namespace NzbDrone.Core.Instrumentation
LogManager.Configuration.AddTarget("DbLogger", target);
LogManager.Configuration.LoggingRules.Add(Rule);
LogManager.ConfigurationChanged += OnLogManagerOnConfigurationReloaded;
LogManager.ConfigurationReloaded += OnLogManagerOnConfigurationReloaded;
LogManager.ReconfigExistingLoggers();
}
public void UnRegister()
{
LogManager.ConfigurationChanged -= OnLogManagerOnConfigurationReloaded;
LogManager.ConfigurationReloaded -= OnLogManagerOnConfigurationReloaded;
LogManager.Configuration.RemoveTarget("DbLogger");
LogManager.Configuration.LoggingRules.Remove(Rule);
LogManager.ReconfigExistingLoggers();
Dispose();
}
private void OnLogManagerOnConfigurationReloaded(object sender, LoggingConfigurationChangedEventArgs args)
private void OnLogManagerOnConfigurationReloaded(object sender, LoggingConfigurationReloadedEventArgs args)
{
if (args.ActivatedConfiguration != null)
{
Register();
}
Register();
}
public LoggingRule Rule { get; set; }

View File

@ -2,7 +2,6 @@ using System.Collections.Generic;
using System.Linq;
using NLog;
using NLog.Config;
using NLog.Targets;
using NLog.Targets.Syslog;
using NLog.Targets.Syslog.Settings;
using NzbDrone.Common.EnvironmentInfo;
@ -52,14 +51,13 @@ namespace NzbDrone.Core.Instrumentation
var rules = LogManager.Configuration.LoggingRules;
// Console
ReconfigureConsole();
SetMinimumLogLevel(rules, "consoleLogger", minimumConsoleLogLevel);
// Log Files
SetMinimumLogLevel(rules, "appFileInfo", minimumLogLevel <= LogLevel.Info ? LogLevel.Info : LogLevel.Off);
SetMinimumLogLevel(rules, "appFileDebug", minimumLogLevel <= LogLevel.Debug ? LogLevel.Debug : LogLevel.Off);
SetMinimumLogLevel(rules, "appFileTrace", minimumLogLevel <= LogLevel.Trace ? LogLevel.Trace : LogLevel.Off);
ReconfigureFile();
SetLogRotation();
// Log Sql
SqlBuilderExtensions.LogSql = _configFileProvider.LogSql;
@ -93,12 +91,11 @@ namespace NzbDrone.Core.Instrumentation
}
}
private void ReconfigureFile()
private void SetLogRotation()
{
foreach (var target in LogManager.Configuration.AllTargets.OfType<NzbDroneFileTarget>())
{
target.MaxArchiveFiles = _configFileProvider.LogRotate;
target.ArchiveAboveSize = _configFileProvider.LogSizeLimit.Megabytes();
}
}
@ -112,22 +109,6 @@ namespace NzbDrone.Core.Instrumentation
}
}
private void ReconfigureConsole()
{
var consoleTarget = LogManager.Configuration.AllTargets.OfType<ColoredConsoleTarget>().FirstOrDefault();
if (consoleTarget != null)
{
var format = _configFileProvider.ConsoleLogFormat;
consoleTarget.Layout = format switch
{
ConsoleLogFormat.Clef => NzbDroneLogger.ClefLogLayout,
_ => NzbDroneLogger.ConsoleLogLayout
};
}
}
private void SetSyslogParameters(string syslogServer, int syslogPort, LogLevel minimumLogLevel)
{
var syslogTarget = new SyslogTarget();
@ -136,7 +117,7 @@ namespace NzbDrone.Core.Instrumentation
syslogTarget.MessageSend.Protocol = ProtocolType.Udp;
syslogTarget.MessageSend.Udp.Port = syslogPort;
syslogTarget.MessageSend.Udp.Server = syslogServer;
syslogTarget.MessageSend.Retry.ConstantBackoff.BaseDelay = 500;
syslogTarget.MessageSend.Udp.ReconnectInterval = 500;
syslogTarget.MessageCreation.Rfc = RfcNumber.Rfc5424;
syslogTarget.MessageCreation.Rfc5424.AppName = _configFileProvider.InstanceName;

View File

@ -1,6 +1,4 @@
{
"AddAutoTag": "أضف كلمات دلالية تلقائيا",
"AddCondition": "إضافة شرط",
"AutoTaggingNegateHelpText": "إذا تم تحديده ، فلن يتم تطبيق التنسيق المخصص إذا تطابق شرط {implementationName} هذا.",
"ConnectionLostReconnect": "سيحاول {appName} الاتصال تلقائيًا ، أو يمكنك النقر فوق إعادة التحميل أدناه."
"AddCondition": "إضافة شرط"
}

View File

@ -1325,8 +1325,8 @@
"NotificationsEmailSettingsUseEncryption": "Use Encryption",
"NotificationsEmailSettingsUseEncryptionHelpText": "Whether to prefer using encryption if configured on the server, to always use encryption via SSL (Port 465 only) or StartTLS (any other port) or to never use encryption",
"NotificationsEmbySettingsSendNotifications": "Send Notifications",
"NotificationsEmbySettingsSendNotificationsHelpText": "Have Emby send notifications to configured providers. Not supported on Jellyfin.",
"NotificationsEmbySettingsUpdateLibraryHelpText": "Update Library on Import, Rename, or Delete",
"NotificationsEmbySettingsSendNotificationsHelpText": "Have MediaBrowser send notifications to configured providers",
"NotificationsEmbySettingsUpdateLibraryHelpText": "Update Library on Import, Rename, or Delete?",
"NotificationsGotifySettingIncludeSeriesPoster": "Include Series Poster",
"NotificationsGotifySettingIncludeSeriesPosterHelpText": "Include series poster in message",
"NotificationsGotifySettingsAppToken": "App Token",
@ -1429,8 +1429,6 @@
"NotificationsTelegramSettingsChatIdHelpText": "You must start a conversation with the bot or add it to your group to receive messages",
"NotificationsTelegramSettingsIncludeAppName": "Include {appName} in Title",
"NotificationsTelegramSettingsIncludeAppNameHelpText": "Optionally prefix message title with {appName} to differentiate notifications from different applications",
"NotificationsTelegramSettingsMetadataLinks": "Metadata Links",
"NotificationsTelegramSettingsMetadataLinksHelpText": "Add a links to series metadata when sending notifications",
"NotificationsTelegramSettingsSendSilently": "Send Silently",
"NotificationsTelegramSettingsSendSilentlyHelpText": "Sends the message silently. Users will receive a notification with no sound",
"NotificationsTelegramSettingsTopicId": "Topic ID",

View File

@ -1401,7 +1401,7 @@
"NotificationsEmailSettingsBccAddressHelpText": "Lista separada por coma de destinatarios de e-mail bcc",
"NotificationsEmailSettingsName": "E-mail",
"NotificationsEmailSettingsRecipientAddress": "Dirección(es) de destinatario",
"NotificationsEmbySettingsSendNotificationsHelpText": "Hace que Emby envíe notificaciones a los proveedores configurados. No soportado en Jellyfin.",
"NotificationsEmbySettingsSendNotificationsHelpText": "Hacer que MediaBrowser envíe notificaciones a los proveedores configurados",
"NotificationsGotifySettingsAppToken": "Token de app",
"NotificationsGotifySettingIncludeSeriesPosterHelpText": "Incluye poster de serie en mensaje",
"NotificationsJoinSettingsDeviceNames": "Nombres de dispositivo",
@ -1839,7 +1839,7 @@
"Titles": "Títulos",
"ToggleUnmonitoredToMonitored": "Sin monitorizar, haz clic para monitorizar",
"TotalFileSize": "Tamaño total de archivo",
"UpdateAvailableHealthCheckMessage": "Hay disponible una nueva actualización: {version}",
"UpdateAvailableHealthCheckMessage": "Hay disponible una nueva actualización",
"UpgradeUntilCustomFormatScore": "Actualizar hasta la puntuación de formato personalizado",
"UrlBase": "URL base",
"UseSsl": "Usar SSL",
@ -1919,7 +1919,7 @@
"NotificationsDiscordSettingsWebhookUrlHelpText": "URL de canal webhook de Discord",
"NotificationsEmailSettingsCcAddress": "Dirección(es) CC",
"NotificationsEmbySettingsSendNotifications": "Enviar notificaciones",
"NotificationsEmbySettingsUpdateLibraryHelpText": "Actualiza biblioteca al importar, renombrar o borrar",
"NotificationsEmbySettingsUpdateLibraryHelpText": "¿Actualiza biblioteca en importar, renombrar o borrar?",
"NotificationsJoinSettingsDeviceIdsHelpText": "En desuso, usar Nombres de dispositivo en su lugar. Lista separada por coma de los IDs de dispositivo a los que te gustaría enviar notificaciones. Si no se establece, todos los dispositivos recibirán notificaciones.",
"NotificationsPushoverSettingsExpire": "Caduca",
"NotificationsMailgunSettingsSenderDomain": "Dominio del remitente",
@ -2093,12 +2093,5 @@
"CountVotes": "{votes} votos",
"InstallMajorVersionUpdateMessage": "Esta actualización instalará una nueva versión principal y podría no ser compatible con tu sistema. ¿Estás seguro que quieres instalar esta actualización?",
"InstallMajorVersionUpdateMessageLink": "Por favor revisa [{domain}]({url}) para más información.",
"NextAiringDate": "Siguiente emisión: {date}",
"SeasonsMonitoredAll": "Todas",
"SeasonsMonitoredNone": "Ninguna",
"SeasonsMonitoredStatus": "Temporadas monitorizadas",
"NoBlocklistItems": "Ningún elemento en la lista de bloqueo",
"SeasonsMonitoredPartial": "Parcial",
"NotificationsTelegramSettingsMetadataLinksHelpText": "Añade un enlace a los metadatos de la serie cuando se envían notificaciones",
"NotificationsTelegramSettingsMetadataLinks": "Enlaces de metadatos"
"NextAiringDate": "Siguiente emisión: {date}"
}

View File

@ -299,7 +299,7 @@
"Sunday": "Dimanche",
"TorrentDelay": "Retard du torrent",
"DownloadClients": "Clients de télécharg.",
"CustomFormats": "Formats personnalisés",
"CustomFormats": "Formats perso.",
"NoIndexersFound": "Aucun indexeur n'a été trouvé",
"Profiles": "Profils",
"Dash": "Tiret",
@ -431,7 +431,7 @@
"Replace": "Remplacer",
"ResetAPIKeyMessageText": "Êtes-vous sûr de vouloir réinitialiser votre clé API ?",
"StopSelecting": "Effacer la sélection",
"WhatsNew": "Quoi de neuf ?",
"WhatsNew": "Quoi de neuf ?",
"EditDownloadClientImplementation": "Modifier le client de téléchargement - {implementationName}",
"External": "Externe",
"Monday": "Lundi",
@ -452,7 +452,7 @@
"RootFolderSelectFreeSpace": "{freeSpace} Libre",
"WantMoreControlAddACustomFormat": "Vous souhaitez avoir plus de contrôle sur les téléchargements préférés ? Ajoutez un [Format personnalisé](/settings/customformats)",
"RemoveSelectedItemsQueueMessageText": "Voulez-vous vraiment supprimer {selectedCount} éléments de la file d'attente ?",
"UpdateAll": "Tout mettre à jour",
"UpdateAll": "Tout actualiser",
"EnableSslHelpText": "Nécessite un redémarrage en tant qu'administrateur pour être effectif",
"UnmonitorDeletedEpisodesHelpText": "Les épisodes effacés du disque dur ne seront plus surveillés dans {appName}",
"RssSync": "Synchronisation RSS",
@ -1130,7 +1130,7 @@
"NotificationsTagsSeriesHelpText": "N'envoyer des notifications que pour les séries avec au moins une balise correspondante",
"OnApplicationUpdate": "Lors de la mise à jour de l'application",
"OnEpisodeFileDelete": "Lors de la suppression du fichier de l'épisode",
"OnHealthIssue": "Lors de problème de santé",
"OnHealthIssue": "Sur la question de la santé",
"OnManualInteractionRequired": "Sur l'interaction manuelle requise",
"OnRename": "Au renommage",
"PreferredSize": "Taille préférée",
@ -1176,7 +1176,7 @@
"Total": "Total",
"Upcoming": "À venir",
"UpdateAutomaticallyHelpText": "Téléchargez et installez automatiquement les mises à jour. Vous pourrez toujours installer à partir du système : mises à jour",
"UpdateAvailableHealthCheckMessage": "Une nouvelle mise à jour est disponible : {version}",
"UpdateAvailableHealthCheckMessage": "Une nouvelle mise à jour est disponible",
"UpdateFiltered": "Mise à jour filtrée",
"IconForSpecialsHelpText": "Afficher l'icône pour les épisodes spéciaux (saison 0)",
"Ignored": "Ignoré",
@ -1276,7 +1276,7 @@
"ConnectSettingsSummary": "Notifications, connexions aux serveurs/lecteurs de médias et scripts personnalisés",
"CopyToClipboard": "Copier dans le presse-papier",
"CreateEmptySeriesFolders": "Créer des dossiers de séries vides",
"Custom": "Personnaliser",
"Custom": "Customisé",
"CopyUsingHardlinksSeriesHelpText": "Les liens physiques permettent à {appName} d'importer des torrents dans le dossier de la série sans prendre d'espace disque supplémentaire ni copier l'intégralité du contenu du fichier. Les liens physiques ne fonctionneront que si la source et la destination sont sur le même volume",
"CustomFormatsSettingsSummary": "Formats et paramètres personnalisés",
"CustomFormatsSettings": "Paramètre des formats personnalisés",
@ -1724,8 +1724,8 @@
"NotificationsGotifySettingsPriorityHelpText": "Priorité de la notification",
"NotificationsGotifySettingsAppTokenHelpText": "Le jeton d'application généré par Gotify",
"NotificationsGotifySettingsAppToken": "Jeton d'app",
"NotificationsEmbySettingsUpdateLibraryHelpText": "Mettre à jour la bibliothèque lors de l'importation, du changement de nom ou de la suppression",
"NotificationsEmbySettingsSendNotificationsHelpText": "Demandez à Emby d'envoyer des notifications aux fournisseurs configurés. Non pris en charge sur Jellyfin.",
"NotificationsEmbySettingsUpdateLibraryHelpText": "Mettre à jour la bibliothèque lors d'import, de renommage ou de suppression ?",
"NotificationsEmbySettingsSendNotificationsHelpText": "Faire en sorte que MediaBrowser envoie des notifications aux fournisseurs configurés",
"NotificationsEmbySettingsSendNotifications": "Envoyer des notifications",
"NotificationsEmailSettingsServerHelpText": "Nom d'hôte ou adresse IP du serveur de courriel",
"NotificationsEmailSettingsServer": "Serveur",
@ -2076,29 +2076,5 @@
"UnableToImportAutomatically": "Impossible d'importer automatiquement",
"DayOfWeekAt": "{day} à {time}",
"TomorrowAt": "Demain à {time}",
"TodayAt": "Aujourd'hui à {time}",
"ShowTagsHelpText": "Afficher les labels sous l'affiche",
"ShowTags": "Afficher les labels",
"CountVotes": "{votes} votes",
"NoBlocklistItems": "Aucun élément de la liste de blocage",
"NotificationsTelegramSettingsMetadataLinksHelpText": "Ajouter un lien vers les métadonnées de la série lors de l'envoi de notifications",
"RatingVotes": "Votes de notation",
"OnFileImport": "Lors de l'importation du fichier",
"OnImportComplete": "Une fois l'importation terminée",
"NotificationsPlexSettingsServer": "Serveur",
"NotificationsPlexSettingsServerHelpText": "Sélectionnez le serveur à partir du compte plex.tv après l'authentification",
"OnFileUpgrade": "Lors de la mise à jour du fichier",
"NextAiringDate": "Prochaine diffusion : {date}",
"CustomColonReplacement": "Remplacement personnalisé des deuxpoints",
"CustomColonReplacementFormatHelpText": "Caractères à utiliser en remplacement des deux-points",
"CustomColonReplacementFormatHint": "Caractère valide du système de fichiers tel que deux-points (lettre)",
"Install": "Installer",
"InstallMajorVersionUpdate": "Installer la mise à jour",
"InstallMajorVersionUpdateMessage": "Cette mise à jour installera une nouvelle version majeure et pourrait ne pas être compatible avec votre système. Êtes-vous sûr de vouloir installer cette mise à jour ?",
"InstallMajorVersionUpdateMessageLink": "Veuillez consulter [{domain}]({url}) pour plus d'informations.",
"SeasonsMonitoredAll": "Toutes",
"SeasonsMonitoredPartial": "Partielle",
"SeasonsMonitoredNone": "Aucune",
"SeasonsMonitoredStatus": "Saisons surveillées",
"NotificationsTelegramSettingsMetadataLinks": "Liens de métadonnées"
"TodayAt": "Aujourd'hui à {time}"
}

Some files were not shown because too many files have changed in this diff Show More