Compare commits
31 Commits
transmissi
...
develop
Author | SHA1 | Date |
---|---|---|
Bogdan | 2f04b037a1 | |
Bogdan | 7b87de2e93 | |
Bogdan | eb2fd13509 | |
Bogdan | ffdb08cfe6 | |
Mark McDowall | 37c4647f24 | |
Mark McDowall | f7a58aab33 | |
Mark McDowall | 4b186e894e | |
kephasdev | 35a2bc9403 | |
Bogdan | cc03ce04f1 | |
Bogdan | 363f8fc347 | |
RaZaSB | 0877a6718d | |
Bogdan | 8b253c36ea | |
Bogdan | e6f82270a9 | |
Mark McDowall | 813965e6a2 | |
Mark McDowall | 0d914f4c53 | |
Mark McDowall | ae7f73208a | |
Weblate | 4c86d673ea | |
Weblate | b1527f9abb | |
Bogdan | 291d792810 | |
Mark McDowall | 9b528eb829 | |
Mark McDowall | 4c0b896174 | |
Bogdan | 4ff83f9efc | |
Bogdan | 217611d716 | |
Mark McDowall | 1299a97579 | |
Mark McDowall | 4c0de55672 | |
Bogdan | 78a0def46a | |
Mark McDowall | 11a9dcb389 | |
Mark McDowall | 4eab168267 | |
Bogdan | c9b5a1258a | |
Mark McDowall | 9127a91dfc | |
Weblate | cc85a28ff7 |
|
@ -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 that 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 the selected 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 doesnt exist
|
||||
# -f to Force so we fail if it doesn't exist
|
||||
rm -f "${app^}".*.tar.gz
|
||||
echo ""
|
||||
echo "Downloading..."
|
||||
|
|
|
@ -359,11 +359,16 @@ module.exports = {
|
|||
],
|
||||
|
||||
rules: Object.assign(typescriptEslintRecommended.rules, {
|
||||
'no-shadow': 'off',
|
||||
// These should be enabled after cleaning things up
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
args: 'after-used',
|
||||
argsIgnorePattern: '^_',
|
||||
ignoreRestSiblings: true
|
||||
}
|
||||
],
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'react/prop-types': 'off',
|
||||
'no-shadow': 'off',
|
||||
'prettier/prettier': 'error',
|
||||
'simple-import-sort/imports': [
|
||||
'error',
|
||||
|
@ -376,7 +381,41 @@ 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'
|
||||
})
|
||||
},
|
||||
{
|
||||
|
|
|
@ -59,6 +59,7 @@ function Blocklist() {
|
|||
sortKey,
|
||||
sortDirection,
|
||||
page,
|
||||
pageSize,
|
||||
totalPages,
|
||||
totalRecords,
|
||||
isRemoving,
|
||||
|
@ -223,6 +224,7 @@ function Blocklist() {
|
|||
<PageToolbarSection alignContent={align.RIGHT}>
|
||||
<TableOptionsModalWrapper
|
||||
columns={columns}
|
||||
pageSize={pageSize}
|
||||
onTableOptionChange={handleTableOptionChange}
|
||||
>
|
||||
<PageToolbarButton
|
||||
|
@ -264,6 +266,7 @@ function Blocklist() {
|
|||
allSelected={allSelected}
|
||||
allUnselected={allUnselected}
|
||||
columns={columns}
|
||||
pageSize={pageSize}
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onTableOptionChange={handleTableOptionChange}
|
||||
|
|
|
@ -53,6 +53,7 @@ function History() {
|
|||
sortKey,
|
||||
sortDirection,
|
||||
page,
|
||||
pageSize,
|
||||
totalPages,
|
||||
totalRecords,
|
||||
} = useSelector((state: AppState) => state.history);
|
||||
|
@ -154,6 +155,7 @@ function History() {
|
|||
<PageToolbarSection alignContent={align.RIGHT}>
|
||||
<TableOptionsModalWrapper
|
||||
columns={columns}
|
||||
pageSize={pageSize}
|
||||
onTableOptionChange={handleTableOptionChange}
|
||||
>
|
||||
<PageToolbarButton
|
||||
|
@ -193,6 +195,7 @@ function History() {
|
|||
<div>
|
||||
<Table
|
||||
columns={columns}
|
||||
pageSize={pageSize}
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onTableOptionChange={handleTableOptionChange}
|
||||
|
|
|
@ -73,6 +73,7 @@ function Queue() {
|
|||
sortKey,
|
||||
sortDirection,
|
||||
page,
|
||||
pageSize,
|
||||
totalPages,
|
||||
totalRecords,
|
||||
isGrabbing,
|
||||
|
@ -269,8 +270,10 @@ function Queue() {
|
|||
allSelected={allSelected}
|
||||
allUnselected={allUnselected}
|
||||
columns={columns}
|
||||
pageSize={pageSize}
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
optionsComponent={QueueOptions}
|
||||
onTableOptionChange={handleTableOptionChange}
|
||||
onSelectAllChange={handleSelectAllChange}
|
||||
onSortPress={handleSortPress}
|
||||
|
@ -344,6 +347,7 @@ function Queue() {
|
|||
<PageToolbarSection alignContent={align.RIGHT}>
|
||||
<TableOptionsModalWrapper
|
||||
columns={columns}
|
||||
pageSize={pageSize}
|
||||
maxPageSize={200}
|
||||
optionsComponent={QueueOptions}
|
||||
onTableOptionChange={handleTableOptionChange}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import React, { Fragment, useCallback } from 'react';
|
||||
import React, { 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 { setQueueOption } from 'Store/Actions/queueActions';
|
||||
import { gotoQueuePage, setQueueOption } from 'Store/Actions/queueActions';
|
||||
import { CheckInputChanged } from 'typings/inputs';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
|
@ -22,24 +22,26 @@ function QueueOptions() {
|
|||
[name]: value,
|
||||
})
|
||||
);
|
||||
|
||||
if (name === 'includeUnknownSeriesItems') {
|
||||
dispatch(gotoQueuePage({ page: 1 }));
|
||||
}
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('ShowUnknownSeriesItems')}</FormLabel>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('ShowUnknownSeriesItems')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="includeUnknownSeriesItems"
|
||||
value={includeUnknownSeriesItems}
|
||||
helpText={translate('ShowUnknownSeriesItemsHelpText')}
|
||||
onChange={handleOptionChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Fragment>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="includeUnknownSeriesItems"
|
||||
value={includeUnknownSeriesItems}
|
||||
helpText={translate('ShowUnknownSeriesItemsHelpText')}
|
||||
onChange={handleOptionChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -26,4 +26,5 @@
|
|||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 70px;
|
||||
text-align: right;
|
||||
}
|
||||
|
|
|
@ -35,6 +35,10 @@ 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>
|
||||
|
@ -51,9 +55,7 @@ function AppRoutes() {
|
|||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
addUrlBase={false}
|
||||
render={() => {
|
||||
return <Redirect to={getPathWithUrlBase('/')} />;
|
||||
}}
|
||||
render={RedirectWithUrlBase}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
@ -61,21 +63,9 @@ function AppRoutes() {
|
|||
|
||||
<Route path="/add/import" component={ImportSeries} />
|
||||
|
||||
<Route
|
||||
path="/serieseditor"
|
||||
exact={true}
|
||||
render={() => {
|
||||
return <Redirect to={getPathWithUrlBase('/')} />;
|
||||
}}
|
||||
/>
|
||||
<Route path="/serieseditor" exact={true} render={RedirectWithUrlBase} />
|
||||
|
||||
<Route
|
||||
path="/seasonpass"
|
||||
exact={true}
|
||||
render={() => {
|
||||
return <Redirect to={getPathWithUrlBase('/')} />;
|
||||
}}
|
||||
/>
|
||||
<Route path="/seasonpass" exact={true} render={RedirectWithUrlBase} />
|
||||
|
||||
<Route path="/series/:titleSlug" component={SeriesDetailsPageConnector} />
|
||||
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import React from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import Popover from 'Components/Tooltip/Popover';
|
||||
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||
|
@ -82,9 +82,7 @@ function EpisodeNumber(props: EpisodeNumberProps) {
|
|||
<Popover
|
||||
anchor={
|
||||
<span>
|
||||
{showSeasonNumber && seasonNumber != null && (
|
||||
<Fragment>{seasonNumber}x</Fragment>
|
||||
)}
|
||||
{showSeasonNumber && seasonNumber != null && <>{seasonNumber}x</>}
|
||||
|
||||
{showSeasonNumber ? padNumber(episodeNumber, 2) : episodeNumber}
|
||||
|
||||
|
@ -111,9 +109,7 @@ function EpisodeNumber(props: EpisodeNumberProps) {
|
|||
/>
|
||||
) : (
|
||||
<span>
|
||||
{showSeasonNumber && seasonNumber != null && (
|
||||
<Fragment>{seasonNumber}x</Fragment>
|
||||
)}
|
||||
{showSeasonNumber && seasonNumber != null && <>{seasonNumber}x</>}
|
||||
|
||||
{showSeasonNumber ? padNumber(episodeNumber, 2) : episodeNumber}
|
||||
|
||||
|
|
|
@ -3,15 +3,15 @@ import { useCallback, useState } from 'react';
|
|||
export default function useModalOpenState(
|
||||
initialState: boolean
|
||||
): [boolean, () => void, () => void] {
|
||||
const [isOpen, setOpen] = useState(initialState);
|
||||
const [isOpen, setIsOpen] = useState(initialState);
|
||||
|
||||
const setModalOpen = useCallback(() => {
|
||||
setOpen(true);
|
||||
}, [setOpen]);
|
||||
setIsOpen(true);
|
||||
}, [setIsOpen]);
|
||||
|
||||
const setModalClosed = useCallback(() => {
|
||||
setOpen(false);
|
||||
}, [setOpen]);
|
||||
setIsOpen(false);
|
||||
}, [setIsOpen]);
|
||||
|
||||
return [isOpen, setModalOpen, setModalClosed];
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -17,7 +17,7 @@ function SelectLanguageModal(props: SelectLanguageModalProps) {
|
|||
props;
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onModalClose={onModalClose} size={sizes.MEDIUM}>
|
||||
<Modal isOpen={isOpen} size={sizes.MEDIUM} onModalClose={onModalClose}>
|
||||
<SelectLanguageModalContent
|
||||
languageIds={languageIds}
|
||||
modalTitle={modalTitle}
|
||||
|
|
|
@ -64,19 +64,20 @@ interface RowItemData {
|
|||
onSeriesSelect(seriesId: number): void;
|
||||
}
|
||||
|
||||
const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
|
||||
index,
|
||||
style,
|
||||
data,
|
||||
}) => {
|
||||
function Row({ index, style, data }: ListChildComponentProps<RowItemData>) {
|
||||
const { items, columns, onSeriesSelect } = data;
|
||||
const series = index >= items.length ? null : items[index];
|
||||
|
||||
if (index >= items.length) {
|
||||
const handlePress = useCallback(() => {
|
||||
if (series?.id) {
|
||||
onSeriesSelect(series.id);
|
||||
}
|
||||
}, [series?.id, onSeriesSelect]);
|
||||
|
||||
if (series == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const series = items[index];
|
||||
|
||||
return (
|
||||
<VirtualTableRowButton
|
||||
style={{
|
||||
|
@ -84,7 +85,7 @@ const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
|
|||
justifyContent: 'space-between',
|
||||
...style,
|
||||
}}
|
||||
onPress={() => onSeriesSelect(series.id)}
|
||||
onPress={handlePress}
|
||||
>
|
||||
<SelectSeriesRow
|
||||
key={series.id}
|
||||
|
@ -98,7 +99,7 @@ const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
|
|||
/>
|
||||
</VirtualTableRowButton>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function SelectSeriesModalContent(props: SelectSeriesModalContentProps) {
|
||||
const { modalTitle, onSeriesSelect, onModalClose } = props;
|
||||
|
@ -197,9 +198,9 @@ function SelectSeriesModalContent(props: SelectSeriesModalContentProps) {
|
|||
/>
|
||||
|
||||
<Scroller
|
||||
ref={scrollerRef}
|
||||
className={styles.scroller}
|
||||
autoFocus={false}
|
||||
ref={scrollerRef}
|
||||
>
|
||||
<SelectSeriesModalTableHeader columns={columns} />
|
||||
<List<RowItemData>
|
||||
|
|
|
@ -17,7 +17,7 @@ function SelectDownloadClientModal(props: SelectDownloadClientModalProps) {
|
|||
props;
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onModalClose={onModalClose} size={sizes.MEDIUM}>
|
||||
<Modal isOpen={isOpen} size={sizes.MEDIUM} onModalClose={onModalClose}>
|
||||
<SelectDownloadClientModalContent
|
||||
protocol={protocol}
|
||||
modalTitle={modalTitle}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { Fragment, useCallback, useState } from 'react';
|
||||
import React, { 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ interface RootFolderRowProps {
|
|||
}
|
||||
|
||||
function RootFolderRow(props: RootFolderRowProps) {
|
||||
const { id, path, accessible, freeSpace, unmappedFolders = [] } = props;
|
||||
const { id, path, accessible, freeSpace = 0, unmappedFolders = [] } = props;
|
||||
|
||||
const isUnavailable = !accessible;
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import DescriptionList from 'Components/DescriptionList/DescriptionList';
|
||||
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
|
||||
|
@ -6,14 +5,19 @@ import formatBytes from 'Utilities/Number/formatBytes';
|
|||
import translate from 'Utilities/String/translate';
|
||||
import styles from './SeasonInfo.css';
|
||||
|
||||
function SeasonInfo(props) {
|
||||
const {
|
||||
totalEpisodeCount,
|
||||
monitoredEpisodeCount,
|
||||
episodeFileCount,
|
||||
sizeOnDisk
|
||||
} = props;
|
||||
interface SeasonInfoProps {
|
||||
totalEpisodeCount: number;
|
||||
monitoredEpisodeCount: number;
|
||||
episodeFileCount: number;
|
||||
sizeOnDisk: number;
|
||||
}
|
||||
|
||||
function SeasonInfo({
|
||||
totalEpisodeCount,
|
||||
monitoredEpisodeCount,
|
||||
episodeFileCount,
|
||||
sizeOnDisk,
|
||||
}: SeasonInfoProps) {
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
|
@ -47,11 +51,4 @@ function SeasonInfo(props) {
|
|||
);
|
||||
}
|
||||
|
||||
SeasonInfo.propTypes = {
|
||||
totalEpisodeCount: PropTypes.number.isRequired,
|
||||
monitoredEpisodeCount: PropTypes.number.isRequired,
|
||||
episodeFileCount: PropTypes.number.isRequired,
|
||||
sizeOnDisk: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
export default SeasonInfo;
|
|
@ -212,8 +212,8 @@ class SeriesDetails extends Component {
|
|||
} = this.props;
|
||||
|
||||
const {
|
||||
episodeFileCount,
|
||||
sizeOnDisk
|
||||
episodeFileCount = 0,
|
||||
sizeOnDisk = 0
|
||||
} = statistics;
|
||||
|
||||
const {
|
||||
|
@ -454,10 +454,9 @@ class SeriesDetails extends Component {
|
|||
name={icons.DRIVE}
|
||||
size={17}
|
||||
/>
|
||||
|
||||
<span className={styles.sizeOnDisk}>
|
||||
{
|
||||
formatBytes(sizeOnDisk || 0)
|
||||
}
|
||||
{formatBytes(sizeOnDisk)}
|
||||
</span>
|
||||
</div>
|
||||
</Label>
|
||||
|
|
|
@ -194,10 +194,12 @@ function getInfoRowProps(
|
|||
}
|
||||
|
||||
if (name === 'sizeOnDisk') {
|
||||
const { sizeOnDisk = 0 } = props;
|
||||
|
||||
return {
|
||||
title: translate('SizeOnDisk'),
|
||||
iconName: icons.DRIVE,
|
||||
label: formatBytes(props.sizeOnDisk),
|
||||
label: formatBytes(sizeOnDisk),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -42,11 +42,7 @@ interface SeriesIndexOverviewsProps {
|
|||
isSmallScreen: boolean;
|
||||
}
|
||||
|
||||
const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
|
||||
index,
|
||||
style,
|
||||
data,
|
||||
}) => {
|
||||
function Row({ index, style, data }: ListChildComponentProps<RowItemData>) {
|
||||
const { items, ...otherData } = data;
|
||||
|
||||
if (index >= items.length) {
|
||||
|
@ -60,7 +56,7 @@ const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
|
|||
<SeriesIndexOverview seriesId={series.id} {...otherData} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function getWindowScrollTopPosition() {
|
||||
return document.documentElement.scrollTop || document.body.scrollTop || 0;
|
||||
|
|
|
@ -37,7 +37,7 @@ function SeriesIndexPosterInfo(props: SeriesIndexPosterInfoProps) {
|
|||
added,
|
||||
seasonCount,
|
||||
path,
|
||||
sizeOnDisk,
|
||||
sizeOnDisk = 0,
|
||||
tags,
|
||||
sortKey,
|
||||
showRelativeDates,
|
||||
|
|
|
@ -60,12 +60,12 @@ const seriesIndexSelector = createSelector(
|
|||
}
|
||||
);
|
||||
|
||||
const Cell: React.FC<GridChildComponentProps<CellItemData>> = ({
|
||||
function Cell({
|
||||
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 @@ const Cell: React.FC<GridChildComponentProps<CellItemData>> = ({
|
|||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function getWindowScrollTopPosition() {
|
||||
return document.documentElement.scrollTop || document.body.scrollTop || 0;
|
||||
|
|
|
@ -45,11 +45,7 @@ const columnsSelector = createSelector(
|
|||
(columns) => columns
|
||||
);
|
||||
|
||||
const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
|
||||
index,
|
||||
style,
|
||||
data,
|
||||
}) => {
|
||||
function Row({ index, style, data }: ListChildComponentProps<RowItemData>) {
|
||||
const { items, sortKey, columns, isSelectMode } = data;
|
||||
|
||||
if (index >= items.length) {
|
||||
|
@ -75,7 +71,7 @@ const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
|
|||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function getWindowScrollTopPosition() {
|
||||
return document.documentElement.scrollTop || document.body.scrollTop || 0;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { Fragment, useCallback } from 'react';
|
||||
import React, { 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import React 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>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -32,7 +32,7 @@ function EditImportListExclusionModal(
|
|||
<Modal size={sizes.MEDIUM} isOpen={isOpen} onModalClose={onModalClosePress}>
|
||||
<EditImportListExclusionModalContent
|
||||
{...otherProps}
|
||||
onModalClose={onModalClose}
|
||||
onModalClose={onModalClosePress}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
@ -261,9 +261,9 @@ function ManageImportListsModalContent(
|
|||
|
||||
<ManageImportListsEditModal
|
||||
isOpen={isEditModalOpen}
|
||||
importListIds={selectedIds}
|
||||
onModalClose={onEditModalClose}
|
||||
onSavePress={onSavePress}
|
||||
importListIds={selectedIds}
|
||||
/>
|
||||
|
||||
<TagsModal
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -7,7 +7,7 @@ function createRemoveItemHandler(section, url) {
|
|||
return function(getState, payload, dispatch) {
|
||||
const {
|
||||
id,
|
||||
...queryParams
|
||||
queryParams
|
||||
} = payload;
|
||||
|
||||
dispatch(set({ section, isDeleting: true }));
|
||||
|
|
|
@ -251,6 +251,11 @@ export const filterBuilderProps = [
|
|||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.SERIES_TYPES
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
label: () => translate('Title'),
|
||||
type: filterBuilderTypes.STRING
|
||||
},
|
||||
{
|
||||
name: 'network',
|
||||
label: () => translate('Network'),
|
||||
|
|
|
@ -66,7 +66,7 @@ function About() {
|
|||
) : null}
|
||||
|
||||
{isDocker ? (
|
||||
<DescriptionListItem title={translate('Docker')} data={'Yes'} />
|
||||
<DescriptionListItem title={translate('Docker')} data="Yes" />
|
||||
) : null}
|
||||
|
||||
<DescriptionListItem
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
import React, {
|
||||
Fragment,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
|
@ -158,7 +152,7 @@ function Updates() {
|
|||
{translate('InstallLatest')}
|
||||
</SpinnerButton>
|
||||
) : (
|
||||
<Fragment>
|
||||
<>
|
||||
<Icon name={icons.WARNING} kind={kinds.WARNING} size={30} />
|
||||
|
||||
<div className={styles.message}>
|
||||
|
@ -171,7 +165,7 @@ function Updates() {
|
|||
}
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isFetching ? (
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
import { filesize } from 'filesize';
|
||||
|
||||
function formatBytes(input?: string | number) {
|
||||
if (!input) {
|
||||
return '';
|
||||
}
|
||||
|
||||
function formatBytes(input: string | number) {
|
||||
const size = Number(input);
|
||||
|
||||
if (isNaN(size)) {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
using FluentAssertions;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Common.Test.ExtensionTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class Int64ExtensionFixture
|
||||
public class NumberExtensionFixture
|
||||
{
|
||||
[TestCase(0, "0 B")]
|
||||
[TestCase(1000, "1,000.0 B")]
|
|
@ -380,8 +380,17 @@ namespace NzbDrone.Common.Test
|
|||
[TestCase(@" C:\Test\TV\")]
|
||||
[TestCase(@" C:\Test\TV")]
|
||||
|
||||
public void IsPathValid_should_be_false(string path)
|
||||
public void IsPathValid_should_be_false_on_windows(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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace NzbDrone.Common.Extensions
|
||||
{
|
||||
public static class Int64Extensions
|
||||
public static class NumberExtensions
|
||||
{
|
||||
private static readonly string[] SizeSuffixes = { "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
|
||||
|
||||
|
@ -26,5 +26,25 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -152,16 +152,20 @@ namespace NzbDrone.Common.Extensions
|
|||
return false;
|
||||
}
|
||||
|
||||
var directoryInfo = new DirectoryInfo(path);
|
||||
|
||||
while (directoryInfo != null)
|
||||
// Only check for leading or trailing spaces for path when running on Windows.
|
||||
if (OsInfo.IsWindows)
|
||||
{
|
||||
if (directoryInfo.Name.Trim() != directoryInfo.Name)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var directoryInfo = new DirectoryInfo(path);
|
||||
|
||||
directoryInfo = directoryInfo.Parent;
|
||||
while (directoryInfo != null)
|
||||
{
|
||||
if (directoryInfo.Name.Trim() != directoryInfo.Name)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
directoryInfo = directoryInfo.Parent;
|
||||
}
|
||||
}
|
||||
|
||||
if (validationType == PathValidationType.AnyOs)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NLog.Fluent;
|
||||
|
||||
namespace NzbDrone.Common.Instrumentation.Extensions
|
||||
{
|
||||
|
@ -8,47 +8,46 @@ namespace NzbDrone.Common.Instrumentation.Extensions
|
|||
{
|
||||
public static readonly Logger SentryLogger = LogManager.GetLogger("Sentry");
|
||||
|
||||
public static LogBuilder SentryFingerprint(this LogBuilder logBuilder, params string[] fingerprint)
|
||||
public static LogEventBuilder SentryFingerprint(this LogEventBuilder logBuilder, params string[] fingerprint)
|
||||
{
|
||||
return logBuilder.Property("Sentry", fingerprint);
|
||||
}
|
||||
|
||||
public static LogBuilder WriteSentryDebug(this LogBuilder logBuilder, params string[] fingerprint)
|
||||
public static LogEventBuilder WriteSentryDebug(this LogEventBuilder logBuilder, params string[] fingerprint)
|
||||
{
|
||||
return LogSentryMessage(logBuilder, LogLevel.Debug, fingerprint);
|
||||
}
|
||||
|
||||
public static LogBuilder WriteSentryInfo(this LogBuilder logBuilder, params string[] fingerprint)
|
||||
public static LogEventBuilder WriteSentryInfo(this LogEventBuilder logBuilder, params string[] fingerprint)
|
||||
{
|
||||
return LogSentryMessage(logBuilder, LogLevel.Info, fingerprint);
|
||||
}
|
||||
|
||||
public static LogBuilder WriteSentryWarn(this LogBuilder logBuilder, params string[] fingerprint)
|
||||
public static LogEventBuilder WriteSentryWarn(this LogEventBuilder logBuilder, params string[] fingerprint)
|
||||
{
|
||||
return LogSentryMessage(logBuilder, LogLevel.Warn, fingerprint);
|
||||
}
|
||||
|
||||
public static LogBuilder WriteSentryError(this LogBuilder logBuilder, params string[] fingerprint)
|
||||
public static LogEventBuilder WriteSentryError(this LogEventBuilder logBuilder, params string[] fingerprint)
|
||||
{
|
||||
return LogSentryMessage(logBuilder, LogLevel.Error, fingerprint);
|
||||
}
|
||||
|
||||
private static LogBuilder LogSentryMessage(LogBuilder logBuilder, LogLevel level, string[] fingerprint)
|
||||
private static LogEventBuilder LogSentryMessage(LogEventBuilder logBuilder, LogLevel level, string[] fingerprint)
|
||||
{
|
||||
SentryLogger.Log(level)
|
||||
.CopyLogEvent(logBuilder.LogEventInfo)
|
||||
SentryLogger.ForLogEvent(level)
|
||||
.CopyLogEvent(logBuilder.LogEvent)
|
||||
.SentryFingerprint(fingerprint)
|
||||
.Write();
|
||||
.Log();
|
||||
|
||||
return logBuilder.Property("Sentry", null);
|
||||
return logBuilder.Property<string>("Sentry", null);
|
||||
}
|
||||
|
||||
private static LogBuilder CopyLogEvent(this LogBuilder logBuilder, LogEventInfo logEvent)
|
||||
private static LogEventBuilder CopyLogEvent(this LogEventBuilder logBuilder, LogEventInfo logEvent)
|
||||
{
|
||||
return logBuilder.LoggerName(logEvent.LoggerName)
|
||||
.TimeStamp(logEvent.TimeStamp)
|
||||
return logBuilder.TimeStamp(logEvent.TimeStamp)
|
||||
.Message(logEvent.Message, logEvent.Parameters)
|
||||
.Properties(logEvent.Properties.ToDictionary(v => v.Key, v => v.Value))
|
||||
.Properties(logEvent.Properties.Select(p => new KeyValuePair<string, object>(p.Key.ToString(), p.Value)))
|
||||
.Exception(logEvent.Exception);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
using NLog;
|
||||
using System.Text;
|
||||
using NLog;
|
||||
using NLog.Targets;
|
||||
|
||||
namespace NzbDrone.Common.Instrumentation
|
||||
{
|
||||
public class NzbDroneFileTarget : FileTarget
|
||||
{
|
||||
protected override string GetFormattedMessage(LogEventInfo logEvent)
|
||||
protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
|
||||
{
|
||||
return CleanseLogMessage.Cleanse(Layout.Render(logEvent));
|
||||
var result = CleanseLogMessage.Cleanse(Layout.Render(logEvent));
|
||||
target.Append(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Diagnostics;
|
|||
using System.IO;
|
||||
using NLog;
|
||||
using NLog.Config;
|
||||
using NLog.Layouts.ClefJsonLayout;
|
||||
using NLog.Targets;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
@ -13,6 +14,8 @@ 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;
|
||||
|
||||
|
@ -34,6 +37,8 @@ namespace NzbDrone.Common.Instrumentation
|
|||
|
||||
var appFolderInfo = new AppFolderInfo(startupContext);
|
||||
|
||||
RegisterGlobalFilters();
|
||||
|
||||
if (Debugger.IsAttached)
|
||||
{
|
||||
RegisterDebugger();
|
||||
|
@ -122,7 +127,16 @@ namespace NzbDrone.Common.Instrumentation
|
|||
var coloredConsoleTarget = new ColoredConsoleTarget();
|
||||
|
||||
coloredConsoleTarget.Name = "consoleLogger";
|
||||
coloredConsoleTarget.Layout = "[${level}] ${logger}: ${message} ${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}}";
|
||||
|
||||
var logFormat = Enum.TryParse<ConsoleLogFormat>(Environment.GetEnvironmentVariable("SONARR__LOG__CONSOLEFORMAT"), out var formatEnumValue)
|
||||
? formatEnumValue
|
||||
: ConsoleLogFormat.Standard;
|
||||
|
||||
coloredConsoleTarget.Layout = logFormat switch
|
||||
{
|
||||
ConsoleLogFormat.Clef => ClefLogLayout,
|
||||
_ => ConsoleLogLayout
|
||||
};
|
||||
|
||||
var loggingRule = new LoggingRule("*", level, coloredConsoleTarget);
|
||||
|
||||
|
@ -148,7 +162,7 @@ namespace NzbDrone.Common.Instrumentation
|
|||
fileTarget.ConcurrentWrites = false;
|
||||
fileTarget.ConcurrentWriteAttemptDelay = 50;
|
||||
fileTarget.ConcurrentWriteAttempts = 10;
|
||||
fileTarget.ArchiveAboveSize = 1024000;
|
||||
fileTarget.ArchiveAboveSize = 1.Megabytes();
|
||||
fileTarget.MaxArchiveFiles = maxArchiveFiles;
|
||||
fileTarget.EnableFileDelete = true;
|
||||
fileTarget.ArchiveNumbering = ArchiveNumberingMode.Rolling;
|
||||
|
@ -196,6 +210,17 @@ 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.", ""));
|
||||
|
@ -206,4 +231,10 @@ namespace NzbDrone.Common.Instrumentation
|
|||
return GetLogger(obj.GetType());
|
||||
}
|
||||
}
|
||||
|
||||
public enum ConsoleLogFormat
|
||||
{
|
||||
Standard,
|
||||
Clef
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,10 @@ 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; }
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
<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="4.7.14" />
|
||||
<PackageReference Include="NLog.Targets.Syslog" Version="6.0.3" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.4" />
|
||||
<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="Sentry" Version="4.0.2" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageReference Include="System.Text.Json" Version="6.0.9" />
|
||||
|
|
|
@ -2,6 +2,7 @@ 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;
|
||||
|
|
|
@ -3,6 +3,7 @@ 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;
|
||||
|
|
|
@ -2,6 +2,7 @@ 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;
|
||||
|
|
|
@ -3,6 +3,7 @@ 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;
|
||||
|
|
|
@ -4,6 +4,7 @@ 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;
|
||||
|
|
|
@ -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<SearchCriteriaBase>()))
|
||||
.Setup(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), 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<SearchCriteriaBase>()), Times.Never());
|
||||
Mocker.GetMock<IParsingService>().Verify(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), 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<SearchCriteriaBase>()), Times.Never());
|
||||
Mocker.GetMock<IParsingService>().Verify(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), 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<SearchCriteriaBase>()), Times.Never());
|
||||
Mocker.GetMock<IParsingService>().Verify(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), 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<SearchCriteriaBase>()))
|
||||
Mocker.GetMock<IParsingService>().Setup(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), 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<SearchCriteriaBase>()), Times.Exactly(_reports.Count));
|
||||
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));
|
||||
|
||||
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<SearchCriteriaBase>()))
|
||||
.Returns<ParsedEpisodeInfo, int, int, SearchCriteriaBase>((p, tvdbid, tvrageid, c) =>
|
||||
.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, _, _, _, _) =>
|
||||
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<SearchCriteriaBase>()))
|
||||
Mocker.GetMock<IParsingService>().Setup(c => c.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), It.IsAny<SearchCriteriaBase>()))
|
||||
.Throws<TestException>();
|
||||
|
||||
_reports = new List<ReleaseInfo>
|
||||
|
|
|
@ -4,6 +4,8 @@ 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;
|
||||
|
@ -62,6 +64,63 @@ 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()
|
||||
{
|
||||
|
|
|
@ -10,7 +10,6 @@ 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;
|
||||
|
@ -35,7 +34,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>(), (SearchCriteriaBase)null))
|
||||
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), null))
|
||||
.Returns(() => CreateRemoteEpisode());
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
|
|
|
@ -49,10 +49,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void magnet_download_should_not_return_the_item()
|
||||
public void magnet_download_should_be_returned_as_queued()
|
||||
{
|
||||
PrepareClientToReturnMagnetItem();
|
||||
Subject.GetItems().Count().Should().Be(0);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
|
||||
item.Status.Should().Be(DownloadItemStatus.Queued);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
|
@ -60,7 +60,10 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
|
|||
public void magnet_download_should_not_return_the_item()
|
||||
{
|
||||
PrepareClientToReturnMagnetItem();
|
||||
Subject.GetItems().Count().Should().Be(0);
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
|
||||
item.Status.Should().Be(DownloadItemStatus.Queued);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
|
|
@ -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>(), null))
|
||||
.Setup(s => s.ParseSpecialEpisodeTitle(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), 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>(), null))
|
||||
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), 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>(), null))
|
||||
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), 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>(), null))
|
||||
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), 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>(), null))
|
||||
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), 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>(), null))
|
||||
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), 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>(), null))
|
||||
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>(), null))
|
||||
.Returns(default(RemoteEpisode));
|
||||
|
||||
Subject.Handle(new SeriesDeletedEvent(new List<Series> { remoteEpisode.Series }, true, true));
|
||||
|
|
|
@ -15,6 +15,10 @@ 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 White‘s Off Their Rockers", "Betty+Whites+Off+Their+Rockers")]
|
||||
[TestCase("Betty White’s 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 };
|
||||
|
|
|
@ -7,6 +7,7 @@ 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;
|
||||
|
|
|
@ -6,6 +6,7 @@ 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;
|
||||
|
@ -74,8 +75,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()
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
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;
|
||||
|
|
|
@ -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))
|
||||
.Setup(v => v.ParseSpecialEpisodeTitle(fileInfo, It.IsAny<string>(), 0, 0, null, null))
|
||||
.Returns(actualInfo);
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(v => v.ParseSpecialEpisodeTitle(folderInfo, It.IsAny<string>(), 0, 0, null))
|
||||
.Setup(v => v.ParseSpecialEpisodeTitle(folderInfo, It.IsAny<string>(), 0, 0, null, null))
|
||||
.Returns(actualInfo);
|
||||
|
||||
Subject.IsSatisfiedBy(localEpisode, null).Accepted.Should().BeTrue();
|
||||
|
|
|
@ -91,7 +91,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
|||
GivenDailySeries();
|
||||
GivenDailyParseResult();
|
||||
|
||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId);
|
||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId);
|
||||
|
||||
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, _singleEpisodeSearchCriteria);
|
||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId, _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, _singleEpisodeSearchCriteria);
|
||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId, _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);
|
||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId);
|
||||
|
||||
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, _singleEpisodeSearchCriteria);
|
||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId, _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);
|
||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId);
|
||||
|
||||
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, _singleEpisodeSearchCriteria);
|
||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId, _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, _singleEpisodeSearchCriteria);
|
||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId, _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);
|
||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId);
|
||||
|
||||
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, _singleEpisodeSearchCriteria);
|
||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId, _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, _singleEpisodeSearchCriteria);
|
||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId, _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);
|
||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId);
|
||||
|
||||
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);
|
||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId);
|
||||
|
||||
Mocker.GetMock<IEpisodeService>()
|
||||
.Verify(v => v.FindEpisode(_series.TvdbId, _parsedEpisodeInfo.SeasonNumber, _parsedEpisodeInfo.EpisodeNumbers.First()), Times.Once());
|
||||
|
|
|
@ -86,7 +86,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
|||
{
|
||||
GivenMatchBySeriesTitle();
|
||||
|
||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId);
|
||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId);
|
||||
|
||||
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);
|
||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId);
|
||||
|
||||
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);
|
||||
Subject.Map(_parsedEpisodeInfo, 0, _series.TvRageId, null);
|
||||
|
||||
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);
|
||||
var result = Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId);
|
||||
|
||||
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, _singleEpisodeSearchCriteria);
|
||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId, _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, _singleEpisodeSearchCriteria);
|
||||
Subject.Map(_parsedEpisodeInfo, 10, 10, null, _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, _singleEpisodeSearchCriteria);
|
||||
Subject.Map(_parsedEpisodeInfo, 10, 10, null, _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, _singleEpisodeSearchCriteria);
|
||||
Subject.Map(_parsedEpisodeInfo, 10, 10, null, _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, _singleEpisodeSearchCriteria);
|
||||
Subject.Map(_parsedEpisodeInfo, 0, 10, null, _singleEpisodeSearchCriteria);
|
||||
|
||||
Mocker.GetMock<ISeriesService>()
|
||||
.Verify(v => v.FindByTvRageId(It.IsAny<int>()), Times.Once());
|
||||
|
@ -202,12 +202,34 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
|||
{
|
||||
GivenParseResultSeriesDoesntMatchSearchCriteria();
|
||||
|
||||
Subject.Map(_parsedEpisodeInfo, 10, 10, _singleEpisodeSearchCriteria);
|
||||
Subject.Map(_parsedEpisodeInfo, 10, 10, null, _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()
|
||||
{
|
||||
|
@ -215,7 +237,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, _singleEpisodeSearchCriteria);
|
||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId, _singleEpisodeSearchCriteria);
|
||||
|
||||
Mocker.GetMock<ISeriesService>()
|
||||
.Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Never());
|
||||
|
@ -226,7 +248,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
|||
{
|
||||
GivenParseResultSeriesDoesntMatchSearchCriteria();
|
||||
|
||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _singleEpisodeSearchCriteria);
|
||||
Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _series.ImdbId, _singleEpisodeSearchCriteria);
|
||||
|
||||
Mocker.GetMock<ISeriesService>()
|
||||
.Verify(v => v.FindByTitle(It.IsAny<string>()), Times.Never());
|
||||
|
|
|
@ -185,8 +185,10 @@ namespace NzbDrone.Core.Blocklisting
|
|||
Indexer = message.Data.GetValueOrDefault("indexer"),
|
||||
Protocol = (DownloadProtocol)Convert.ToInt32(message.Data.GetValueOrDefault("protocol")),
|
||||
Message = message.Message,
|
||||
TorrentInfoHash = message.Data.GetValueOrDefault("torrentInfoHash"),
|
||||
Languages = message.Languages
|
||||
Languages = message.Languages,
|
||||
TorrentInfoHash = message.TrackedDownload?.Protocol == DownloadProtocol.Torrent
|
||||
? message.TrackedDownload.DownloadItem.DownloadId
|
||||
: message.Data.GetValueOrDefault("torrentInfoHash", null)
|
||||
};
|
||||
|
||||
if (Enum.TryParse(message.Data.GetValueOrDefault("indexerFlags"), true, out IndexerFlags flags))
|
||||
|
|
|
@ -10,6 +10,7 @@ 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;
|
||||
|
@ -38,8 +39,10 @@ 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; }
|
||||
|
@ -220,9 +223,14 @@ namespace NzbDrone.Core.Configuration
|
|||
|
||||
public string Branch => _updateOptions.Branch ?? GetValue("Branch", "main").ToLowerInvariant();
|
||||
|
||||
public string LogLevel => _logOptions.Level ?? GetValue("LogLevel", "info").ToLowerInvariant();
|
||||
public string LogLevel => _logOptions.Level ?? GetValue("LogLevel", "debug").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);
|
||||
|
@ -234,6 +242,7 @@ 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", "");
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using FluentValidation;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
|
|
|
@ -63,7 +63,9 @@ 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().ToList();
|
||||
.Select(n => n.SearchTerm)
|
||||
.Distinct(StringComparer.InvariantCultureIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
return names;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
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;
|
||||
|
|
|
@ -80,7 +80,7 @@ namespace NzbDrone.Core.DecisionEngine
|
|||
|
||||
if (parsedEpisodeInfo == null || parsedEpisodeInfo.IsPossibleSpecialEpisode)
|
||||
{
|
||||
var specialEpisodeInfo = _parsingService.ParseSpecialEpisodeTitle(parsedEpisodeInfo, report.Title, report.TvdbId, report.TvRageId, searchCriteria);
|
||||
var specialEpisodeInfo = _parsingService.ParseSpecialEpisodeTitle(parsedEpisodeInfo, report.Title, report.TvdbId, report.TvRageId, report.ImdbId, 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, searchCriteria);
|
||||
var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, report.TvdbId, report.TvRageId, report.ImdbId, searchCriteria);
|
||||
remoteEpisode.Release = report;
|
||||
|
||||
if (remoteEpisode.Series == null)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using NLog;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ 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;
|
||||
|
@ -10,10 +12,13 @@ namespace NzbDrone.Core.Download.Aggregation.Aggregators
|
|||
{
|
||||
public class AggregateLanguages : IAggregateRemoteEpisode
|
||||
{
|
||||
private readonly IIndexerFactory _indexerFactory;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public AggregateLanguages(Logger logger)
|
||||
public AggregateLanguages(IIndexerFactory indexerFactory,
|
||||
Logger logger)
|
||||
{
|
||||
_indexerFactory = indexerFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
@ -71,6 +76,17 @@ 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))
|
||||
{
|
||||
|
|
|
@ -129,10 +129,8 @@ namespace NzbDrone.Core.Download.Clients.Aria2
|
|||
|
||||
var outputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(GetOutputPath(torrent)));
|
||||
|
||||
yield return new DownloadClientItem
|
||||
var queueItem = new DownloadClientItem
|
||||
{
|
||||
CanMoveFiles = false,
|
||||
CanBeRemoved = torrent.Status == "complete",
|
||||
Category = null,
|
||||
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this, false),
|
||||
DownloadId = torrent.InfoHash?.ToUpper(),
|
||||
|
@ -146,7 +144,12 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
|
|||
{
|
||||
foreach (var item in _scanWatchFolder.GetItems(Settings.WatchFolder, ScanGracePeriod))
|
||||
{
|
||||
yield return new DownloadClientItem
|
||||
var queueItem = new DownloadClientItem
|
||||
{
|
||||
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this, false),
|
||||
DownloadId = Definition.Name + "_" + item.DownloadId,
|
||||
|
@ -101,11 +101,14 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
|
|||
|
||||
OutputPath = item.OutputPath,
|
||||
|
||||
Status = item.Status,
|
||||
|
||||
CanMoveFiles = !Settings.ReadOnly,
|
||||
CanBeRemoved = !Settings.ReadOnly
|
||||
Status = item.Status
|
||||
};
|
||||
|
||||
queueItem.CanMoveFiles = queueItem.CanBeRemoved =
|
||||
queueItem.DownloadClientInfo.RemoveCompletedDownloads &&
|
||||
!Settings.ReadOnly;
|
||||
|
||||
yield return queueItem;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -190,6 +190,7 @@ 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 &&
|
||||
|
@ -200,7 +201,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
|||
|
||||
if (ignoredCount > 0)
|
||||
{
|
||||
_logger.Warn("{0} torrent(s) were ignored becuase they did not have a title, check Deluge and remove any invalid torrents");
|
||||
_logger.Warn("{0} torrent(s) were ignored because they did not have a title. Check Deluge and remove any invalid torrents");
|
||||
}
|
||||
|
||||
return items;
|
||||
|
|
|
@ -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),
|
||||
CanMoveFiles = IsFinished(torrent),
|
||||
CanBeRemoved = IsFinished(torrent)
|
||||
Message = GetMessage(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);
|
||||
|
|
|
@ -153,7 +153,7 @@ namespace NzbDrone.Core.Download.Clients.Flood
|
|||
item.Status = DownloadItemStatus.Downloading;
|
||||
}
|
||||
|
||||
if (item.Status == DownloadItemStatus.Completed)
|
||||
if (item.DownloadClientInfo.RemoveCompletedDownloads && 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 != null && properties.DateFinished > 0)
|
||||
else if (properties.DateFinished is > 0)
|
||||
{
|
||||
// Check if seed time reached
|
||||
if ((DateTimeOffset.Now - DateTimeOffset.FromUnixTimeSeconds((long)properties.DateFinished)) >= seedConfig.SeedTime)
|
||||
|
|
|
@ -119,7 +119,7 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
|||
break;
|
||||
}
|
||||
|
||||
item.CanBeRemoved = item.CanMoveFiles = torrent.Status == FreeboxDownloadTaskStatus.Done;
|
||||
item.CanBeRemoved = item.CanMoveFiles = item.DownloadClientInfo.RemoveCompletedDownloads && torrent.Status == FreeboxDownloadTaskStatus.Done;
|
||||
|
||||
queueItems.Add(item);
|
||||
}
|
||||
|
|
|
@ -92,7 +92,10 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
|
|||
item.Status = DownloadItemStatus.Downloading;
|
||||
}
|
||||
|
||||
item.CanMoveFiles = item.CanBeRemoved = torrent.IsFinished && torrent.State == HadoukenTorrentState.Paused;
|
||||
item.CanMoveFiles = item.CanBeRemoved =
|
||||
item.DownloadClientInfo.RemoveCompletedDownloads &&
|
||||
torrent.IsFinished &&
|
||||
torrent.State == HadoukenTorrentState.Paused;
|
||||
|
||||
items.Add(item);
|
||||
}
|
||||
|
|
|
@ -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,7 +239,10 @@ 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 = torrent.State is "pausedUP" or "stoppedUP" && HasReachedSeedLimit(torrent, config);
|
||||
item.CanMoveFiles = item.CanBeRemoved =
|
||||
item.DownloadClientInfo.RemoveCompletedDownloads &&
|
||||
torrent.State is "pausedUP" or "stoppedUP" &&
|
||||
HasReachedSeedLimit(torrent, config);
|
||||
|
||||
switch (torrent.State)
|
||||
{
|
||||
|
|
|
@ -43,12 +43,6 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
|||
|
||||
foreach (var torrent in torrents)
|
||||
{
|
||||
// If totalsize == 0 the torrent is a magnet downloading metadata
|
||||
if (torrent.TotalSize == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var outputPath = new OsPath(torrent.DownloadDir);
|
||||
|
||||
if (Settings.TvDirectory.IsNotNullOrWhiteSpace())
|
||||
|
@ -99,6 +93,10 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
|||
item.Status = DownloadItemStatus.Warning;
|
||||
item.Message = torrent.ErrorString;
|
||||
}
|
||||
else if (torrent.TotalSize == 0)
|
||||
{
|
||||
item.Status = DownloadItemStatus.Queued;
|
||||
}
|
||||
else if (torrent.LeftUntilDone == 0 && (torrent.Status == TransmissionTorrentStatus.Stopped ||
|
||||
torrent.Status == TransmissionTorrentStatus.Seeding ||
|
||||
torrent.Status == TransmissionTorrentStatus.SeedingWait))
|
||||
|
@ -119,7 +117,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
|||
item.Status = DownloadItemStatus.Downloading;
|
||||
}
|
||||
|
||||
item.CanBeRemoved = HasReachedSeedLimit(torrent, item.SeedRatio, configFunc);
|
||||
item.CanBeRemoved = item.DownloadClientInfo.RemoveCompletedDownloads && HasReachedSeedLimit(torrent, item.SeedRatio, configFunc);
|
||||
item.CanMoveFiles = item.CanBeRemoved && torrent.Status == TransmissionTorrentStatus.Stopped;
|
||||
|
||||
items.Add(item);
|
||||
|
|
|
@ -185,7 +185,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
|||
// Grab cached seedConfig
|
||||
var seedConfig = _downloadSeedConfigProvider.GetSeedConfiguration(torrent.Hash);
|
||||
|
||||
if (torrent.IsFinished && seedConfig != null)
|
||||
if (item.DownloadClientInfo.RemoveCompletedDownloads && torrent.IsFinished && seedConfig != null)
|
||||
{
|
||||
var canRemove = false;
|
||||
|
||||
|
|
|
@ -167,6 +167,7 @@ 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);
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ 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;
|
||||
|
@ -247,14 +246,14 @@ namespace NzbDrone.Core.Download
|
|||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug()
|
||||
_logger.ForDebugEvent()
|
||||
.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")
|
||||
.Write();
|
||||
.Log();
|
||||
}
|
||||
|
||||
var episodes = _episodeService.GetEpisodes(trackedDownload.RemoteEpisode.Episodes.Select(e => e.Id));
|
||||
|
|
|
@ -37,6 +37,7 @@ 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>(
|
||||
|
@ -49,6 +50,7 @@ namespace NzbDrone.Core.Download
|
|||
Type = downloadClient.Name,
|
||||
Id = downloadClient.Definition.Id,
|
||||
Name = downloadClient.Definition.Name,
|
||||
RemoveCompletedDownloads = downloadClient.Definition is DownloadClientDefinition { RemoveCompletedDownloads: true },
|
||||
HasPostImportCategory = hasPostImportCategory
|
||||
};
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace NzbDrone.Core.Download
|
|||
public interface IFailedDownloadService
|
||||
{
|
||||
void MarkAsFailed(int historyId, bool skipRedownload = false);
|
||||
void MarkAsFailed(string downloadId, bool skipRedownload = false);
|
||||
void MarkAsFailed(TrackedDownload trackedDownload, bool skipRedownload = false);
|
||||
void Check(TrackedDownload trackedDownload);
|
||||
void ProcessFailed(TrackedDownload trackedDownload);
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ namespace NzbDrone.Core.Download
|
|||
public class FailedDownloadService : IFailedDownloadService
|
||||
{
|
||||
private readonly IHistoryService _historyService;
|
||||
private readonly ITrackedDownloadService _trackedDownloadService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
|
||||
public FailedDownloadService(IHistoryService historyService,
|
||||
|
@ -28,7 +27,6 @@ namespace NzbDrone.Core.Download
|
|||
IEventAggregator eventAggregator)
|
||||
{
|
||||
_historyService = historyService;
|
||||
_trackedDownloadService = trackedDownloadService;
|
||||
_eventAggregator = eventAggregator;
|
||||
}
|
||||
|
||||
|
@ -37,9 +35,10 @@ namespace NzbDrone.Core.Download
|
|||
var history = _historyService.Get(historyId);
|
||||
|
||||
var downloadId = history.DownloadId;
|
||||
|
||||
if (downloadId.IsNullOrWhiteSpace())
|
||||
{
|
||||
PublishDownloadFailedEvent(new List<EpisodeHistory> { history }, "Manually marked as failed", skipRedownload: skipRedownload);
|
||||
PublishDownloadFailedEvent(history, new List<int> { history.EpisodeId }, "Manually marked as failed", skipRedownload: skipRedownload);
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -53,21 +52,19 @@ namespace NzbDrone.Core.Download
|
|||
}
|
||||
|
||||
// Add any other history items for the download ID then filter out any duplicate history items.
|
||||
grabbedHistory.AddRange(_historyService.Find(downloadId, EpisodeHistoryEventType.Grabbed));
|
||||
grabbedHistory.AddRange(GetGrabbedHistory(downloadId));
|
||||
grabbedHistory = grabbedHistory.DistinctBy(h => h.Id).ToList();
|
||||
|
||||
PublishDownloadFailedEvent(grabbedHistory, "Manually marked as failed");
|
||||
PublishDownloadFailedEvent(history, GetEpisodeIds(grabbedHistory), "Manually marked as failed");
|
||||
}
|
||||
|
||||
public void MarkAsFailed(string downloadId, bool skipRedownload = false)
|
||||
public void MarkAsFailed(TrackedDownload trackedDownload, bool skipRedownload = false)
|
||||
{
|
||||
var history = _historyService.Find(downloadId, EpisodeHistoryEventType.Grabbed);
|
||||
var history = GetGrabbedHistory(trackedDownload.DownloadItem.DownloadId);
|
||||
|
||||
if (history.Any())
|
||||
{
|
||||
var trackedDownload = _trackedDownloadService.Find(downloadId);
|
||||
|
||||
PublishDownloadFailedEvent(history, "Manually marked as failed", trackedDownload, skipRedownload: skipRedownload);
|
||||
PublishDownloadFailedEvent(history.First(), GetEpisodeIds(history), "Manually marked as failed", trackedDownload, skipRedownload: skipRedownload);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,9 +79,7 @@ namespace NzbDrone.Core.Download
|
|||
if (trackedDownload.DownloadItem.IsEncrypted ||
|
||||
trackedDownload.DownloadItem.Status == DownloadItemStatus.Failed)
|
||||
{
|
||||
var grabbedItems = _historyService
|
||||
.Find(trackedDownload.DownloadItem.DownloadId, EpisodeHistoryEventType.Grabbed)
|
||||
.ToList();
|
||||
var grabbedItems = GetGrabbedHistory(trackedDownload.DownloadItem.DownloadId);
|
||||
|
||||
if (grabbedItems.Empty())
|
||||
{
|
||||
|
@ -103,9 +98,7 @@ namespace NzbDrone.Core.Download
|
|||
return;
|
||||
}
|
||||
|
||||
var grabbedItems = _historyService
|
||||
.Find(trackedDownload.DownloadItem.DownloadId, EpisodeHistoryEventType.Grabbed)
|
||||
.ToList();
|
||||
var grabbedItems = GetGrabbedHistory(trackedDownload.DownloadItem.DownloadId);
|
||||
|
||||
if (grabbedItems.Empty())
|
||||
{
|
||||
|
@ -124,18 +117,17 @@ namespace NzbDrone.Core.Download
|
|||
}
|
||||
|
||||
trackedDownload.State = TrackedDownloadState.Failed;
|
||||
PublishDownloadFailedEvent(grabbedItems, failure, trackedDownload);
|
||||
PublishDownloadFailedEvent(grabbedItems.First(), GetEpisodeIds(grabbedItems), failure, trackedDownload);
|
||||
}
|
||||
|
||||
private void PublishDownloadFailedEvent(List<EpisodeHistory> historyItems, string message, TrackedDownload trackedDownload = null, bool skipRedownload = false)
|
||||
private void PublishDownloadFailedEvent(EpisodeHistory historyItem, List<int> episodeIds, 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 = historyItems.Select(h => h.EpisodeId).Distinct().ToList(),
|
||||
EpisodeIds = episodeIds,
|
||||
Quality = historyItem.Quality,
|
||||
SourceTitle = historyItem.SourceTitle,
|
||||
DownloadClient = historyItem.Data.GetValueOrDefault(EpisodeHistory.DOWNLOAD_CLIENT),
|
||||
|
@ -145,10 +137,23 @@ 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
|||
|
||||
if (parsedEpisodeInfo != null)
|
||||
{
|
||||
trackedDownload.RemoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0, 0);
|
||||
trackedDownload.RemoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0, 0, null);
|
||||
|
||||
_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);
|
||||
_parsingService.ParseSpecialEpisodeTitle(parsedEpisodeInfo, firstHistoryItem.SourceTitle, 0, 0, null);
|
||||
|
||||
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);
|
||||
trackedDownload.RemoteEpisode = parsedEpisodeInfo == null ? null : _parsingService.Map(parsedEpisodeInfo, 0, 0, null);
|
||||
|
||||
_aggregationService.Augment(trackedDownload.RemoteEpisode);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System.Text.RegularExpressions;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
|
||||
{
|
||||
|
|
|
@ -20,26 +20,6 @@ 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);
|
||||
|
|
|
@ -165,6 +165,7 @@ 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());
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
|
|||
{
|
||||
public abstract class SearchCriteriaBase
|
||||
{
|
||||
private static readonly Regex SpecialCharacter = new Regex(@"[`'.]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
private static readonly Regex SpecialCharacter = new Regex(@"['.\u0060\u00B4\u2018\u2019]", 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);
|
||||
|
||||
|
|
|
@ -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().ToList();
|
||||
item.Value.SceneTitles = item.Value.SceneTitles.Distinct(StringComparer.InvariantCultureIgnoreCase).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().ToList();
|
||||
item.Value.SceneTitles = item.Value.SceneTitles.Distinct(StringComparer.InvariantCultureIgnoreCase).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))
|
||||
if (!spec.SceneTitles.Contains(series.Title, StringComparer.InvariantCultureIgnoreCase))
|
||||
{
|
||||
spec.SceneTitles.Add(series.Title);
|
||||
}
|
||||
|
|
|
@ -95,6 +95,11 @@ 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ namespace NzbDrone.Core.Indexers.FileList
|
|||
{
|
||||
var id = result.Id;
|
||||
|
||||
torrentInfos.Add(new TorrentInfo
|
||||
var torrentInfo = new TorrentInfo
|
||||
{
|
||||
Guid = $"FileList-{id}",
|
||||
Title = result.Name,
|
||||
|
@ -48,9 +48,15 @@ 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();
|
||||
|
|
|
@ -61,6 +61,7 @@ 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)
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
|
@ -20,8 +19,6 @@ 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;
|
||||
|
@ -94,7 +91,7 @@ namespace NzbDrone.Core.Indexers
|
|||
result.ForEach(c =>
|
||||
{
|
||||
// Use multi languages from setting if ReleaseInfo languages is empty
|
||||
if (c.Languages.Empty() && MultiRegex.IsMatch(c.Title) && settings.MultiLanguages.Any())
|
||||
if (c.Languages.Empty() && settings.MultiLanguages.Any() && Parser.Parser.HasMultipleLanguages(c.Title))
|
||||
{
|
||||
c.Languages = settings.MultiLanguages.Select(i => (Language)i).ToList();
|
||||
}
|
||||
|
|
|
@ -90,6 +90,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
|||
|
||||
releaseInfo.TvdbId = GetTvdbId(item);
|
||||
releaseInfo.TvRageId = GetTvRageId(item);
|
||||
releaseInfo.ImdbId = GetImdbId(item);
|
||||
|
||||
return releaseInfo;
|
||||
}
|
||||
|
@ -182,6 +183,18 @@ 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));
|
||||
|
|
|
@ -83,6 +83,7 @@ namespace NzbDrone.Core.Indexers.Torznab
|
|||
{
|
||||
torrentInfo.TvdbId = GetTvdbId(item);
|
||||
torrentInfo.TvRageId = GetTvRageId(item);
|
||||
releaseInfo.ImdbId = GetImdbId(item);
|
||||
torrentInfo.IndexerFlags = GetFlags(item);
|
||||
}
|
||||
|
||||
|
@ -177,6 +178,18 @@ 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");
|
||||
|
|
|
@ -33,22 +33,25 @@ namespace NzbDrone.Core.Instrumentation
|
|||
|
||||
LogManager.Configuration.AddTarget("DbLogger", target);
|
||||
LogManager.Configuration.LoggingRules.Add(Rule);
|
||||
LogManager.ConfigurationReloaded += OnLogManagerOnConfigurationReloaded;
|
||||
LogManager.ConfigurationChanged += OnLogManagerOnConfigurationReloaded;
|
||||
LogManager.ReconfigExistingLoggers();
|
||||
}
|
||||
|
||||
public void UnRegister()
|
||||
{
|
||||
LogManager.ConfigurationReloaded -= OnLogManagerOnConfigurationReloaded;
|
||||
LogManager.ConfigurationChanged -= OnLogManagerOnConfigurationReloaded;
|
||||
LogManager.Configuration.RemoveTarget("DbLogger");
|
||||
LogManager.Configuration.LoggingRules.Remove(Rule);
|
||||
LogManager.ReconfigExistingLoggers();
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void OnLogManagerOnConfigurationReloaded(object sender, LoggingConfigurationReloadedEventArgs args)
|
||||
private void OnLogManagerOnConfigurationReloaded(object sender, LoggingConfigurationChangedEventArgs args)
|
||||
{
|
||||
Register();
|
||||
if (args.ActivatedConfiguration != null)
|
||||
{
|
||||
Register();
|
||||
}
|
||||
}
|
||||
|
||||
public LoggingRule Rule { get; set; }
|
||||
|
|
|
@ -2,6 +2,7 @@ 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;
|
||||
|
@ -51,13 +52,14 @@ 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);
|
||||
SetLogRotation();
|
||||
ReconfigureFile();
|
||||
|
||||
// Log Sql
|
||||
SqlBuilderExtensions.LogSql = _configFileProvider.LogSql;
|
||||
|
@ -91,11 +93,12 @@ namespace NzbDrone.Core.Instrumentation
|
|||
}
|
||||
}
|
||||
|
||||
private void SetLogRotation()
|
||||
private void ReconfigureFile()
|
||||
{
|
||||
foreach (var target in LogManager.Configuration.AllTargets.OfType<NzbDroneFileTarget>())
|
||||
{
|
||||
target.MaxArchiveFiles = _configFileProvider.LogRotate;
|
||||
target.ArchiveAboveSize = _configFileProvider.LogSizeLimit.Megabytes();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,6 +112,22 @@ 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();
|
||||
|
@ -117,7 +136,7 @@ namespace NzbDrone.Core.Instrumentation
|
|||
syslogTarget.MessageSend.Protocol = ProtocolType.Udp;
|
||||
syslogTarget.MessageSend.Udp.Port = syslogPort;
|
||||
syslogTarget.MessageSend.Udp.Server = syslogServer;
|
||||
syslogTarget.MessageSend.Udp.ReconnectInterval = 500;
|
||||
syslogTarget.MessageSend.Retry.ConstantBackoff.BaseDelay = 500;
|
||||
syslogTarget.MessageCreation.Rfc = RfcNumber.Rfc5424;
|
||||
syslogTarget.MessageCreation.Rfc5424.AppName = _configFileProvider.InstanceName;
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
{
|
||||
"AddAutoTag": "أضف كلمات دلالية تلقائيا",
|
||||
"AddCondition": "إضافة شرط"
|
||||
"AddCondition": "إضافة شرط",
|
||||
"AutoTaggingNegateHelpText": "إذا تم تحديده ، فلن يتم تطبيق التنسيق المخصص إذا تطابق شرط {implementationName} هذا.",
|
||||
"ConnectionLostReconnect": "سيحاول {appName} الاتصال تلقائيًا ، أو يمكنك النقر فوق إعادة التحميل أدناه."
|
||||
}
|
||||
|
|
|
@ -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 MediaBrowser send notifications to configured providers",
|
||||
"NotificationsEmbySettingsUpdateLibraryHelpText": "Update Library on Import, Rename, or Delete?",
|
||||
"NotificationsEmbySettingsSendNotificationsHelpText": "Have Emby send notifications to configured providers. Not supported on Jellyfin.",
|
||||
"NotificationsEmbySettingsUpdateLibraryHelpText": "Update Library on Import, Rename, or Delete",
|
||||
"NotificationsGotifySettingIncludeSeriesPoster": "Include Series Poster",
|
||||
"NotificationsGotifySettingIncludeSeriesPosterHelpText": "Include series poster in message",
|
||||
"NotificationsGotifySettingsAppToken": "App Token",
|
||||
|
@ -1429,6 +1429,8 @@
|
|||
"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",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue