mirror of
https://github.com/Sonarr/Sonarr.git
synced 2026-03-29 18:14:18 -04:00
Compare commits
19 Commits
small-fixe
...
image-shar
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
720371c1fd | ||
|
|
6f1d461dad | ||
|
|
6ccab3cfc8 | ||
|
|
5e47cc3baa | ||
|
|
78ca30d1f8 | ||
|
|
f9d0abada3 | ||
|
|
4bdb0408f1 | ||
|
|
40ea6ce4e5 | ||
|
|
ccf33033dc | ||
|
|
996c0e9f50 | ||
|
|
8b7f9daab0 | ||
|
|
dfb6fdfbeb | ||
|
|
29d0073ee6 | ||
|
|
9cf6be32fa | ||
|
|
fee3f8150e | ||
|
|
010bbbd222 | ||
|
|
d3c3a6ebce | ||
|
|
f26344ae75 | ||
|
|
034f731308 |
@@ -1,4 +1,6 @@
|
|||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import AppState from 'App/State/AppState';
|
||||||
import FormGroup from 'Components/Form/FormGroup';
|
import FormGroup from 'Components/Form/FormGroup';
|
||||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
import FormLabel from 'Components/Form/FormLabel';
|
import FormLabel from 'Components/Form/FormLabel';
|
||||||
@@ -10,6 +12,8 @@ import ModalContent from 'Components/Modal/ModalContent';
|
|||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||||
|
import { setQueueRemovalOption } from 'Store/Actions/queueActions';
|
||||||
|
import { InputChanged } from 'typings/inputs';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import styles from './RemoveQueueItemModal.css';
|
import styles from './RemoveQueueItemModal.css';
|
||||||
|
|
||||||
@@ -31,12 +35,6 @@ interface RemoveQueueItemModalProps {
|
|||||||
onModalClose: () => void;
|
onModalClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type RemovalMethod = 'removeFromClient' | 'changeCategory' | 'ignore';
|
|
||||||
type BlocklistMethod =
|
|
||||||
| 'doNotBlocklist'
|
|
||||||
| 'blocklistAndSearch'
|
|
||||||
| 'blocklistOnly';
|
|
||||||
|
|
||||||
function RemoveQueueItemModal(props: RemoveQueueItemModalProps) {
|
function RemoveQueueItemModal(props: RemoveQueueItemModalProps) {
|
||||||
const {
|
const {
|
||||||
isOpen,
|
isOpen,
|
||||||
@@ -49,12 +47,13 @@ function RemoveQueueItemModal(props: RemoveQueueItemModalProps) {
|
|||||||
onModalClose,
|
onModalClose,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const multipleSelected = selectedCount && selectedCount > 1;
|
const multipleSelected = selectedCount && selectedCount > 1;
|
||||||
|
|
||||||
const [removalMethod, setRemovalMethod] =
|
const { removalMethod, blocklistMethod } = useSelector(
|
||||||
useState<RemovalMethod>('removeFromClient');
|
(state: AppState) => state.queue.removalOptions
|
||||||
const [blocklistMethod, setBlocklistMethod] =
|
);
|
||||||
useState<BlocklistMethod>('doNotBlocklist');
|
|
||||||
|
|
||||||
const { title, message } = useMemo(() => {
|
const { title, message } = useMemo(() => {
|
||||||
if (!selectedCount) {
|
if (!selectedCount) {
|
||||||
@@ -138,18 +137,11 @@ function RemoveQueueItemModal(props: RemoveQueueItemModalProps) {
|
|||||||
return options;
|
return options;
|
||||||
}, [isPending, multipleSelected]);
|
}, [isPending, multipleSelected]);
|
||||||
|
|
||||||
const handleRemovalMethodChange = useCallback(
|
const handleRemovalOptionInputChange = useCallback(
|
||||||
({ value }: { value: RemovalMethod }) => {
|
({ name, value }: InputChanged) => {
|
||||||
setRemovalMethod(value);
|
dispatch(setQueueRemovalOption({ [name]: value }));
|
||||||
},
|
},
|
||||||
[setRemovalMethod]
|
[dispatch]
|
||||||
);
|
|
||||||
|
|
||||||
const handleBlocklistMethodChange = useCallback(
|
|
||||||
({ value }: { value: BlocklistMethod }) => {
|
|
||||||
setBlocklistMethod(value);
|
|
||||||
},
|
|
||||||
[setBlocklistMethod]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleConfirmRemove = useCallback(() => {
|
const handleConfirmRemove = useCallback(() => {
|
||||||
@@ -159,23 +151,11 @@ function RemoveQueueItemModal(props: RemoveQueueItemModalProps) {
|
|||||||
blocklist: blocklistMethod !== 'doNotBlocklist',
|
blocklist: blocklistMethod !== 'doNotBlocklist',
|
||||||
skipRedownload: blocklistMethod === 'blocklistOnly',
|
skipRedownload: blocklistMethod === 'blocklistOnly',
|
||||||
});
|
});
|
||||||
|
}, [removalMethod, blocklistMethod, onRemovePress]);
|
||||||
setRemovalMethod('removeFromClient');
|
|
||||||
setBlocklistMethod('doNotBlocklist');
|
|
||||||
}, [
|
|
||||||
removalMethod,
|
|
||||||
blocklistMethod,
|
|
||||||
setRemovalMethod,
|
|
||||||
setBlocklistMethod,
|
|
||||||
onRemovePress,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleModalClose = useCallback(() => {
|
const handleModalClose = useCallback(() => {
|
||||||
setRemovalMethod('removeFromClient');
|
|
||||||
setBlocklistMethod('doNotBlocklist');
|
|
||||||
|
|
||||||
onModalClose();
|
onModalClose();
|
||||||
}, [setRemovalMethod, setBlocklistMethod, onModalClose]);
|
}, [onModalClose]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} size={sizes.MEDIUM} onModalClose={handleModalClose}>
|
<Modal isOpen={isOpen} size={sizes.MEDIUM} onModalClose={handleModalClose}>
|
||||||
@@ -198,7 +178,7 @@ function RemoveQueueItemModal(props: RemoveQueueItemModalProps) {
|
|||||||
helpTextWarning={translate(
|
helpTextWarning={translate(
|
||||||
'RemoveQueueItemRemovalMethodHelpTextWarning'
|
'RemoveQueueItemRemovalMethodHelpTextWarning'
|
||||||
)}
|
)}
|
||||||
onChange={handleRemovalMethodChange}
|
onChange={handleRemovalOptionInputChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
@@ -216,7 +196,7 @@ function RemoveQueueItemModal(props: RemoveQueueItemModalProps) {
|
|||||||
value={blocklistMethod}
|
value={blocklistMethod}
|
||||||
values={blocklistMethodOptions}
|
values={blocklistMethodOptions}
|
||||||
helpText={translate('BlocklistReleaseHelpText')}
|
helpText={translate('BlocklistReleaseHelpText')}
|
||||||
onChange={handleBlocklistMethodChange}
|
onChange={handleRemovalOptionInputChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|||||||
@@ -32,6 +32,17 @@ export interface QueuePagedAppState
|
|||||||
removeError: Error;
|
removeError: Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type RemovalMethod = 'removeFromClient' | 'changeCategory' | 'ignore';
|
||||||
|
export type BlocklistMethod =
|
||||||
|
| 'doNotBlocklist'
|
||||||
|
| 'blocklistAndSearch'
|
||||||
|
| 'blocklistOnly';
|
||||||
|
|
||||||
|
interface RemovalOptions {
|
||||||
|
removalMethod: RemovalMethod;
|
||||||
|
blocklistMethod: BlocklistMethod;
|
||||||
|
}
|
||||||
|
|
||||||
interface QueueAppState {
|
interface QueueAppState {
|
||||||
status: AppSectionItemState<QueueStatus>;
|
status: AppSectionItemState<QueueStatus>;
|
||||||
details: QueueDetailsAppState;
|
details: QueueDetailsAppState;
|
||||||
@@ -39,6 +50,7 @@ interface QueueAppState {
|
|||||||
options: {
|
options: {
|
||||||
includeUnknownSeriesItems: boolean;
|
includeUnknownSeriesItems: boolean;
|
||||||
};
|
};
|
||||||
|
removalOptions: RemovalOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default QueueAppState;
|
export default QueueAppState;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
.header {
|
.header {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 425px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.backdrop {
|
.backdrop {
|
||||||
@@ -30,20 +29,18 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
|
gap: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.poster {
|
.poster {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-right: 35px;
|
|
||||||
width: 250px;
|
width: 250px;
|
||||||
height: 368px;
|
height: 368px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex-grow: 1;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.titleRow {
|
.titleRow {
|
||||||
@@ -59,10 +56,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
overflow: auto;
|
||||||
|
max-height: calc(3 * 50px);
|
||||||
text-wrap: balance;
|
text-wrap: balance;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-size: 50px;
|
font-size: 50px;
|
||||||
line-height: 50px;
|
line-height: 50px;
|
||||||
|
line-clamp: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggleMonitoredContainer {
|
.toggleMonitoredContainer {
|
||||||
@@ -170,6 +170,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
overflow: hidden;
|
||||||
|
max-height: calc(3 * 30px);
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import TextTruncate from 'react-text-truncate';
|
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import AppState from 'App/State/AppState';
|
import AppState from 'App/State/AppState';
|
||||||
import * as commandNames from 'Commands/commandNames';
|
import * as commandNames from 'Commands/commandNames';
|
||||||
@@ -21,7 +20,6 @@ import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
|||||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||||
import Popover from 'Components/Tooltip/Popover';
|
import Popover from 'Components/Tooltip/Popover';
|
||||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||||
import useMeasure from 'Helpers/Hooks/useMeasure';
|
|
||||||
import usePrevious from 'Helpers/Hooks/usePrevious';
|
import usePrevious from 'Helpers/Hooks/usePrevious';
|
||||||
import {
|
import {
|
||||||
align,
|
align,
|
||||||
@@ -56,7 +54,6 @@ import {
|
|||||||
import { toggleSeriesMonitored } from 'Store/Actions/seriesActions';
|
import { toggleSeriesMonitored } from 'Store/Actions/seriesActions';
|
||||||
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector';
|
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector';
|
||||||
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
||||||
import fonts from 'Styles/Variables/fonts';
|
|
||||||
import sortByProp from 'Utilities/Array/sortByProp';
|
import sortByProp from 'Utilities/Array/sortByProp';
|
||||||
import { findCommand, isCommandExecuting } from 'Utilities/Command';
|
import { findCommand, isCommandExecuting } from 'Utilities/Command';
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
@@ -75,9 +72,6 @@ import SeriesProgressLabel from './SeriesProgressLabel';
|
|||||||
import SeriesTags from './SeriesTags';
|
import SeriesTags from './SeriesTags';
|
||||||
import styles from './SeriesDetails.css';
|
import styles from './SeriesDetails.css';
|
||||||
|
|
||||||
const defaultFontSize = parseInt(fonts.defaultFontSize);
|
|
||||||
const lineHeight = parseFloat(fonts.lineHeight);
|
|
||||||
|
|
||||||
function getFanartUrl(images: Image[]) {
|
function getFanartUrl(images: Image[]) {
|
||||||
return images.find((image) => image.coverType === 'fanart')?.url;
|
return images.find((image) => image.coverType === 'fanart')?.url;
|
||||||
}
|
}
|
||||||
@@ -246,7 +240,6 @@ function SeriesDetails({ seriesId }: SeriesDetailsProps) {
|
|||||||
allCollapsed: false,
|
allCollapsed: false,
|
||||||
seasons: {},
|
seasons: {},
|
||||||
});
|
});
|
||||||
const [overviewRef, { height: overviewHeight }] = useMeasure();
|
|
||||||
const wasRefreshing = usePrevious(isRefreshing);
|
const wasRefreshing = usePrevious(isRefreshing);
|
||||||
const wasRenaming = usePrevious(isRenaming);
|
const wasRenaming = usePrevious(isRenaming);
|
||||||
|
|
||||||
@@ -796,16 +789,7 @@ function SeriesDetails({ seriesId }: SeriesDetailsProps) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ref={overviewRef} className={styles.overview}>
|
<div className={styles.overview}>{overview}</div>
|
||||||
<TextTruncate
|
|
||||||
line={
|
|
||||||
Math.floor(
|
|
||||||
overviewHeight / (defaultFontSize * lineHeight)
|
|
||||||
) - 1
|
|
||||||
}
|
|
||||||
text={overview}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<MetadataAttribution />
|
<MetadataAttribution />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -361,6 +361,24 @@ function MediaManagement() {
|
|||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
<FormGroup
|
||||||
|
advancedSettings={showAdvancedSettings}
|
||||||
|
isAdvanced={true}
|
||||||
|
>
|
||||||
|
<FormLabel>{translate('UserRejectedExtensions')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.TEXT}
|
||||||
|
name="userRejectedExtensions"
|
||||||
|
helpTexts={[
|
||||||
|
translate('UserRejectedExtensionsHelpText'),
|
||||||
|
translate('UserRejectedExtensionsTextsExamples'),
|
||||||
|
]}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
{...settings.userRejectedExtensions}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
</FieldSet>
|
</FieldSet>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ export const defaultState = {
|
|||||||
includeUnknownSeriesItems: true
|
includeUnknownSeriesItems: true
|
||||||
},
|
},
|
||||||
|
|
||||||
|
removalOptions: {
|
||||||
|
removalMethod: 'removeFromClient',
|
||||||
|
blocklistMethod: 'doNotBlocklist'
|
||||||
|
},
|
||||||
|
|
||||||
status: {
|
status: {
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
isPopulated: false,
|
isPopulated: false,
|
||||||
@@ -225,6 +230,7 @@ export const defaultState = {
|
|||||||
|
|
||||||
export const persistState = [
|
export const persistState = [
|
||||||
'queue.options',
|
'queue.options',
|
||||||
|
'queue.removalOptions',
|
||||||
'queue.paged.pageSize',
|
'queue.paged.pageSize',
|
||||||
'queue.paged.sortKey',
|
'queue.paged.sortKey',
|
||||||
'queue.paged.sortDirection',
|
'queue.paged.sortDirection',
|
||||||
@@ -257,6 +263,7 @@ export const SET_QUEUE_SORT = 'queue/setQueueSort';
|
|||||||
export const SET_QUEUE_FILTER = 'queue/setQueueFilter';
|
export const SET_QUEUE_FILTER = 'queue/setQueueFilter';
|
||||||
export const SET_QUEUE_TABLE_OPTION = 'queue/setQueueTableOption';
|
export const SET_QUEUE_TABLE_OPTION = 'queue/setQueueTableOption';
|
||||||
export const SET_QUEUE_OPTION = 'queue/setQueueOption';
|
export const SET_QUEUE_OPTION = 'queue/setQueueOption';
|
||||||
|
export const SET_QUEUE_REMOVAL_OPTION = 'queue/setQueueRemoveOption';
|
||||||
export const CLEAR_QUEUE = 'queue/clearQueue';
|
export const CLEAR_QUEUE = 'queue/clearQueue';
|
||||||
|
|
||||||
export const GRAB_QUEUE_ITEM = 'queue/grabQueueItem';
|
export const GRAB_QUEUE_ITEM = 'queue/grabQueueItem';
|
||||||
@@ -282,6 +289,7 @@ export const setQueueSort = createThunk(SET_QUEUE_SORT);
|
|||||||
export const setQueueFilter = createThunk(SET_QUEUE_FILTER);
|
export const setQueueFilter = createThunk(SET_QUEUE_FILTER);
|
||||||
export const setQueueTableOption = createAction(SET_QUEUE_TABLE_OPTION);
|
export const setQueueTableOption = createAction(SET_QUEUE_TABLE_OPTION);
|
||||||
export const setQueueOption = createAction(SET_QUEUE_OPTION);
|
export const setQueueOption = createAction(SET_QUEUE_OPTION);
|
||||||
|
export const setQueueRemovalOption = createAction(SET_QUEUE_REMOVAL_OPTION);
|
||||||
export const clearQueue = createAction(CLEAR_QUEUE);
|
export const clearQueue = createAction(CLEAR_QUEUE);
|
||||||
|
|
||||||
export const grabQueueItem = createThunk(GRAB_QUEUE_ITEM);
|
export const grabQueueItem = createThunk(GRAB_QUEUE_ITEM);
|
||||||
@@ -529,6 +537,18 @@ export const reducers = createHandleActions({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[SET_QUEUE_REMOVAL_OPTION]: function(state, { payload }) {
|
||||||
|
const queueRemovalOptions = state.removalOptions;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
removalOptions: {
|
||||||
|
...queueRemovalOptions,
|
||||||
|
...payload
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
[CLEAR_QUEUE]: createClearReducer(paged, {
|
[CLEAR_QUEUE]: createClearReducer(paged, {
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
isPopulated: false,
|
isPopulated: false,
|
||||||
|
|||||||
@@ -18,5 +18,6 @@ export default interface MediaManagement {
|
|||||||
scriptImportPath: string;
|
scriptImportPath: string;
|
||||||
importExtraFiles: boolean;
|
importExtraFiles: boolean;
|
||||||
extraFileExtensions: string;
|
extraFileExtensions: string;
|
||||||
|
userRejectedExtensions: string;
|
||||||
enableMediaInfo: boolean;
|
enableMediaInfo: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,6 +215,7 @@ namespace NzbDrone.Common.Instrumentation
|
|||||||
c.ForLogger("Microsoft.*").WriteToNil(LogLevel.Warn);
|
c.ForLogger("Microsoft.*").WriteToNil(LogLevel.Warn);
|
||||||
c.ForLogger("Microsoft.Hosting.Lifetime*").WriteToNil(LogLevel.Info);
|
c.ForLogger("Microsoft.Hosting.Lifetime*").WriteToNil(LogLevel.Info);
|
||||||
c.ForLogger("Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware").WriteToNil(LogLevel.Fatal);
|
c.ForLogger("Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware").WriteToNil(LogLevel.Fatal);
|
||||||
|
c.ForLogger("Sonarr.Http.Authentication.ApiKeyAuthenticationHandler").WriteToNil(LogLevel.Info);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -185,6 +185,9 @@ namespace NzbDrone.Core.Test.ParserTests
|
|||||||
[TestCase("[Erai-raws] Series Title! - 01 ~ 10 [1080p][Multiple Subtitle]", "Series Title!", 1, 10)]
|
[TestCase("[Erai-raws] Series Title! - 01 ~ 10 [1080p][Multiple Subtitle]", "Series Title!", 1, 10)]
|
||||||
[TestCase("[Erai-raws] Series-Title! 2 - 01 ~ 10 [1080p][Multiple Subtitle]", "Series-Title! 2", 1, 10)]
|
[TestCase("[Erai-raws] Series-Title! 2 - 01 ~ 10 [1080p][Multiple Subtitle]", "Series-Title! 2", 1, 10)]
|
||||||
[TestCase("Series_Title_2_[01-05]_[AniLibria_TV]_[WEBRip_1080p]", "Series Title 2", 1, 5)]
|
[TestCase("Series_Title_2_[01-05]_[AniLibria_TV]_[WEBRip_1080p]", "Series Title 2", 1, 5)]
|
||||||
|
[TestCase("[Moxie] One Series - The Country (892-916) (BD Remux 1080p AAC FLAC) [Dual Audio]", "One Series - The Country", 892, 916)]
|
||||||
|
[TestCase("[HatSubs] One Series (1017-1088) (WEB 1080p)", "One Series", 1017, 1088)]
|
||||||
|
[TestCase("[HatSubs] One Series 1017-1088 (WEB 1080p)", "One Series", 1017, 1088)]
|
||||||
|
|
||||||
// [TestCase("", "", 1, 2)]
|
// [TestCase("", "", 1, 2)]
|
||||||
public void should_parse_multi_episode_absolute_numbers(string postTitle, string title, int firstAbsoluteEpisodeNumber, int lastAbsoluteEpisodeNumber)
|
public void should_parse_multi_episode_absolute_numbers(string postTitle, string title, int firstAbsoluteEpisodeNumber, int lastAbsoluteEpisodeNumber)
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ namespace NzbDrone.Core.Test.ParserTests
|
|||||||
[TestCase("[hchcsen] Mobile Series 00 S01 [BD Remux Dual Audio 1080p AVC 2xFLAC] (Kidou Senshi Gundam 00 Season 1)", "Mobile Series 00", 1)]
|
[TestCase("[hchcsen] Mobile Series 00 S01 [BD Remux Dual Audio 1080p AVC 2xFLAC] (Kidou Senshi Gundam 00 Season 1)", "Mobile Series 00", 1)]
|
||||||
[TestCase("[HorribleRips] Mobile Series 00 S1 [1080p]", "Mobile Series 00", 1)]
|
[TestCase("[HorribleRips] Mobile Series 00 S1 [1080p]", "Mobile Series 00", 1)]
|
||||||
[TestCase("[Zoombie] Series 100: Bucket List S01 [Web][MKV][h265 10-bit][1080p][AC3 2.0][Softsubs (Zoombie)]", "Series 100: Bucket List", 1)]
|
[TestCase("[Zoombie] Series 100: Bucket List S01 [Web][MKV][h265 10-bit][1080p][AC3 2.0][Softsubs (Zoombie)]", "Series 100: Bucket List", 1)]
|
||||||
|
[TestCase("[GROUP] Series: Title (2023) (Season 1) [BDRip] [1080p Dual Audio HEVC 10 bits DDP] (serie) (Batch)", "Series: Title (2023)", 1)]
|
||||||
|
[TestCase("[GROUP] Series: Title (2023) (Season 1) [BDRip] [1080p Dual Audio HEVC 10-bits DDP] (serie) (Batch)", "Series: Title (2023)", 1)]
|
||||||
|
[TestCase("[GROUP] Series: Title (2023) (Season 1) [BDRip] [1080p Dual Audio HEVC 10-bit DDP] (serie) (Batch)", "Series: Title (2023)", 1)]
|
||||||
[TestCase("Seriesless (2016/S01/WEB-DL/1080p/AC3 5.1/DUAL/SUB)", "Seriesless (2016)", 1)]
|
[TestCase("Seriesless (2016/S01/WEB-DL/1080p/AC3 5.1/DUAL/SUB)", "Seriesless (2016)", 1)]
|
||||||
public void should_parse_full_season_release(string postTitle, string title, int season)
|
public void should_parse_full_season_release(string postTitle, string title, int season)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -257,6 +257,12 @@ namespace NzbDrone.Core.Configuration
|
|||||||
set { SetValue("EpisodeTitleRequired", value); }
|
set { SetValue("EpisodeTitleRequired", value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string UserRejectedExtensions
|
||||||
|
{
|
||||||
|
get { return GetValue("UserRejectedExtensions", string.Empty); }
|
||||||
|
set { SetValue("UserRejectedExtensions", value); }
|
||||||
|
}
|
||||||
|
|
||||||
public bool SetPermissionsLinux
|
public bool SetPermissionsLinux
|
||||||
{
|
{
|
||||||
get { return GetValueBoolean("SetPermissionsLinux", false); }
|
get { return GetValueBoolean("SetPermissionsLinux", false); }
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ namespace NzbDrone.Core.Configuration
|
|||||||
string ExtraFileExtensions { get; set; }
|
string ExtraFileExtensions { get; set; }
|
||||||
RescanAfterRefreshType RescanAfterRefresh { get; set; }
|
RescanAfterRefreshType RescanAfterRefresh { get; set; }
|
||||||
EpisodeTitleRequiredType EpisodeTitleRequired { get; set; }
|
EpisodeTitleRequiredType EpisodeTitleRequired { get; set; }
|
||||||
|
string UserRejectedExtensions { get; set; }
|
||||||
|
|
||||||
// Permissions (Media Management)
|
// Permissions (Media Management)
|
||||||
bool SetPermissionsLinux { get; set; }
|
bool SetPermissionsLinux { get; set; }
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using NzbDrone.Core.Localization;
|
|||||||
using NzbDrone.Core.MediaFiles.TorrentInfo;
|
using NzbDrone.Core.MediaFiles.TorrentInfo;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Core.RemotePathMappings;
|
using NzbDrone.Core.RemotePathMappings;
|
||||||
|
using NzbDrone.Core.Tags;
|
||||||
using NzbDrone.Core.Validation;
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Download.Clients.QBittorrent
|
namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||||
@@ -22,6 +23,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
|||||||
{
|
{
|
||||||
private readonly IQBittorrentProxySelector _proxySelector;
|
private readonly IQBittorrentProxySelector _proxySelector;
|
||||||
private readonly ICached<SeedingTimeCacheEntry> _seedingTimeCache;
|
private readonly ICached<SeedingTimeCacheEntry> _seedingTimeCache;
|
||||||
|
private readonly ITagRepository _tagRepository;
|
||||||
|
|
||||||
private class SeedingTimeCacheEntry
|
private class SeedingTimeCacheEntry
|
||||||
{
|
{
|
||||||
@@ -38,12 +40,14 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
|||||||
ICacheManager cacheManager,
|
ICacheManager cacheManager,
|
||||||
ILocalizationService localizationService,
|
ILocalizationService localizationService,
|
||||||
IBlocklistService blocklistService,
|
IBlocklistService blocklistService,
|
||||||
|
ITagRepository tagRepository,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
|
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
|
||||||
{
|
{
|
||||||
_proxySelector = proxySelector;
|
_proxySelector = proxySelector;
|
||||||
|
|
||||||
_seedingTimeCache = cacheManager.GetCache<SeedingTimeCacheEntry>(GetType(), "seedingTime");
|
_seedingTimeCache = cacheManager.GetCache<SeedingTimeCacheEntry>(GetType(), "seedingTime");
|
||||||
|
_tagRepository = tagRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IQBittorrentProxy Proxy => _proxySelector.GetProxy(Settings);
|
private IQBittorrentProxy Proxy => _proxySelector.GetProxy(Settings);
|
||||||
@@ -83,7 +87,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
|||||||
|
|
||||||
Proxy.AddTorrentFromUrl(magnetLink, addHasSetShareLimits && setShareLimits ? remoteEpisode.SeedConfiguration : null, Settings);
|
Proxy.AddTorrentFromUrl(magnetLink, addHasSetShareLimits && setShareLimits ? remoteEpisode.SeedConfiguration : null, Settings);
|
||||||
|
|
||||||
if ((!addHasSetShareLimits && setShareLimits) || moveToTop || forceStart)
|
if ((!addHasSetShareLimits && setShareLimits) || moveToTop || forceStart || (Settings.AddSeriesTags && remoteEpisode.Series.Tags.Count > 0))
|
||||||
{
|
{
|
||||||
if (!WaitForTorrent(hash))
|
if (!WaitForTorrent(hash))
|
||||||
{
|
{
|
||||||
@@ -125,6 +129,18 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
|||||||
_logger.Warn(ex, "Failed to set ForceStart for {0}.", hash);
|
_logger.Warn(ex, "Failed to set ForceStart for {0}.", hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Settings.AddSeriesTags && remoteEpisode.Series.Tags.Count > 0)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Proxy.AddTags(hash.ToLower(), _tagRepository.GetTags(remoteEpisode.Series.Tags).Select(tag => tag.Label), Settings);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Warn(ex, "Failed to add tags for {0}.", hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return hash;
|
return hash;
|
||||||
@@ -140,7 +156,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
|||||||
|
|
||||||
Proxy.AddTorrentFromFile(filename, fileContent, addHasSetShareLimits ? remoteEpisode.SeedConfiguration : null, Settings);
|
Proxy.AddTorrentFromFile(filename, fileContent, addHasSetShareLimits ? remoteEpisode.SeedConfiguration : null, Settings);
|
||||||
|
|
||||||
if ((!addHasSetShareLimits && setShareLimits) || moveToTop || forceStart)
|
if ((!addHasSetShareLimits && setShareLimits) || moveToTop || forceStart || (Settings.AddSeriesTags && remoteEpisode.Series.Tags.Count > 0))
|
||||||
{
|
{
|
||||||
if (!WaitForTorrent(hash))
|
if (!WaitForTorrent(hash))
|
||||||
{
|
{
|
||||||
@@ -182,6 +198,18 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
|||||||
_logger.Warn(ex, "Failed to set ForceStart for {0}.", hash);
|
_logger.Warn(ex, "Failed to set ForceStart for {0}.", hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Settings.AddSeriesTags && remoteEpisode.Series.Tags.Count > 0)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Proxy.AddTags(hash.ToLower(), _tagRepository.GetTags(remoteEpisode.Series.Tags).Select(tag => tag.Label), Settings);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Warn(ex, "Failed to add tags for {0}.", hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return hash;
|
return hash;
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
|||||||
void SetTorrentSeedingConfiguration(string hash, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings);
|
void SetTorrentSeedingConfiguration(string hash, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings);
|
||||||
void MoveTorrentToTopInQueue(string hash, QBittorrentSettings settings);
|
void MoveTorrentToTopInQueue(string hash, QBittorrentSettings settings);
|
||||||
void SetForceStart(string hash, bool enabled, QBittorrentSettings settings);
|
void SetForceStart(string hash, bool enabled, QBittorrentSettings settings);
|
||||||
|
void AddTags(string hash, IEnumerable<string> tags, QBittorrentSettings settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IQBittorrentProxySelector
|
public interface IQBittorrentProxySelector
|
||||||
|
|||||||
@@ -273,6 +273,11 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
|||||||
ProcessRequest(request, settings);
|
ProcessRequest(request, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddTags(string hash, IEnumerable<string> tags, QBittorrentSettings settings)
|
||||||
|
{
|
||||||
|
// Not supported on api v1
|
||||||
|
}
|
||||||
|
|
||||||
private HttpRequestBuilder BuildRequest(QBittorrentSettings settings)
|
private HttpRequestBuilder BuildRequest(QBittorrentSettings settings)
|
||||||
{
|
{
|
||||||
var requestBuilder = new HttpRequestBuilder(settings.UseSsl, settings.Host, settings.Port, settings.UrlBase)
|
var requestBuilder = new HttpRequestBuilder(settings.UseSsl, settings.Host, settings.Port, settings.UrlBase)
|
||||||
|
|||||||
@@ -338,6 +338,15 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
|||||||
ProcessRequest(request, settings);
|
ProcessRequest(request, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddTags(string hash, IEnumerable<string> tags, QBittorrentSettings settings)
|
||||||
|
{
|
||||||
|
var request = BuildRequest(settings).Resource("/api/v2/torrents/addTags")
|
||||||
|
.Post()
|
||||||
|
.AddFormParameter("hashes", hash)
|
||||||
|
.AddFormParameter("tags", string.Join(",", tags));
|
||||||
|
ProcessRequest(request, settings);
|
||||||
|
}
|
||||||
|
|
||||||
private HttpRequestBuilder BuildRequest(QBittorrentSettings settings)
|
private HttpRequestBuilder BuildRequest(QBittorrentSettings settings)
|
||||||
{
|
{
|
||||||
var requestBuilder = new HttpRequestBuilder(settings.UseSsl, settings.Host, settings.Port, settings.UrlBase)
|
var requestBuilder = new HttpRequestBuilder(settings.UseSsl, settings.Host, settings.Port, settings.UrlBase)
|
||||||
|
|||||||
@@ -73,6 +73,9 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
|||||||
[FieldDefinition(13, Label = "DownloadClientQbittorrentSettingsContentLayout", Type = FieldType.Select, SelectOptions = typeof(QBittorrentContentLayout), HelpText = "DownloadClientQbittorrentSettingsContentLayoutHelpText")]
|
[FieldDefinition(13, Label = "DownloadClientQbittorrentSettingsContentLayout", Type = FieldType.Select, SelectOptions = typeof(QBittorrentContentLayout), HelpText = "DownloadClientQbittorrentSettingsContentLayoutHelpText")]
|
||||||
public int ContentLayout { get; set; }
|
public int ContentLayout { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(14, Label = "DownloadClientQbittorrentSettingsAddSeriesTags", Type = FieldType.Checkbox, HelpText = "DownloadClientQbittorrentSettingsAddSeriesTagsHelpText")]
|
||||||
|
public bool AddSeriesTags { get; set; }
|
||||||
|
|
||||||
public override NzbDroneValidationResult Validate()
|
public override NzbDroneValidationResult Validate()
|
||||||
{
|
{
|
||||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||||
|
|||||||
@@ -50,6 +50,12 @@ public class RejectedImportService : IRejectedImportService
|
|||||||
_logger.Trace("Download '{0}' contains executable file, marking as failed", trackedDownload.DownloadItem.Title);
|
_logger.Trace("Download '{0}' contains executable file, marking as failed", trackedDownload.DownloadItem.Title);
|
||||||
trackedDownload.Fail();
|
trackedDownload.Fail();
|
||||||
}
|
}
|
||||||
|
else if (rejectionReason == ImportRejectionReason.UserRejectedExtension &&
|
||||||
|
indexerSettings.FailDownloads.Contains(FailDownloads.UserDefinedExtensions))
|
||||||
|
{
|
||||||
|
_logger.Trace("Download '{0}' contains user defined rejected file extension, marking as failed", trackedDownload.DownloadItem.Title);
|
||||||
|
trackedDownload.Fail();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trackedDownload.Warn(new TrackedDownloadStatusMessage(trackedDownload.DownloadItem.Title, importResult.Errors));
|
trackedDownload.Warn(new TrackedDownloadStatusMessage(trackedDownload.DownloadItem.Title, importResult.Errors));
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ namespace NzbDrone.Core.Download
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
hash = MagnetLink.Parse(magnetUrl).InfoHash.ToHex();
|
hash = MagnetLink.Parse(magnetUrl).InfoHashes.V1OrV2.ToHex();
|
||||||
}
|
}
|
||||||
catch (FormatException ex)
|
catch (FormatException ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,5 +8,8 @@ public enum FailDownloads
|
|||||||
Executables = 0,
|
Executables = 0,
|
||||||
|
|
||||||
[FieldOption(Label = "Potentially Dangerous")]
|
[FieldOption(Label = "Potentially Dangerous")]
|
||||||
PotentiallyDangerous = 1
|
PotentiallyDangerous = 1,
|
||||||
|
|
||||||
|
[FieldOption(Label = "User Defined Extensions")]
|
||||||
|
UserDefinedExtensions = 2
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ namespace NzbDrone.Core.Indexers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return MagnetLink.Parse(magnetUrl).InfoHash.ToHex();
|
return MagnetLink.Parse(magnetUrl).InfoHashes.V1OrV2.ToHex();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -776,7 +776,7 @@
|
|||||||
"DownloadClientFreeboxSettingsApiUrl": "URL de l'API",
|
"DownloadClientFreeboxSettingsApiUrl": "URL de l'API",
|
||||||
"DownloadClientSabnzbdValidationEnableDisableTvSorting": "Desactiva l'ordenació de TV",
|
"DownloadClientSabnzbdValidationEnableDisableTvSorting": "Desactiva l'ordenació de TV",
|
||||||
"MonitorNoEpisodes": "Cap",
|
"MonitorNoEpisodes": "Cap",
|
||||||
"RemoveRootFolder": "Elimina la carpeta arrel",
|
"RemoveRootFolder": "Elimina la Carpeta Arrel",
|
||||||
"CustomFormatsSpecificationMaximumSize": "Mida màxima",
|
"CustomFormatsSpecificationMaximumSize": "Mida màxima",
|
||||||
"DownloadClientFloodSettingsAdditionalTags": "Etiquetes addicionals",
|
"DownloadClientFloodSettingsAdditionalTags": "Etiquetes addicionals",
|
||||||
"DownloadClientSettings": "Configuració del client de baixada",
|
"DownloadClientSettings": "Configuració del client de baixada",
|
||||||
@@ -2161,5 +2161,10 @@
|
|||||||
"DownloadClientItemErrorMessage": "{clientName} está reportant un error: {message}",
|
"DownloadClientItemErrorMessage": "{clientName} está reportant un error: {message}",
|
||||||
"EpisodesInSeason": "{episodeCount} episodis en la temporada",
|
"EpisodesInSeason": "{episodeCount} episodis en la temporada",
|
||||||
"NotificationsPushcutSettingsNotificationName": "Nom de la notificació",
|
"NotificationsPushcutSettingsNotificationName": "Nom de la notificació",
|
||||||
"AutoTaggingSpecificationNetwork": "Xarxa(es)"
|
"AutoTaggingSpecificationNetwork": "Xarxa(es)",
|
||||||
|
"MonitorEpisodes": "Monitorar episodis",
|
||||||
|
"NotificationsAppriseSettingsIncludePosterHelpText": "Inclou el cartell al missatge",
|
||||||
|
"MonitorEpisodesModalInfo": "Aquesta opció només ajustarà quins episodis o temporades són monitorats en les sèries. Seleccionar Cap deixarà de monitorar les sèries",
|
||||||
|
"EpisodeMonitoring": "Monitoratge d'episodis",
|
||||||
|
"NotificationsAppriseSettingsIncludePoster": "Inclou el cartell"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -481,6 +481,8 @@
|
|||||||
"DownloadClientPneumaticSettingsStrmFolder": "Strm Folder",
|
"DownloadClientPneumaticSettingsStrmFolder": "Strm Folder",
|
||||||
"DownloadClientPneumaticSettingsStrmFolderHelpText": ".strm files in this folder will be import by drone",
|
"DownloadClientPneumaticSettingsStrmFolderHelpText": ".strm files in this folder will be import by drone",
|
||||||
"DownloadClientPriorityHelpText": "Download Client Priority from 1 (Highest) to 50 (Lowest). Default: 1. Round-Robin is used for clients with the same priority.",
|
"DownloadClientPriorityHelpText": "Download Client Priority from 1 (Highest) to 50 (Lowest). Default: 1. Round-Robin is used for clients with the same priority.",
|
||||||
|
"DownloadClientQbittorrentSettingsAddSeriesTags": "Add Series Tags",
|
||||||
|
"DownloadClientQbittorrentSettingsAddSeriesTagsHelpText": "Add series tags to new torrents added to the download client (qBittorrent 4.1.0+)",
|
||||||
"DownloadClientQbittorrentSettingsContentLayout": "Content Layout",
|
"DownloadClientQbittorrentSettingsContentLayout": "Content Layout",
|
||||||
"DownloadClientQbittorrentSettingsContentLayoutHelpText": "Whether to use qBittorrent's configured content layout, the original layout from the torrent or always create a subfolder (qBittorrent 4.3.2+)",
|
"DownloadClientQbittorrentSettingsContentLayoutHelpText": "Whether to use qBittorrent's configured content layout, the original layout from the torrent or always create a subfolder (qBittorrent 4.3.2+)",
|
||||||
"DownloadClientQbittorrentSettingsFirstAndLastFirst": "First and Last First",
|
"DownloadClientQbittorrentSettingsFirstAndLastFirst": "First and Last First",
|
||||||
@@ -2140,6 +2142,9 @@
|
|||||||
"UsenetDelayTime": "Usenet Delay: {usenetDelay}",
|
"UsenetDelayTime": "Usenet Delay: {usenetDelay}",
|
||||||
"UsenetDisabled": "Usenet Disabled",
|
"UsenetDisabled": "Usenet Disabled",
|
||||||
"UserInvokedSearch": "User Invoked Search",
|
"UserInvokedSearch": "User Invoked Search",
|
||||||
|
"UserRejectedExtensions": "Additional Rejected File Extensions",
|
||||||
|
"UserRejectedExtensionsHelpText": "Comma separated list of files extensions to fail (Fail Downloads also needs to be enabled per indexer)",
|
||||||
|
"UserRejectedExtensionsTextsExamples": "Examples: '.ext, .xyz' or 'ext,xyz'",
|
||||||
"Username": "Username",
|
"Username": "Username",
|
||||||
"UtcAirDate": "UTC Air Date",
|
"UtcAirDate": "UTC Air Date",
|
||||||
"Version": "Version",
|
"Version": "Version",
|
||||||
|
|||||||
@@ -1945,7 +1945,7 @@
|
|||||||
"NotificationsTwitterSettingsDirectMessageHelpText": "Envía un mensaje directo en lugar de un mensaje público",
|
"NotificationsTwitterSettingsDirectMessageHelpText": "Envía un mensaje directo en lugar de un mensaje público",
|
||||||
"OnApplicationUpdate": "Al actualizar la aplicación",
|
"OnApplicationUpdate": "Al actualizar la aplicación",
|
||||||
"OnSeriesAdd": "Al añadir series",
|
"OnSeriesAdd": "Al añadir series",
|
||||||
"OnlyForBulkSeasonReleases": "Solo para lanzamientos de temporada a granel",
|
"OnlyForBulkSeasonReleases": "Solo para lanzamientos de temporada en bloque",
|
||||||
"OrganizeModalHeaderSeason": "Organizar y renombrar - {season}",
|
"OrganizeModalHeaderSeason": "Organizar y renombrar - {season}",
|
||||||
"OverrideGrabNoEpisode": "Al menos un episodio debe ser seleccionado",
|
"OverrideGrabNoEpisode": "Al menos un episodio debe ser seleccionado",
|
||||||
"OverrideGrabNoQuality": "La calidad debe ser seleccionada",
|
"OverrideGrabNoQuality": "La calidad debe ser seleccionada",
|
||||||
@@ -2163,5 +2163,8 @@
|
|||||||
"CloneImportList": "Clonar lista de importación",
|
"CloneImportList": "Clonar lista de importación",
|
||||||
"AutoTaggingSpecificationNetwork": "Red(es)",
|
"AutoTaggingSpecificationNetwork": "Red(es)",
|
||||||
"NotificationsAppriseSettingsIncludePoster": "Incluir póster",
|
"NotificationsAppriseSettingsIncludePoster": "Incluir póster",
|
||||||
"NotificationsAppriseSettingsIncludePosterHelpText": "Incluir póster en el mensaje"
|
"NotificationsAppriseSettingsIncludePosterHelpText": "Incluir póster en el mensaje",
|
||||||
|
"EpisodeMonitoring": "Monitorización de episodios",
|
||||||
|
"MonitorEpisodes": "Monitorizar episodios",
|
||||||
|
"MonitorEpisodesModalInfo": "Esta opción solo ajustará qué episodios o temporadas son monitorizados en las series. Seleccionar Ninguno dejará de monitorizar las series"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2161,5 +2161,7 @@
|
|||||||
"DownloadClientItemErrorMessage": "{clientName} ilmoittaa virheestä: {message}",
|
"DownloadClientItemErrorMessage": "{clientName} ilmoittaa virheestä: {message}",
|
||||||
"EpisodesInSeason": "Tuotantokaudessa on {episodeCount} jaksoa",
|
"EpisodesInSeason": "Tuotantokaudessa on {episodeCount} jaksoa",
|
||||||
"CloneImportList": "Monista tuontilista",
|
"CloneImportList": "Monista tuontilista",
|
||||||
"DefaultNameCopiedImportList": "{name} (kopio)"
|
"DefaultNameCopiedImportList": "{name} (kopio)",
|
||||||
|
"EpisodeMonitoring": "Jakson Valvonta",
|
||||||
|
"MonitorEpisodes": "Valvo Jaksoja"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -677,7 +677,7 @@
|
|||||||
"RootFolderMultipleMissingHealthCheckMessage": "Plusieurs dossiers racine sont manquants : {rootFolderPaths}",
|
"RootFolderMultipleMissingHealthCheckMessage": "Plusieurs dossiers racine sont manquants : {rootFolderPaths}",
|
||||||
"RssIsNotSupportedWithThisIndexer": "RSS n'est pas pris en charge avec cet indexeur",
|
"RssIsNotSupportedWithThisIndexer": "RSS n'est pas pris en charge avec cet indexeur",
|
||||||
"RssSyncInterval": "Intervalle de synchronisation RSS",
|
"RssSyncInterval": "Intervalle de synchronisation RSS",
|
||||||
"RssSyncIntervalHelpText": "Intervalle en minutes. Réglez sur zéro pour désactiver (cela arrêtera toute capture de libération automatique)",
|
"RssSyncIntervalHelpText": "Intervalle en minutes. Réglez sur zéro pour désactiver (cela arrêtera toute capture de release automatique)",
|
||||||
"RssSyncIntervalHelpTextWarning": "Cela s'appliquera à tous les indexeurs, veuillez suivre les règles énoncées par eux",
|
"RssSyncIntervalHelpTextWarning": "Cela s'appliquera à tous les indexeurs, veuillez suivre les règles énoncées par eux",
|
||||||
"SaveChanges": "Sauvegarder les modifications",
|
"SaveChanges": "Sauvegarder les modifications",
|
||||||
"SceneNumbering": "Numérotation des scènes",
|
"SceneNumbering": "Numérotation des scènes",
|
||||||
|
|||||||
@@ -58,5 +58,10 @@
|
|||||||
"AuthenticationRequiredUsernameHelpTextWarning": "Unesi novo korisničko ime",
|
"AuthenticationRequiredUsernameHelpTextWarning": "Unesi novo korisničko ime",
|
||||||
"AddConditionError": "Neuspješno dodavanje novog uvjeta, molimo pokušaj ponovno.",
|
"AddConditionError": "Neuspješno dodavanje novog uvjeta, molimo pokušaj ponovno.",
|
||||||
"AddIndexerImplementation": "Dodaj Indexer - {implementationName}",
|
"AddIndexerImplementation": "Dodaj Indexer - {implementationName}",
|
||||||
"AuthenticationRequiredWarning": "Kako bi se spriječio udaljeni pristup bez autentikacije, {appName} sad zahtjeva da autentikacija bude omogućena. Izborno se može onemogućiti autentikacija s lokalnih adresa."
|
"AuthenticationRequiredWarning": "Kako bi se spriječio udaljeni pristup bez autentikacije, {appName} sad zahtjeva da autentikacija bude omogućena. Izborno se može onemogućiti autentikacija s lokalnih adresa.",
|
||||||
|
"AddANewPath": "Dodaj novu putanju",
|
||||||
|
"AddCustomFilter": "Dodaj proizvoljan filter",
|
||||||
|
"AddCustomFormat": "Dodaj proizvoljan format",
|
||||||
|
"Add": "Dodaj",
|
||||||
|
"Activity": "Aktivnost"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -326,7 +326,7 @@
|
|||||||
"Grabbed": "Obtido",
|
"Grabbed": "Obtido",
|
||||||
"Ignored": "Ignorado",
|
"Ignored": "Ignorado",
|
||||||
"Imported": "Importado",
|
"Imported": "Importado",
|
||||||
"IncludeUnmonitored": "Incluir não monitorados",
|
"IncludeUnmonitored": "Incluir Não Monitorados",
|
||||||
"Indexer": "Indexador",
|
"Indexer": "Indexador",
|
||||||
"LatestSeason": "Temporada mais recente",
|
"LatestSeason": "Temporada mais recente",
|
||||||
"MissingEpisodes": "Episódios ausentes",
|
"MissingEpisodes": "Episódios ausentes",
|
||||||
@@ -383,7 +383,7 @@
|
|||||||
"LastUsed": "Usado por último",
|
"LastUsed": "Usado por último",
|
||||||
"MoveAutomatically": "Mover automaticamente",
|
"MoveAutomatically": "Mover automaticamente",
|
||||||
"NoResultsFound": "Nenhum resultado encontrado",
|
"NoResultsFound": "Nenhum resultado encontrado",
|
||||||
"RemoveRootFolder": "Remover pasta raiz",
|
"RemoveRootFolder": "Remover Pasta Raiz",
|
||||||
"RemoveTagsAutomatically": "Remover Tags Automaticamente",
|
"RemoveTagsAutomatically": "Remover Tags Automaticamente",
|
||||||
"Season": "Temporada",
|
"Season": "Temporada",
|
||||||
"SelectFolder": "Selecionar Pasta",
|
"SelectFolder": "Selecionar Pasta",
|
||||||
@@ -919,7 +919,7 @@
|
|||||||
"VisitTheWikiForMoreDetails": "Visite o wiki para mais detalhes: ",
|
"VisitTheWikiForMoreDetails": "Visite o wiki para mais detalhes: ",
|
||||||
"WantMoreControlAddACustomFormat": "Quer mais controle sobre quais downloads são preferidos? Adicione um [Formato Personalizado](/settings/customformats)",
|
"WantMoreControlAddACustomFormat": "Quer mais controle sobre quais downloads são preferidos? Adicione um [Formato Personalizado](/settings/customformats)",
|
||||||
"UnmonitorSpecialEpisodes": "Não Monitorar Especiais",
|
"UnmonitorSpecialEpisodes": "Não Monitorar Especiais",
|
||||||
"MonitorAllEpisodes": "Todos os episódios",
|
"MonitorAllEpisodes": "Todos os Episódios",
|
||||||
"AddNewSeries": "Adicionar nova série",
|
"AddNewSeries": "Adicionar nova série",
|
||||||
"AddNewSeriesHelpText": "É fácil adicionar uma nova série, basta começar a digitar o nome da série que deseja acrescentar.",
|
"AddNewSeriesHelpText": "É fácil adicionar uma nova série, basta começar a digitar o nome da série que deseja acrescentar.",
|
||||||
"AddNewSeriesSearchForCutoffUnmetEpisodes": "Iniciar a pesquisa por episódios cujos limites não foram atendidos",
|
"AddNewSeriesSearchForCutoffUnmetEpisodes": "Iniciar a pesquisa por episódios cujos limites não foram atendidos",
|
||||||
@@ -942,7 +942,7 @@
|
|||||||
"LibraryImportTipsQualityInEpisodeFilename": "Certifique-se de que seus arquivos incluam a qualidade nos nomes de arquivo. Por exemplo: \"episódio.s02e15.bluray.mkv\"",
|
"LibraryImportTipsQualityInEpisodeFilename": "Certifique-se de que seus arquivos incluam a qualidade nos nomes de arquivo. Por exemplo: \"episódio.s02e15.bluray.mkv\"",
|
||||||
"Monitor": "Monitorar",
|
"Monitor": "Monitorar",
|
||||||
"MonitorAllEpisodesDescription": "Monitorar todos os episódios, exceto os especiais",
|
"MonitorAllEpisodesDescription": "Monitorar todos os episódios, exceto os especiais",
|
||||||
"MonitorExistingEpisodes": "Episódios existentes",
|
"MonitorExistingEpisodes": "Episódios Existentes",
|
||||||
"MonitorFirstSeason": "Primeira temporada",
|
"MonitorFirstSeason": "Primeira temporada",
|
||||||
"MonitorFirstSeasonDescription": "Monitorar todos os episódios da primeira temporada. As demais temporadas serão ignoradas",
|
"MonitorFirstSeasonDescription": "Monitorar todos os episódios da primeira temporada. As demais temporadas serão ignoradas",
|
||||||
"MonitorFutureEpisodes": "Futuros episódios",
|
"MonitorFutureEpisodes": "Futuros episódios",
|
||||||
@@ -1510,7 +1510,7 @@
|
|||||||
"DownloadClientFloodSettingsTagsHelpText": "Etiquetas iniciais de um download. Para ser reconhecido, um download deve ter todas as etiquetas iniciais. Isso evita conflitos com downloads não relacionados.",
|
"DownloadClientFloodSettingsTagsHelpText": "Etiquetas iniciais de um download. Para ser reconhecido, um download deve ter todas as etiquetas iniciais. Isso evita conflitos com downloads não relacionados.",
|
||||||
"DownloadClientFloodSettingsAdditionalTagsHelpText": "Adiciona propriedades de mídia como etiquetas. As dicas são exemplos.",
|
"DownloadClientFloodSettingsAdditionalTagsHelpText": "Adiciona propriedades de mídia como etiquetas. As dicas são exemplos.",
|
||||||
"DownloadClientFloodSettingsPostImportTagsHelpText": "Acrescenta etiquetas após a importação de um download.",
|
"DownloadClientFloodSettingsPostImportTagsHelpText": "Acrescenta etiquetas após a importação de um download.",
|
||||||
"BlackholeWatchFolder": "Pasta de monitoramento",
|
"BlackholeWatchFolder": "Pasta de Monitoramento",
|
||||||
"BlackholeWatchFolderHelpText": "Pasta da qual o {appName} deve importar os downloads concluídos",
|
"BlackholeWatchFolderHelpText": "Pasta da qual o {appName} deve importar os downloads concluídos",
|
||||||
"Category": "Categoria",
|
"Category": "Categoria",
|
||||||
"Directory": "Diretório",
|
"Directory": "Diretório",
|
||||||
@@ -1675,7 +1675,7 @@
|
|||||||
"PasswordConfirmation": "Confirmação Da Senha",
|
"PasswordConfirmation": "Confirmação Da Senha",
|
||||||
"MonitorPilotEpisodeDescription": "Monitorar apenas o primeiro episódio da primeira temporada",
|
"MonitorPilotEpisodeDescription": "Monitorar apenas o primeiro episódio da primeira temporada",
|
||||||
"MonitorNoNewSeasonsDescription": "Não monitorar nenhuma nova temporada automaticamente",
|
"MonitorNoNewSeasonsDescription": "Não monitorar nenhuma nova temporada automaticamente",
|
||||||
"MonitorAllSeasons": "Todas as temporadas",
|
"MonitorAllSeasons": "Todas as Temporadas",
|
||||||
"MonitorAllSeasonsDescription": "Monitorar todas as novas temporadas automaticamente",
|
"MonitorAllSeasonsDescription": "Monitorar todas as novas temporadas automaticamente",
|
||||||
"MonitorLastSeason": "Última temporada",
|
"MonitorLastSeason": "Última temporada",
|
||||||
"MonitorLastSeasonDescription": "Monitorar todos os episódios da última temporada",
|
"MonitorLastSeasonDescription": "Monitorar todos os episódios da última temporada",
|
||||||
@@ -2019,7 +2019,7 @@
|
|||||||
"IgnoreDownload": "Ignorar download",
|
"IgnoreDownload": "Ignorar download",
|
||||||
"ImportListStatusAllPossiblePartialFetchHealthCheckMessage": "Todas as listas requerem interação manual devido a possíveis buscas parciais",
|
"ImportListStatusAllPossiblePartialFetchHealthCheckMessage": "Todas as listas requerem interação manual devido a possíveis buscas parciais",
|
||||||
"KeepAndTagSeries": "Manter e etiquetar séries",
|
"KeepAndTagSeries": "Manter e etiquetar séries",
|
||||||
"KeepAndUnmonitorSeries": "Manter e desmonitorar séries",
|
"KeepAndUnmonitorSeries": "Manter e Não Monitorar Séries",
|
||||||
"ListSyncLevelHelpText": "As séries na biblioteca serão tratadas com base na sua seleção se saírem de sua(s) lista(s) ou não aparecerem nela(s)",
|
"ListSyncLevelHelpText": "As séries na biblioteca serão tratadas com base na sua seleção se saírem de sua(s) lista(s) ou não aparecerem nela(s)",
|
||||||
"ListSyncTag": "Etiqueta de sincronização de lista",
|
"ListSyncTag": "Etiqueta de sincronização de lista",
|
||||||
"ListSyncTagHelpText": "Esta etiqueta será adicionada quando uma série sair ou não estiver mais na(s) sua(s) lista(s)",
|
"ListSyncTagHelpText": "Esta etiqueta será adicionada quando uma série sair ou não estiver mais na(s) sua(s) lista(s)",
|
||||||
@@ -2032,8 +2032,8 @@
|
|||||||
"CustomFormatsSpecificationFlag": "Sinalizador",
|
"CustomFormatsSpecificationFlag": "Sinalizador",
|
||||||
"IndexerFlags": "Sinalizadores do indexador",
|
"IndexerFlags": "Sinalizadores do indexador",
|
||||||
"SetIndexerFlags": "Definir Sinalizadores de Indexador",
|
"SetIndexerFlags": "Definir Sinalizadores de Indexador",
|
||||||
"ImportListsSonarrSettingsSyncSeasonMonitoring": "Sincronizar monitoramento da temporada",
|
"ImportListsSonarrSettingsSyncSeasonMonitoring": "Sincronizar Monitoramento da Temporada",
|
||||||
"ImportListsSonarrSettingsSyncSeasonMonitoringHelpText": "Sincronizar o monitoramento da temporada da instância do {appName}. Se ativada, \"Monitorar\" será ignorado",
|
"ImportListsSonarrSettingsSyncSeasonMonitoringHelpText": "Sincronizar o monitoramento da temporada da instância do {appName}. Se ativada, 'Monitorar' será ignorado",
|
||||||
"CustomFilter": "Filtro personalizado",
|
"CustomFilter": "Filtro personalizado",
|
||||||
"Filters": "Filtros",
|
"Filters": "Filtros",
|
||||||
"Label": "Rótulo",
|
"Label": "Rótulo",
|
||||||
@@ -2065,7 +2065,7 @@
|
|||||||
"DayOfWeekAt": "{day} às {time}",
|
"DayOfWeekAt": "{day} às {time}",
|
||||||
"TodayAt": "Hoje às {time}",
|
"TodayAt": "Hoje às {time}",
|
||||||
"TomorrowAt": "Amanhã às {time}",
|
"TomorrowAt": "Amanhã às {time}",
|
||||||
"HasUnmonitoredSeason": "Há temporadas não monitoradas",
|
"HasUnmonitoredSeason": "Há Temporadas Não Monitoradas",
|
||||||
"YesterdayAt": "Ontem às {time}",
|
"YesterdayAt": "Ontem às {time}",
|
||||||
"UnableToImportAutomatically": "Não foi possível importar automaticamente",
|
"UnableToImportAutomatically": "Não foi possível importar automaticamente",
|
||||||
"CustomColonReplacement": "Personalizar substituto do dois-pontos",
|
"CustomColonReplacement": "Personalizar substituto do dois-pontos",
|
||||||
@@ -2163,5 +2163,8 @@
|
|||||||
"EpisodesInSeason": "{episodeCount} episódios na temporada",
|
"EpisodesInSeason": "{episodeCount} episódios na temporada",
|
||||||
"AutoTaggingSpecificationNetwork": "Rede(s)",
|
"AutoTaggingSpecificationNetwork": "Rede(s)",
|
||||||
"NotificationsAppriseSettingsIncludePoster": "Incluir Pôster",
|
"NotificationsAppriseSettingsIncludePoster": "Incluir Pôster",
|
||||||
"NotificationsAppriseSettingsIncludePosterHelpText": "Incluir pôster na mensagem"
|
"NotificationsAppriseSettingsIncludePosterHelpText": "Incluir pôster na mensagem",
|
||||||
|
"EpisodeMonitoring": "Monitoramento do Episódio",
|
||||||
|
"MonitorEpisodes": "Monitorar Episódios",
|
||||||
|
"MonitorEpisodesModalInfo": "Esta configuração ajustará apenas quais episódios ou temporadas serão monitorados dentro de uma série. Selecionar Nenhum desativará o monitoramento da série"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
"Close": "Închide",
|
"Close": "Închide",
|
||||||
"Delete": "Șterge",
|
"Delete": "Șterge",
|
||||||
"Added": "Adăugat",
|
"Added": "Adăugat",
|
||||||
"CountSeasons": "{count} sezoane",
|
"CountSeasons": "{count} Sezoane",
|
||||||
"DownloadClientStatusSingleClientHealthCheckMessage": "Clienții de descărcare indisponibili datorită erorilor: {downloadClientNames}",
|
"DownloadClientStatusSingleClientHealthCheckMessage": "Clienții de descărcare indisponibili datorită erorilor: {downloadClientNames}",
|
||||||
"EnableAutomaticSearch": "Activați căutarea automată",
|
"EnableAutomaticSearch": "Activați căutarea automată",
|
||||||
"EnableInteractiveSearch": "Activați căutarea interactivă",
|
"EnableInteractiveSearch": "Activați căutarea interactivă",
|
||||||
@@ -172,7 +172,7 @@
|
|||||||
"HistoryLoadError": "Istoricul nu poate fi încărcat",
|
"HistoryLoadError": "Istoricul nu poate fi încărcat",
|
||||||
"DefaultNameCopiedSpecification": "{name} - Copie",
|
"DefaultNameCopiedSpecification": "{name} - Copie",
|
||||||
"DefaultNameCopiedProfile": "{name} - Copie",
|
"DefaultNameCopiedProfile": "{name} - Copie",
|
||||||
"DeletedReasonManual": "Fișierul a fost șters prin interfața de utilizare",
|
"DeletedReasonManual": "Fișierul a fost șters folosind {appName}, fie manual, fie de către un alt instrument prin API",
|
||||||
"NoHistoryFound": "Nu s-a găsit istoric",
|
"NoHistoryFound": "Nu s-a găsit istoric",
|
||||||
"Or": "sau",
|
"Or": "sau",
|
||||||
"PendingDownloadClientUnavailable": "În așteptare - Clientul de descărcare nu este disponibil",
|
"PendingDownloadClientUnavailable": "În așteptare - Clientul de descărcare nu este disponibil",
|
||||||
@@ -212,5 +212,17 @@
|
|||||||
"Clone": "Clonează",
|
"Clone": "Clonează",
|
||||||
"DownloadClientSettingsOlderPriority": "Prioritate mai vechi",
|
"DownloadClientSettingsOlderPriority": "Prioritate mai vechi",
|
||||||
"DownloadClientSettingsRecentPriority": "Prioritate recente",
|
"DownloadClientSettingsRecentPriority": "Prioritate recente",
|
||||||
"Absolute": "Absolut"
|
"Absolute": "Absolut",
|
||||||
|
"Pending": "În așteptare",
|
||||||
|
"YesterdayAt": "Ieri la {time}",
|
||||||
|
"YesCancel": "Da, Anulează",
|
||||||
|
"CalendarOptions": "Setări Calendar",
|
||||||
|
"Yesterday": "Ieri",
|
||||||
|
"Any": "Oricare",
|
||||||
|
"AddCondition": "Adaugă Condiție",
|
||||||
|
"AddImportList": "Adaugă Lista de Import",
|
||||||
|
"Week": "Săptămână",
|
||||||
|
"WhatsNew": "Ce-i nou?",
|
||||||
|
"Warning": "Avertisment",
|
||||||
|
"AddAutoTag": "Adaugă Etichetă Automată"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2162,5 +2162,8 @@
|
|||||||
"DownloadClientItemErrorMessage": "{clientName} сообщает об ошибке: {message}",
|
"DownloadClientItemErrorMessage": "{clientName} сообщает об ошибке: {message}",
|
||||||
"AutoTaggingSpecificationNetwork": "Сеть(и)",
|
"AutoTaggingSpecificationNetwork": "Сеть(и)",
|
||||||
"NotificationsAppriseSettingsIncludePoster": "Добавить постер",
|
"NotificationsAppriseSettingsIncludePoster": "Добавить постер",
|
||||||
"NotificationsAppriseSettingsIncludePosterHelpText": "Добавлять постер в сообщение"
|
"NotificationsAppriseSettingsIncludePosterHelpText": "Добавлять постер в сообщение",
|
||||||
|
"EpisodeMonitoring": "Отслеживание эпизода",
|
||||||
|
"MonitorEpisodes": "Отслеживать эпизоды",
|
||||||
|
"MonitorEpisodesModalInfo": "Эта настройка влияет только на отслеживание эпизодов или сезонов внутри сериала. Выбор ничего приведёт к остановке отслеживания сериала"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1424,7 +1424,7 @@
|
|||||||
"Reload": "Tekrar yükle",
|
"Reload": "Tekrar yükle",
|
||||||
"RemoveFailedDownloadsHelpText": "Başarısız indirmeleri indirme istemcisi geçmişinden kaldırın",
|
"RemoveFailedDownloadsHelpText": "Başarısız indirmeleri indirme istemcisi geçmişinden kaldırın",
|
||||||
"RemoveFromBlocklist": "Kara listeden kaldır",
|
"RemoveFromBlocklist": "Kara listeden kaldır",
|
||||||
"RemoveRootFolder": "Kök klasörü kaldır",
|
"RemoveRootFolder": "Kök Klasörü Kaldır",
|
||||||
"RemoveSelected": "Seçilenleri Kaldır",
|
"RemoveSelected": "Seçilenleri Kaldır",
|
||||||
"RemovingTag": "Etiket kaldırılıyor",
|
"RemovingTag": "Etiket kaldırılıyor",
|
||||||
"RenameFiles": "Yeniden Adlandır",
|
"RenameFiles": "Yeniden Adlandır",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using NLog;
|
|||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.Download;
|
using NzbDrone.Core.Download;
|
||||||
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
@@ -31,6 +32,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||||||
private readonly IImportApprovedEpisodes _importApprovedEpisodes;
|
private readonly IImportApprovedEpisodes _importApprovedEpisodes;
|
||||||
private readonly IDetectSample _detectSample;
|
private readonly IDetectSample _detectSample;
|
||||||
private readonly IRuntimeInfo _runtimeInfo;
|
private readonly IRuntimeInfo _runtimeInfo;
|
||||||
|
private readonly IConfigService _configService;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public DownloadedEpisodesImportService(IDiskProvider diskProvider,
|
public DownloadedEpisodesImportService(IDiskProvider diskProvider,
|
||||||
@@ -41,6 +43,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||||||
IImportApprovedEpisodes importApprovedEpisodes,
|
IImportApprovedEpisodes importApprovedEpisodes,
|
||||||
IDetectSample detectSample,
|
IDetectSample detectSample,
|
||||||
IRuntimeInfo runtimeInfo,
|
IRuntimeInfo runtimeInfo,
|
||||||
|
IConfigService configService,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
{
|
{
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
@@ -51,6 +54,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||||||
_importApprovedEpisodes = importApprovedEpisodes;
|
_importApprovedEpisodes = importApprovedEpisodes;
|
||||||
_detectSample = detectSample;
|
_detectSample = detectSample;
|
||||||
_runtimeInfo = runtimeInfo;
|
_runtimeInfo = runtimeInfo;
|
||||||
|
_configService = configService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,6 +284,27 @@ namespace NzbDrone.Core.MediaFiles
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_configService.UserRejectedExtensions is not null)
|
||||||
|
{
|
||||||
|
var userRejectedExtensions = _configService.UserRejectedExtensions.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(e => e.Trim(' ', '.')
|
||||||
|
.Insert(0, "."))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (userRejectedExtensions.Contains(extension))
|
||||||
|
{
|
||||||
|
return new List<ImportResult>
|
||||||
|
{
|
||||||
|
new ImportResult(new ImportDecision(new LocalEpisode
|
||||||
|
{
|
||||||
|
Path = fileInfo.FullName
|
||||||
|
},
|
||||||
|
new ImportRejection(ImportRejectionReason.UserRejectedExtension, $"Caution: Found file with user defined rejected extension: '{extension}'")),
|
||||||
|
$"Caution: Found executable file with user defined rejected extension: '{extension}'")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (extension.IsNullOrWhiteSpace() || !MediaFileExtensions.Extensions.Contains(extension))
|
if (extension.IsNullOrWhiteSpace() || !MediaFileExtensions.Extensions.Contains(extension))
|
||||||
{
|
{
|
||||||
_logger.Debug("[{0}] has an unsupported extension: '{1}'", fileInfo.FullName, extension);
|
_logger.Debug("[{0}] has an unsupported extension: '{1}'", fileInfo.FullName, extension);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ public enum ImportRejectionReason
|
|||||||
UnknownSeries,
|
UnknownSeries,
|
||||||
DangerousFile,
|
DangerousFile,
|
||||||
ExecutableFile,
|
ExecutableFile,
|
||||||
|
UserRejectedExtension,
|
||||||
ArchiveFile,
|
ArchiveFile,
|
||||||
SeriesFolder,
|
SeriesFolder,
|
||||||
InvalidFilePath,
|
InvalidFilePath,
|
||||||
|
|||||||
@@ -483,6 +483,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
|||||||
|
|
||||||
var imported = new List<ImportResult>();
|
var imported = new List<ImportResult>();
|
||||||
var importedTrackedDownload = new List<ManuallyImportedFile>();
|
var importedTrackedDownload = new List<ManuallyImportedFile>();
|
||||||
|
var importedUntrackedDownload = new List<ImportResult>();
|
||||||
|
|
||||||
for (var i = 0; i < message.Files.Count; i++)
|
for (var i = 0; i < message.Files.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -545,7 +546,10 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
|||||||
|
|
||||||
if (trackedDownload == null)
|
if (trackedDownload == null)
|
||||||
{
|
{
|
||||||
imported.AddRange(_importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode));
|
var importResult = _importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode);
|
||||||
|
|
||||||
|
imported.AddRange(importResult);
|
||||||
|
importedUntrackedDownload.AddRange(importResult);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -566,7 +570,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
|||||||
_logger.ProgressTrace("Manually imported {0} files", imported.Count);
|
_logger.ProgressTrace("Manually imported {0} files", imported.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
var untrackedImports = imported.Where(i => i.Result == ImportResultType.Imported && importedTrackedDownload.FirstOrDefault(t => t.ImportResult != i) == null).ToList();
|
var untrackedImports = importedUntrackedDownload.Where(i => i.Result == ImportResultType.Imported).ToList();
|
||||||
|
|
||||||
if (untrackedImports.Any())
|
if (untrackedImports.Any())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace NzbDrone.Core.MediaFiles
|
namespace NzbDrone.Core.MediaFiles
|
||||||
{
|
{
|
||||||
internal static class FileExtensions
|
public static class FileExtensions
|
||||||
{
|
{
|
||||||
private static List<string> _archiveExtensions = new List<string>
|
private static List<string> _archiveExtensions = new List<string>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||||||
public interface IMediaFileRepository : IBasicRepository<EpisodeFile>
|
public interface IMediaFileRepository : IBasicRepository<EpisodeFile>
|
||||||
{
|
{
|
||||||
List<EpisodeFile> GetFilesBySeries(int seriesId);
|
List<EpisodeFile> GetFilesBySeries(int seriesId);
|
||||||
|
List<EpisodeFile> GetFilesBySeriesIds(List<int> seriesIds);
|
||||||
List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber);
|
List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber);
|
||||||
List<EpisodeFile> GetFilesWithoutMediaInfo();
|
List<EpisodeFile> GetFilesWithoutMediaInfo();
|
||||||
List<EpisodeFile> GetFilesWithRelativePath(int seriesId, string relativePath);
|
List<EpisodeFile> GetFilesWithRelativePath(int seriesId, string relativePath);
|
||||||
@@ -26,6 +27,11 @@ namespace NzbDrone.Core.MediaFiles
|
|||||||
return Query(c => c.SeriesId == seriesId).ToList();
|
return Query(c => c.SeriesId == seriesId).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<EpisodeFile> GetFilesBySeriesIds(List<int> seriesIds)
|
||||||
|
{
|
||||||
|
return Query(c => seriesIds.Contains(c.SeriesId)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
public List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber)
|
public List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber)
|
||||||
{
|
{
|
||||||
return Query(c => c.SeriesId == seriesId && c.SeasonNumber == seasonNumber).ToList();
|
return Query(c => c.SeriesId == seriesId && c.SeasonNumber == seasonNumber).ToList();
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||||||
void Update(List<EpisodeFile> episodeFiles);
|
void Update(List<EpisodeFile> episodeFiles);
|
||||||
void Delete(EpisodeFile episodeFile, DeleteMediaFileReason reason);
|
void Delete(EpisodeFile episodeFile, DeleteMediaFileReason reason);
|
||||||
List<EpisodeFile> GetFilesBySeries(int seriesId);
|
List<EpisodeFile> GetFilesBySeries(int seriesId);
|
||||||
|
List<EpisodeFile> GetFilesBySeriesIds(List<int> seriesIds);
|
||||||
List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber);
|
List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber);
|
||||||
List<EpisodeFile> GetFiles(IEnumerable<int> ids);
|
List<EpisodeFile> GetFiles(IEnumerable<int> ids);
|
||||||
List<EpisodeFile> GetFilesWithoutMediaInfo();
|
List<EpisodeFile> GetFilesWithoutMediaInfo();
|
||||||
@@ -71,6 +72,11 @@ namespace NzbDrone.Core.MediaFiles
|
|||||||
return _mediaFileRepository.GetFilesBySeries(seriesId);
|
return _mediaFileRepository.GetFilesBySeries(seriesId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<EpisodeFile> GetFilesBySeriesIds(List<int> seriesIds)
|
||||||
|
{
|
||||||
|
return _mediaFileRepository.GetFilesBySeriesIds(seriesIds);
|
||||||
|
}
|
||||||
|
|
||||||
public List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber)
|
public List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber)
|
||||||
{
|
{
|
||||||
return _mediaFileRepository.GetFilesBySeason(seriesId, seasonNumber);
|
return _mediaFileRepository.GetFilesBySeason(seriesId, seasonNumber);
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
|||||||
|
|
||||||
if (videoFormat == "mpeg4" || videoFormat.Contains("msmpeg4"))
|
if (videoFormat == "mpeg4" || videoFormat.Contains("msmpeg4"))
|
||||||
{
|
{
|
||||||
if (videoCodecID == "XVID")
|
if (videoCodecID.ToUpperInvariant() == "XVID")
|
||||||
{
|
{
|
||||||
return "XviD";
|
return "XviD";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||||||
{
|
{
|
||||||
List<RenameEpisodeFilePreview> GetRenamePreviews(int seriesId);
|
List<RenameEpisodeFilePreview> GetRenamePreviews(int seriesId);
|
||||||
List<RenameEpisodeFilePreview> GetRenamePreviews(int seriesId, int seasonNumber);
|
List<RenameEpisodeFilePreview> GetRenamePreviews(int seriesId, int seasonNumber);
|
||||||
|
List<RenameEpisodeFilePreview> GetRenamePreviews(List<int> seriesIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RenameEpisodeFileService : IRenameEpisodeFileService,
|
public class RenameEpisodeFileService : IRenameEpisodeFileService,
|
||||||
@@ -75,6 +76,25 @@ namespace NzbDrone.Core.MediaFiles
|
|||||||
.OrderByDescending(e => e.EpisodeNumbers.First()).ToList();
|
.OrderByDescending(e => e.EpisodeNumbers.First()).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<RenameEpisodeFilePreview> GetRenamePreviews(List<int> seriesIds)
|
||||||
|
{
|
||||||
|
var seriesList = _seriesService.GetSeries(seriesIds);
|
||||||
|
var episodesList = _episodeService.GetEpisodesBySeries(seriesIds).ToLookup(e => e.SeriesId);
|
||||||
|
var filesList = _mediaFileService.GetFilesBySeriesIds(seriesIds).ToLookup(f => f.SeriesId);
|
||||||
|
|
||||||
|
return seriesList.SelectMany(series =>
|
||||||
|
{
|
||||||
|
var episodes = episodesList[series.Id].ToList();
|
||||||
|
var files = filesList[series.Id].ToList();
|
||||||
|
|
||||||
|
return GetPreviews(series, episodes, files);
|
||||||
|
})
|
||||||
|
.OrderByDescending(e => e.SeriesId)
|
||||||
|
.ThenByDescending(e => e.SeasonNumber)
|
||||||
|
.ThenByDescending(e => e.EpisodeNumbers.First())
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
private IEnumerable<RenameEpisodeFilePreview> GetPreviews(Series series, List<Episode> episodes, List<EpisodeFile> files)
|
private IEnumerable<RenameEpisodeFilePreview> GetPreviews(Series series, List<Episode> episodes, List<EpisodeFile> files)
|
||||||
{
|
{
|
||||||
foreach (var f in files)
|
foreach (var f in files)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace NzbDrone.Core.MediaFiles.TorrentInfo
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return Torrent.Load(fileContents).InfoHash.ToHex();
|
return Torrent.Load(fileContents).InfoHashes.V1OrV2.ToHex();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Http;
|
using NzbDrone.Common.Http;
|
||||||
using NzbDrone.Common.Serializer;
|
using NzbDrone.Common.Serializer;
|
||||||
@@ -20,6 +19,16 @@ namespace NzbDrone.Core.Notifications.Emby
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void TestConnection(MediaBrowserSettings settings)
|
||||||
|
{
|
||||||
|
var path = "/System/Configuration";
|
||||||
|
var request = BuildRequest(path, settings);
|
||||||
|
request.Headers.Add("X-MediaBrowser-Token", settings.ApiKey);
|
||||||
|
|
||||||
|
var response = _httpClient.Get(request);
|
||||||
|
_logger.Trace("Response: {0}", response.Content);
|
||||||
|
}
|
||||||
|
|
||||||
public void Notify(MediaBrowserSettings settings, string title, string message)
|
public void Notify(MediaBrowserSettings settings, string title, string message)
|
||||||
{
|
{
|
||||||
var path = "/Notifications/Admin";
|
var path = "/Notifications/Admin";
|
||||||
@@ -34,21 +43,7 @@ namespace NzbDrone.Core.Notifications.Emby
|
|||||||
ImageUrl = "https://raw.github.com/Sonarr/Sonarr/develop/Logo/64.png"
|
ImageUrl = "https://raw.github.com/Sonarr/Sonarr/develop/Logo/64.png"
|
||||||
}.ToJson());
|
}.ToJson());
|
||||||
|
|
||||||
try
|
ProcessRequest(request, settings);
|
||||||
{
|
|
||||||
ProcessRequest(request, settings);
|
|
||||||
}
|
|
||||||
catch (HttpException e)
|
|
||||||
{
|
|
||||||
if (e.Response.StatusCode == HttpStatusCode.NotFound)
|
|
||||||
{
|
|
||||||
_logger.Warn("Unable to send notification to Emby. If you're using Jellyfin disable 'Send Notifications'");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public HashSet<string> GetPaths(MediaBrowserSettings settings, Series series)
|
public HashSet<string> GetPaths(MediaBrowserSettings settings, Series series)
|
||||||
|
|||||||
@@ -62,8 +62,7 @@ namespace NzbDrone.Core.Notifications.Emby
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.Debug("Testing connection to Emby/Jellyfin : {0}", settings.Address);
|
_logger.Debug("Testing connection to Emby/Jellyfin : {0}", settings.Address);
|
||||||
|
_proxy.TestConnection(settings);
|
||||||
Notify(settings, "Test from Sonarr", "Success! MediaBrowser has been successfully configured!");
|
|
||||||
}
|
}
|
||||||
catch (HttpException ex)
|
catch (HttpException ex)
|
||||||
{
|
{
|
||||||
@@ -71,6 +70,11 @@ namespace NzbDrone.Core.Notifications.Emby
|
|||||||
{
|
{
|
||||||
return new ValidationFailure("ApiKey", _localizationService.GetLocalizedString("NotificationsValidationInvalidApiKey"));
|
return new ValidationFailure("ApiKey", _localizationService.GetLocalizedString("NotificationsValidationInvalidApiKey"));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Trace(ex, "Error when connecting to Emby/Jellyfin");
|
||||||
|
return new ValidationFailure("Host", _localizationService.GetLocalizedString("NotificationsValidationUnableToSendTestMessage", new Dictionary<string, object> { { "exceptionMessage", ex.Message } }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -78,6 +82,25 @@ namespace NzbDrone.Core.Notifications.Emby
|
|||||||
return new ValidationFailure("Host", _localizationService.GetLocalizedString("NotificationsValidationUnableToSendTestMessage", new Dictionary<string, object> { { "exceptionMessage", ex.Message } }));
|
return new ValidationFailure("Host", _localizationService.GetLocalizedString("NotificationsValidationUnableToSendTestMessage", new Dictionary<string, object> { { "exceptionMessage", ex.Message } }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (settings.Notify)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Notify(settings, "Test from Sonarr", "Success! MediaBrowser has been successfully configured!");
|
||||||
|
}
|
||||||
|
catch (HttpException ex)
|
||||||
|
{
|
||||||
|
if (ex.Response.StatusCode == HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
return new ValidationFailure("Notify", "Unable to send notification to Emby. If you're using Jellyfin disable 'Send Notifications'");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -243,6 +243,10 @@ namespace NzbDrone.Core.Parser
|
|||||||
new Regex(@"^(?<title>.+?)(?:\W+S(?<season>(?<!\d+)(?:\d{1,2})(?!\d+))\W+(?:(?:(?:Part|Vol)\W?|(?<!\d+\W+)e|p)(?<seasonpart>\d{1,2}(?!\d+)))+)",
|
new Regex(@"^(?<title>.+?)(?:\W+S(?<season>(?<!\d+)(?:\d{1,2})(?!\d+))\W+(?:(?:(?:Part|Vol)\W?|(?<!\d+\W+)e|p)(?<seasonpart>\d{1,2}(?!\d+)))+)",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
|
// Anime - 4 digit absolute episode number in batch (1017-1088) or 1017-1088
|
||||||
|
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)(?<title>.+?)[-_. ]+?\(?(?<absoluteepisode>\d{4}(\.\d{1,2})?(?!\d+))[-](?<absoluteepisode>\d{4}(\.\d{1,2})?(?!\d+))\)?",
|
||||||
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
|
||||||
// Anime - 4 digit absolute episode number
|
// Anime - 4 digit absolute episode number
|
||||||
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)(?<title>.+?)[-_. ]+?(?<absoluteepisode>\d{4}(\.\d{1,2})?(?!\d+))",
|
new Regex(@"^(?:\[(?<subgroup>.+?)\][-_. ]?)(?<title>.+?)[-_. ]+?(?<absoluteepisode>\d{4}(\.\d{1,2})?(?!\d+))",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
@@ -539,7 +543,7 @@ namespace NzbDrone.Core.Parser
|
|||||||
private static readonly Regex FileExtensionRegex = new Regex(@"\.[a-z0-9]{2,4}$",
|
private static readonly Regex FileExtensionRegex = new Regex(@"\.[a-z0-9]{2,4}$",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
|
|
||||||
private static readonly RegexReplace SimpleTitleRegex = new RegexReplace(@"(?:(480|540|576|720|1080|2160)[ip]|[xh][\W_]?26[45]|DD\W?5\W1|[<>?*]|848x480|1280x720|1920x1080|3840x2160|4096x2160|(?<![a-f0-9])(8|10)(b(?![a-z0-9])|bit)|10-bit)\s*?",
|
private static readonly RegexReplace SimpleTitleRegex = new RegexReplace(@"(?:(480|540|576|720|1080|2160)[ip]|[xh][\W_]?26[45]|DD\W?5\W1|[<>?*]|848x480|1280x720|1920x1080|3840x2160|4096x2160|(?<![a-f0-9])(8|10)[ -]?(b(?![a-z0-9])|bit))\s*?",
|
||||||
string.Empty,
|
string.Empty,
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
|
|
||||||
|
|||||||
@@ -24,10 +24,10 @@
|
|||||||
<PackageReference Include="Servarr.FluentMigrator.Runner.SQLite" Version="3.3.2.9" />
|
<PackageReference Include="Servarr.FluentMigrator.Runner.SQLite" Version="3.3.2.9" />
|
||||||
<PackageReference Include="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" />
|
<PackageReference Include="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" />
|
||||||
<PackageReference Include="FluentValidation" Version="9.5.4" />
|
<PackageReference Include="FluentValidation" Version="9.5.4" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.7" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="NLog" Version="5.3.4" />
|
<PackageReference Include="NLog" Version="5.3.4" />
|
||||||
<PackageReference Include="MonoTorrent" Version="2.0.7" />
|
<PackageReference Include="MonoTorrent" Version="3.0.2" />
|
||||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||||
<PackageReference Include="System.Text.Json" Version="8.0.5" />
|
<PackageReference Include="System.Text.Json" Version="8.0.5" />
|
||||||
<PackageReference Include="Npgsql" Version="9.0.3" />
|
<PackageReference Include="Npgsql" Version="9.0.3" />
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ namespace NzbDrone.Core.Tv
|
|||||||
List<Episode> FindEpisodesBySceneNumbering(int seriesId, int sceneAbsoluteEpisodeNumber);
|
List<Episode> FindEpisodesBySceneNumbering(int seriesId, int sceneAbsoluteEpisodeNumber);
|
||||||
Episode FindEpisode(int seriesId, string date, int? part);
|
Episode FindEpisode(int seriesId, string date, int? part);
|
||||||
List<Episode> GetEpisodeBySeries(int seriesId);
|
List<Episode> GetEpisodeBySeries(int seriesId);
|
||||||
|
List<Episode> GetEpisodesBySeries(List<int> seriesIds);
|
||||||
List<Episode> GetEpisodesBySeason(int seriesId, int seasonNumber);
|
List<Episode> GetEpisodesBySeason(int seriesId, int seasonNumber);
|
||||||
List<Episode> GetEpisodesBySceneSeason(int seriesId, int sceneSeasonNumber);
|
List<Episode> GetEpisodesBySceneSeason(int seriesId, int sceneSeasonNumber);
|
||||||
List<Episode> EpisodesWithFiles(int seriesId);
|
List<Episode> EpisodesWithFiles(int seriesId);
|
||||||
@@ -99,6 +100,11 @@ namespace NzbDrone.Core.Tv
|
|||||||
return _episodeRepository.GetEpisodes(seriesId).ToList();
|
return _episodeRepository.GetEpisodes(seriesId).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Episode> GetEpisodesBySeries(List<int> seriesIds)
|
||||||
|
{
|
||||||
|
return _episodeRepository.GetEpisodesBySeriesIds(seriesIds).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
public List<Episode> GetEpisodesBySeason(int seriesId, int seasonNumber)
|
public List<Episode> GetEpisodesBySeason(int seriesId, int seasonNumber)
|
||||||
{
|
{
|
||||||
return _episodeRepository.GetEpisodes(seriesId, seasonNumber);
|
return _episodeRepository.GetEpisodes(seriesId, seasonNumber);
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ namespace NzbDrone.Host
|
|||||||
b.ClearProviders();
|
b.ClearProviders();
|
||||||
b.SetMinimumLevel(LogLevel.Trace);
|
b.SetMinimumLevel(LogLevel.Trace);
|
||||||
b.AddFilter("Microsoft.AspNetCore", LogLevel.Warning);
|
b.AddFilter("Microsoft.AspNetCore", LogLevel.Warning);
|
||||||
b.AddFilter("Sonarr.Http.Authentication", LogLevel.Information);
|
b.AddFilter("Sonarr.Http.Authentication.ApiKeyAuthenticationHandler", LogLevel.Information);
|
||||||
b.AddFilter("Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager", LogLevel.Error);
|
b.AddFilter("Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager", LogLevel.Error);
|
||||||
b.AddNLog();
|
b.AddNLog();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.Validation;
|
using NzbDrone.Core.Validation;
|
||||||
using NzbDrone.Core.Validation.Paths;
|
using NzbDrone.Core.Validation.Paths;
|
||||||
using Sonarr.Http;
|
using Sonarr.Http;
|
||||||
@@ -37,6 +40,28 @@ namespace Sonarr.Api.V3.Config
|
|||||||
SharedValidator.RuleFor(c => c.ScriptImportPath).IsValidPath().When(c => c.UseScriptImport);
|
SharedValidator.RuleFor(c => c.ScriptImportPath).IsValidPath().When(c => c.UseScriptImport);
|
||||||
|
|
||||||
SharedValidator.RuleFor(c => c.MinimumFreeSpaceWhenImporting).GreaterThanOrEqualTo(100);
|
SharedValidator.RuleFor(c => c.MinimumFreeSpaceWhenImporting).GreaterThanOrEqualTo(100);
|
||||||
|
|
||||||
|
SharedValidator.RuleFor(c => c.UserRejectedExtensions).Custom((extensions, context) =>
|
||||||
|
{
|
||||||
|
var userRejectedExtensions = extensions.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(e => e.Trim(' ', '.')
|
||||||
|
.Insert(0, "."))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var matchingArchiveExtensions = userRejectedExtensions.Where(ext => FileExtensions.ArchiveExtensions.Contains(ext)).ToList();
|
||||||
|
|
||||||
|
if (matchingArchiveExtensions.Count > 0)
|
||||||
|
{
|
||||||
|
context.AddFailure($"Rejected extensions may not include valid archive extensions: {string.Join(", ", matchingArchiveExtensions)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var matchingMediaFileExtensions = userRejectedExtensions.Where(ext => MediaFileExtensions.Extensions.Contains(ext)).ToList();
|
||||||
|
|
||||||
|
if (matchingMediaFileExtensions.Count > 0)
|
||||||
|
{
|
||||||
|
context.AddFailure($"Rejected extensions may not include valid media file extensions: {string.Join(", ", matchingMediaFileExtensions)}");
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override MediaManagementConfigResource ToResource(IConfigService model)
|
protected override MediaManagementConfigResource ToResource(IConfigService model)
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ namespace Sonarr.Api.V3.Config
|
|||||||
public bool ImportExtraFiles { get; set; }
|
public bool ImportExtraFiles { get; set; }
|
||||||
public string ExtraFileExtensions { get; set; }
|
public string ExtraFileExtensions { get; set; }
|
||||||
public bool EnableMediaInfo { get; set; }
|
public bool EnableMediaInfo { get; set; }
|
||||||
|
public string UserRejectedExtensions { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class MediaManagementConfigResourceMapper
|
public static class MediaManagementConfigResourceMapper
|
||||||
@@ -59,7 +60,8 @@ namespace Sonarr.Api.V3.Config
|
|||||||
ScriptImportPath = model.ScriptImportPath,
|
ScriptImportPath = model.ScriptImportPath,
|
||||||
ImportExtraFiles = model.ImportExtraFiles,
|
ImportExtraFiles = model.ImportExtraFiles,
|
||||||
ExtraFileExtensions = model.ExtraFileExtensions,
|
ExtraFileExtensions = model.ExtraFileExtensions,
|
||||||
EnableMediaInfo = model.EnableMediaInfo
|
EnableMediaInfo = model.EnableMediaInfo,
|
||||||
|
UserRejectedExtensions = model.UserRejectedExtensions
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using Sonarr.Http;
|
using Sonarr.Http;
|
||||||
|
using Sonarr.Http.REST;
|
||||||
|
|
||||||
namespace Sonarr.Api.V3.Episodes
|
namespace Sonarr.Api.V3.Episodes
|
||||||
{
|
{
|
||||||
@@ -26,5 +28,22 @@ namespace Sonarr.Api.V3.Episodes
|
|||||||
|
|
||||||
return _renameEpisodeFileService.GetRenamePreviews(seriesId).ToResource();
|
return _renameEpisodeFileService.GetRenamePreviews(seriesId).ToResource();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("bulk")]
|
||||||
|
[Produces("application/json")]
|
||||||
|
public List<RenameEpisodeResource> GetEpisodes([FromQuery] List<int> seriesIds)
|
||||||
|
{
|
||||||
|
if (seriesIds is { Count: 0 })
|
||||||
|
{
|
||||||
|
throw new BadRequestException("seriesIds must be provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seriesIds.Any(seriesId => seriesId <= 0))
|
||||||
|
{
|
||||||
|
throw new BadRequestException("seriesIds must be positive integers");
|
||||||
|
}
|
||||||
|
|
||||||
|
return _renameEpisodeFileService.GetRenamePreviews(seriesIds).ToResource();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using NzbDrone.Core.Qualities;
|
|||||||
using Sonarr.Api.V3.CustomFormats;
|
using Sonarr.Api.V3.CustomFormats;
|
||||||
using Sonarr.Api.V3.Episodes;
|
using Sonarr.Api.V3.Episodes;
|
||||||
using Sonarr.Http;
|
using Sonarr.Http;
|
||||||
|
using Sonarr.Http.REST;
|
||||||
|
|
||||||
namespace Sonarr.Api.V3.ManualImport
|
namespace Sonarr.Api.V3.ManualImport
|
||||||
{
|
{
|
||||||
@@ -37,6 +38,11 @@ namespace Sonarr.Api.V3.ManualImport
|
|||||||
[Consumes("application/json")]
|
[Consumes("application/json")]
|
||||||
public object ReprocessItems([FromBody] List<ManualImportReprocessResource> items)
|
public object ReprocessItems([FromBody] List<ManualImportReprocessResource> items)
|
||||||
{
|
{
|
||||||
|
if (items is { Count: 0 })
|
||||||
|
{
|
||||||
|
throw new BadRequestException("items must be provided");
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
var processedItem = _manualImportService.ReprocessItem(item.Path, item.DownloadId, item.SeriesId, item.SeasonNumber, item.EpisodeIds ?? new List<int>(), item.ReleaseGroup, item.Quality, item.Languages, item.IndexerFlags, item.ReleaseType);
|
var processedItem = _manualImportService.ReprocessItem(item.Path, item.DownloadId, item.SeriesId, item.SeasonNumber, item.EpisodeIds ?? new List<int>(), item.ReleaseGroup, item.Quality, item.Languages, item.IndexerFlags, item.ReleaseType);
|
||||||
|
|||||||
Reference in New Issue
Block a user