mirror of
https://github.com/Sonarr/Sonarr.git
synced 2026-04-17 21:26:13 -04:00
Compare commits
42 Commits
v4.0.5.176
...
v4.0.8.187
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1aaa9a14bc | ||
|
|
c6c37a408a | ||
|
|
ae4a97b4ae | ||
|
|
3afae968eb | ||
|
|
c01abbf3b5 | ||
|
|
f5ccf98162 | ||
|
|
6afd3bd344 | ||
|
|
acaf5cd353 | ||
|
|
e97e5bfe8f | ||
|
|
678872b879 | ||
|
|
10e9735c1c | ||
|
|
293a1bc618 | ||
|
|
0c883f7886 | ||
|
|
46c7de379c | ||
|
|
a83b521766 | ||
|
|
1d06e40acb | ||
|
|
bfcdc89f6a | ||
|
|
67943edfbc | ||
|
|
04f8595498 | ||
|
|
81ac73299a | ||
|
|
a779a5fad2 | ||
|
|
bfe6a740fa | ||
|
|
c9ea40b874 | ||
|
|
4ee0ae1418 | ||
|
|
ac1da45ecd | ||
|
|
5c327d5be3 | ||
|
|
55c1ce2e3d | ||
|
|
fd7f0ea973 | ||
|
|
d5dff8e8d6 | ||
|
|
8099ba10af | ||
|
|
143ccb1e2a | ||
|
|
29480d9544 | ||
|
|
6de536a7ad | ||
|
|
bce848facf | ||
|
|
ea4fe392a0 | ||
|
|
45fe585944 | ||
|
|
a0d2933134 | ||
|
|
4c622fd412 | ||
|
|
fb060730c7 | ||
|
|
6d5ff9c4d6 | ||
|
|
63bed3e670 | ||
|
|
e684c10432 |
242
.github/workflows/build.yml
vendored
242
.github/workflows/build.yml
vendored
@@ -6,13 +6,13 @@ on:
|
||||
- develop
|
||||
- main
|
||||
paths-ignore:
|
||||
- 'src/Sonarr.Api.*/openapi.json'
|
||||
- "src/Sonarr.Api.*/openapi.json"
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
paths-ignore:
|
||||
- 'src/NzbDrone.Core/Localization/Core/**'
|
||||
- 'src/Sonarr.Api.*/openapi.json'
|
||||
- "src/NzbDrone.Core/Localization/Core/**"
|
||||
- "src/Sonarr.Api.*/openapi.json"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
@@ -22,7 +22,7 @@ env:
|
||||
FRAMEWORK: net6.0
|
||||
RAW_BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
||||
SONARR_MAJOR_VERSION: 4
|
||||
VERSION: 4.0.5
|
||||
VERSION: 4.0.8
|
||||
|
||||
jobs:
|
||||
backend:
|
||||
@@ -32,105 +32,105 @@ jobs:
|
||||
major_version: ${{ steps.variables.outputs.major_version }}
|
||||
version: ${{ steps.variables.outputs.version }}
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
|
||||
- name: Setup Environment Variables
|
||||
id: variables
|
||||
shell: bash
|
||||
run: |
|
||||
# Add 800 to the build number because GitHub won't let us pick an arbitrary starting point
|
||||
SONARR_VERSION="${{ env.VERSION }}.$((${{ github.run_number }}+800))"
|
||||
DOTNET_VERSION=$(jq -r '.sdk.version' global.json)
|
||||
- name: Setup Environment Variables
|
||||
id: variables
|
||||
shell: bash
|
||||
run: |
|
||||
# Add 800 to the build number because GitHub won't let us pick an arbitrary starting point
|
||||
SONARR_VERSION="${{ env.VERSION }}.$((${{ github.run_number }}+800))"
|
||||
DOTNET_VERSION=$(jq -r '.sdk.version' global.json)
|
||||
|
||||
echo "SDK_PATH=${{ env.DOTNET_ROOT }}/sdk/${DOTNET_VERSION}" >> "$GITHUB_ENV"
|
||||
echo "SONARR_VERSION=$SONARR_VERSION" >> "$GITHUB_ENV"
|
||||
echo "BRANCH=${RAW_BRANCH_NAME//\//-}" >> "$GITHUB_ENV"
|
||||
echo "SDK_PATH=${{ env.DOTNET_ROOT }}/sdk/${DOTNET_VERSION}" >> "$GITHUB_ENV"
|
||||
echo "SONARR_VERSION=$SONARR_VERSION" >> "$GITHUB_ENV"
|
||||
echo "BRANCH=${RAW_BRANCH_NAME//\//-}" >> "$GITHUB_ENV"
|
||||
|
||||
echo "framework=${{ env.FRAMEWORK }}" >> "$GITHUB_OUTPUT"
|
||||
echo "major_version=${{ env.SONARR_MAJOR_VERSION }}" >> "$GITHUB_OUTPUT"
|
||||
echo "version=$SONARR_VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "framework=${{ env.FRAMEWORK }}" >> "$GITHUB_OUTPUT"
|
||||
echo "major_version=${{ env.SONARR_MAJOR_VERSION }}" >> "$GITHUB_OUTPUT"
|
||||
echo "version=$SONARR_VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Enable Extra Platforms In SDK
|
||||
shell: bash
|
||||
run: ./build.sh --enable-extra-platforms-in-sdk
|
||||
- name: Enable Extra Platforms In SDK
|
||||
shell: bash
|
||||
run: ./build.sh --enable-extra-platforms-in-sdk
|
||||
|
||||
- name: Build Backend
|
||||
shell: bash
|
||||
run: ./build.sh --backend --enable-extra-platforms --packages
|
||||
- name: Build Backend
|
||||
shell: bash
|
||||
run: ./build.sh --backend --enable-extra-platforms --packages
|
||||
|
||||
# Test Artifacts
|
||||
# Test Artifacts
|
||||
|
||||
- name: Publish win-x64 Test Artifact
|
||||
uses: ./.github/actions/publish-test-artifact
|
||||
with:
|
||||
framework: ${{ env.FRAMEWORK }}
|
||||
runtime: win-x64
|
||||
- name: Publish win-x64 Test Artifact
|
||||
uses: ./.github/actions/publish-test-artifact
|
||||
with:
|
||||
framework: ${{ env.FRAMEWORK }}
|
||||
runtime: win-x64
|
||||
|
||||
- name: Publish linux-x64 Test Artifact
|
||||
uses: ./.github/actions/publish-test-artifact
|
||||
with:
|
||||
framework: ${{ env.FRAMEWORK }}
|
||||
runtime: linux-x64
|
||||
- name: Publish linux-x64 Test Artifact
|
||||
uses: ./.github/actions/publish-test-artifact
|
||||
with:
|
||||
framework: ${{ env.FRAMEWORK }}
|
||||
runtime: linux-x64
|
||||
|
||||
- name: Publish osx-arm64 Test Artifact
|
||||
uses: ./.github/actions/publish-test-artifact
|
||||
with:
|
||||
framework: ${{ env.FRAMEWORK }}
|
||||
runtime: osx-arm64
|
||||
- name: Publish osx-arm64 Test Artifact
|
||||
uses: ./.github/actions/publish-test-artifact
|
||||
with:
|
||||
framework: ${{ env.FRAMEWORK }}
|
||||
runtime: osx-arm64
|
||||
|
||||
# Build Artifacts (grouped by OS)
|
||||
|
||||
- name: Publish FreeBSD Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build_freebsd
|
||||
path: _artifacts/freebsd-*/**/*
|
||||
- name: Publish Linux Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build_linux
|
||||
path: _artifacts/linux-*/**/*
|
||||
- name: Publish macOS Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build_macos
|
||||
path: _artifacts/osx-*/**/*
|
||||
- name: Publish Windows Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build_windows
|
||||
path: _artifacts/win-*/**/*
|
||||
# Build Artifacts (grouped by OS)
|
||||
|
||||
- name: Publish FreeBSD Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build_freebsd
|
||||
path: _artifacts/freebsd-*/**/*
|
||||
- name: Publish Linux Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build_linux
|
||||
path: _artifacts/linux-*/**/*
|
||||
- name: Publish macOS Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build_macos
|
||||
path: _artifacts/osx-*/**/*
|
||||
- name: Publish Windows Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build_windows
|
||||
path: _artifacts/win-*/**/*
|
||||
|
||||
frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Volta
|
||||
uses: volta-cli/action@v4
|
||||
- name: Volta
|
||||
uses: volta-cli/action@v4
|
||||
|
||||
- name: Yarn Install
|
||||
run: yarn install
|
||||
- name: Yarn Install
|
||||
run: yarn install
|
||||
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
|
||||
- name: Stylelint
|
||||
run: yarn stylelint -f github
|
||||
- name: Stylelint
|
||||
run: yarn stylelint -f github
|
||||
|
||||
- name: Build
|
||||
run: yarn build --env production
|
||||
- name: Build
|
||||
run: yarn build --env production
|
||||
|
||||
- name: Publish UI Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build_ui
|
||||
path: _output/UI/**/*
|
||||
- name: Publish UI Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build_ui
|
||||
path: _output/UI/**/*
|
||||
|
||||
unit_test:
|
||||
needs: backend
|
||||
@@ -150,32 +150,32 @@ jobs:
|
||||
filter: TestCategory!=ManualTest&TestCategory!=LINUX&TestCategory!=IntegrationTest&TestCategory!=AutomationTest
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test
|
||||
uses: ./.github/actions/test
|
||||
with:
|
||||
os: ${{ matrix.os }}
|
||||
artifact: ${{ matrix.artifact }}
|
||||
pattern: Sonarr.*.Test.dll
|
||||
filter: ${{ matrix.filter }}
|
||||
- name: Test
|
||||
uses: ./.github/actions/test
|
||||
with:
|
||||
os: ${{ matrix.os }}
|
||||
artifact: ${{ matrix.artifact }}
|
||||
pattern: Sonarr.*.Test.dll
|
||||
filter: ${{ matrix.filter }}
|
||||
|
||||
unit_test_postgres:
|
||||
needs: backend
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test
|
||||
uses: ./.github/actions/test
|
||||
with:
|
||||
os: ubuntu-latest
|
||||
artifact: tests-linux-x64
|
||||
pattern: Sonarr.*.Test.dll
|
||||
filter: TestCategory!=ManualTest&TestCategory!=WINDOWS&TestCategory!=IntegrationTest&TestCategory!=AutomationTest
|
||||
use_postgres: true
|
||||
- name: Test
|
||||
uses: ./.github/actions/test
|
||||
with:
|
||||
os: ubuntu-latest
|
||||
artifact: tests-linux-x64
|
||||
pattern: Sonarr.*.Test.dll
|
||||
filter: TestCategory!=ManualTest&TestCategory!=WINDOWS&TestCategory!=IntegrationTest&TestCategory!=AutomationTest
|
||||
use_postgres: true
|
||||
|
||||
integration_test:
|
||||
needs: backend
|
||||
@@ -201,19 +201,19 @@ jobs:
|
||||
binary_path: win-x64/${{ needs.backend.outputs.framework }}/Sonarr
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test
|
||||
uses: ./.github/actions/test
|
||||
with:
|
||||
os: ${{ matrix.os }}
|
||||
artifact: ${{ matrix.artifact }}
|
||||
pattern: Sonarr.*.Test.dll
|
||||
filter: ${{ matrix.filter }}
|
||||
integration_tests: true
|
||||
binary_artifact: ${{ matrix.binary_artifact }}
|
||||
binary_path: ${{ matrix.binary_path }}
|
||||
- name: Test
|
||||
uses: ./.github/actions/test
|
||||
with:
|
||||
os: ${{ matrix.os }}
|
||||
artifact: ${{ matrix.artifact }}
|
||||
pattern: Sonarr.*.Test.dll
|
||||
filter: ${{ matrix.filter }}
|
||||
integration_tests: true
|
||||
binary_artifact: ${{ matrix.binary_artifact }}
|
||||
binary_path: ${{ matrix.binary_path }}
|
||||
|
||||
deploy:
|
||||
if: ${{ github.ref_name == 'develop' || github.ref_name == 'main' }}
|
||||
@@ -228,7 +228,15 @@ jobs:
|
||||
|
||||
notify:
|
||||
name: Discord Notification
|
||||
needs: [backend, frontend, unit_test, unit_test_postgres, integration_test, deploy]
|
||||
needs:
|
||||
[
|
||||
backend,
|
||||
frontend,
|
||||
unit_test,
|
||||
unit_test_postgres,
|
||||
integration_test,
|
||||
deploy,
|
||||
]
|
||||
if: ${{ !cancelled() && (github.ref_name == 'develop' || github.ref_name == 'main') }}
|
||||
env:
|
||||
STATUS: ${{ contains(needs.*.result, 'failure') && 'failure' || 'success' }}
|
||||
@@ -239,10 +247,10 @@ jobs:
|
||||
uses: tsickert/discord-webhook@v6.0.0
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
username: 'GitHub Actions'
|
||||
avatar-url: 'https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png'
|
||||
username: "GitHub Actions"
|
||||
avatar-url: "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
|
||||
embed-title: "${{ github.workflow }}: ${{ env.STATUS == 'success' && 'Success' || 'Failure' }}"
|
||||
embed-url: 'https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}'
|
||||
embed-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
embed-description: |
|
||||
**Branch** ${{ github.ref }}
|
||||
**Build** ${{ needs.backend.outputs.version }}
|
||||
|
||||
@@ -77,7 +77,7 @@ class HistoryRow extends Component {
|
||||
onMarkAsFailedPress
|
||||
} = this.props;
|
||||
|
||||
if (!episode) {
|
||||
if (!series || !episode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -217,6 +217,7 @@ class Queue extends Component {
|
||||
>
|
||||
<TableOptionsModalWrapper
|
||||
columns={columns}
|
||||
maxPageSize={200}
|
||||
{...otherProps}
|
||||
optionsComponent={QueueOptionsConnector}
|
||||
>
|
||||
|
||||
@@ -70,6 +70,11 @@ function QueueStatus(props) {
|
||||
iconName = icons.DOWNLOADED;
|
||||
title = translate('Downloaded');
|
||||
|
||||
if (trackedDownloadState === 'importBlocked') {
|
||||
title += ` - ${translate('UnableToImportAutomatically')}`;
|
||||
iconKind = kinds.WARNING;
|
||||
}
|
||||
|
||||
if (trackedDownloadState === 'importPending') {
|
||||
title += ` - ${translate('WaitingToImport')}`;
|
||||
iconKind = kinds.PURPLE;
|
||||
|
||||
@@ -118,6 +118,7 @@ function RemoveQueueItemModal(props: RemoveQueueItemModalProps) {
|
||||
{
|
||||
key: 'blocklistAndSearch',
|
||||
value: translate('BlocklistAndSearch'),
|
||||
isDisabled: isPending,
|
||||
hint: multipleSelected
|
||||
? translate('BlocklistAndSearchMultipleHint')
|
||||
: translate('BlocklistAndSearchHint'),
|
||||
@@ -130,7 +131,7 @@ function RemoveQueueItemModal(props: RemoveQueueItemModalProps) {
|
||||
: translate('BlocklistOnlyHint'),
|
||||
},
|
||||
];
|
||||
}, [multipleSelected]);
|
||||
}, [isPending, multipleSelected]);
|
||||
|
||||
const handleRemovalMethodChange = useCallback(
|
||||
({ value }: { value: RemovalMethod }) => {
|
||||
|
||||
@@ -271,26 +271,32 @@ class EnhancedSelectInput extends Component {
|
||||
this.setState({ isOpen: !this.state.isOpen });
|
||||
};
|
||||
|
||||
onSelect = (value) => {
|
||||
if (Array.isArray(this.props.value)) {
|
||||
let newValue = null;
|
||||
const index = this.props.value.indexOf(value);
|
||||
onSelect = (newValue) => {
|
||||
const { name, value, values, onChange } = this.props;
|
||||
const additionalProperties = values.find((v) => v.key === newValue)?.additionalProperties;
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
let arrayValue = null;
|
||||
const index = value.indexOf(newValue);
|
||||
|
||||
if (index === -1) {
|
||||
newValue = this.props.values.map((v) => v.key).filter((v) => (v === value) || this.props.value.includes(v));
|
||||
arrayValue = values.map((v) => v.key).filter((v) => (v === newValue) || value.includes(v));
|
||||
} else {
|
||||
newValue = [...this.props.value];
|
||||
newValue.splice(index, 1);
|
||||
arrayValue = [...value];
|
||||
arrayValue.splice(index, 1);
|
||||
}
|
||||
this.props.onChange({
|
||||
name: this.props.name,
|
||||
value: newValue
|
||||
onChange({
|
||||
name,
|
||||
value: arrayValue,
|
||||
additionalProperties
|
||||
});
|
||||
} else {
|
||||
this.setState({ isOpen: false });
|
||||
|
||||
this.props.onChange({
|
||||
name: this.props.name,
|
||||
value
|
||||
onChange({
|
||||
name,
|
||||
value: newValue,
|
||||
additionalProperties
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -485,7 +491,7 @@ class EnhancedSelectInput extends Component {
|
||||
values.map((v, index) => {
|
||||
const hasParent = v.parentKey !== undefined;
|
||||
const depth = hasParent ? 1 : 0;
|
||||
const parentSelected = hasParent && value.includes(v.parentKey);
|
||||
const parentSelected = hasParent && Array.isArray(value) && value.includes(v.parentKey);
|
||||
return (
|
||||
<OptionComponent
|
||||
key={v.key}
|
||||
|
||||
@@ -9,7 +9,8 @@ import EnhancedSelectInput from './EnhancedSelectInput';
|
||||
const importantFieldNames = [
|
||||
'baseUrl',
|
||||
'apiPath',
|
||||
'apiKey'
|
||||
'apiKey',
|
||||
'authToken'
|
||||
];
|
||||
|
||||
function getProviderDataKey(providerData) {
|
||||
@@ -34,7 +35,9 @@ function getSelectOptions(items) {
|
||||
key: option.value,
|
||||
value: option.name,
|
||||
hint: option.hint,
|
||||
parentKey: option.parentValue
|
||||
parentKey: option.parentValue,
|
||||
isDisabled: option.isDisabled,
|
||||
additionalProperties: option.additionalProperties
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -147,7 +150,7 @@ EnhancedSelectInputConnector.propTypes = {
|
||||
provider: PropTypes.string.isRequired,
|
||||
providerData: PropTypes.object.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])).isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.string), PropTypes.arrayOf(PropTypes.number)]).isRequired,
|
||||
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
selectOptionsProviderAction: PropTypes.string,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
|
||||
@@ -88,6 +88,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
.purple {
|
||||
border-color: var(--purple);
|
||||
background-color: var(--purple);
|
||||
|
||||
&.outline {
|
||||
color: var(--purple);
|
||||
}
|
||||
}
|
||||
|
||||
/** Sizes **/
|
||||
|
||||
.small {
|
||||
|
||||
1
frontend/src/Components/Label.css.d.ts
vendored
1
frontend/src/Components/Label.css.d.ts
vendored
@@ -11,6 +11,7 @@ interface CssExports {
|
||||
'medium': string;
|
||||
'outline': string;
|
||||
'primary': string;
|
||||
'purple': string;
|
||||
'small': string;
|
||||
'success': string;
|
||||
'warning': string;
|
||||
|
||||
@@ -49,11 +49,12 @@ class TableOptionsModal extends Component {
|
||||
|
||||
onPageSizeChange = ({ value }) => {
|
||||
let pageSizeError = null;
|
||||
const maxPageSize = this.props.maxPageSize ?? 250;
|
||||
|
||||
if (value < 5) {
|
||||
pageSizeError = translate('TablePageSizeMinimum', { minimumValue: '5' });
|
||||
} else if (value > 250) {
|
||||
pageSizeError = translate('TablePageSizeMaximum', { maximumValue: '250' });
|
||||
} else if (value > maxPageSize) {
|
||||
pageSizeError = translate('TablePageSizeMaximum', { maximumValue: `${maxPageSize}` });
|
||||
} else {
|
||||
this.props.onTableOptionChange({ pageSize: value });
|
||||
}
|
||||
@@ -248,6 +249,7 @@ TableOptionsModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
pageSize: PropTypes.number,
|
||||
maxPageSize: PropTypes.number,
|
||||
canModifyColumns: PropTypes.bool.isRequired,
|
||||
optionsComponent: PropTypes.elementType,
|
||||
onTableOptionChange: PropTypes.func.isRequired,
|
||||
|
||||
69
frontend/src/Series/Details/SeasonProgressLabel.tsx
Normal file
69
frontend/src/Series/Details/SeasonProgressLabel.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import Label from 'Components/Label';
|
||||
import { kinds, sizes } from 'Helpers/Props';
|
||||
import createSeriesQueueItemsDetailsSelector, {
|
||||
SeriesQueueDetails,
|
||||
} from 'Series/Index/createSeriesQueueDetailsSelector';
|
||||
|
||||
function getEpisodeCountKind(
|
||||
monitored: boolean,
|
||||
episodeFileCount: number,
|
||||
episodeCount: number,
|
||||
isDownloading: boolean
|
||||
) {
|
||||
if (isDownloading) {
|
||||
return kinds.PURPLE;
|
||||
}
|
||||
|
||||
if (episodeFileCount === episodeCount && episodeCount > 0) {
|
||||
return kinds.SUCCESS;
|
||||
}
|
||||
|
||||
if (!monitored) {
|
||||
return kinds.WARNING;
|
||||
}
|
||||
|
||||
return kinds.DANGER;
|
||||
}
|
||||
|
||||
interface SeasonProgressLabelProps {
|
||||
seriesId: number;
|
||||
seasonNumber: number;
|
||||
monitored: boolean;
|
||||
episodeCount: number;
|
||||
episodeFileCount: number;
|
||||
}
|
||||
|
||||
function SeasonProgressLabel({
|
||||
seriesId,
|
||||
seasonNumber,
|
||||
monitored,
|
||||
episodeCount,
|
||||
episodeFileCount,
|
||||
}: SeasonProgressLabelProps) {
|
||||
const queueDetails: SeriesQueueDetails = useSelector(
|
||||
createSeriesQueueItemsDetailsSelector(seriesId, seasonNumber)
|
||||
);
|
||||
|
||||
const newDownloads = queueDetails.count - queueDetails.episodesWithFiles;
|
||||
const text = newDownloads
|
||||
? `${episodeFileCount} + ${newDownloads} / ${episodeCount}`
|
||||
: `${episodeFileCount} / ${episodeCount}`;
|
||||
|
||||
return (
|
||||
<Label
|
||||
kind={getEpisodeCountKind(
|
||||
monitored,
|
||||
episodeFileCount,
|
||||
episodeCount,
|
||||
queueDetails.count > 0
|
||||
)}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
<span>{text}</span>
|
||||
</Label>
|
||||
);
|
||||
}
|
||||
|
||||
export default SeasonProgressLabel;
|
||||
@@ -2,7 +2,6 @@ import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import Label from 'Components/Label';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import Link from 'Components/Link/Link';
|
||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||
@@ -15,7 +14,7 @@ import SpinnerIcon from 'Components/SpinnerIcon';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import Popover from 'Components/Tooltip/Popover';
|
||||
import { align, icons, kinds, sizes, sortDirections, tooltipPositions } from 'Helpers/Props';
|
||||
import { align, icons, sortDirections, tooltipPositions } from 'Helpers/Props';
|
||||
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
||||
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
||||
import SeriesHistoryModal from 'Series/History/SeriesHistoryModal';
|
||||
@@ -27,6 +26,7 @@ import translate from 'Utilities/String/translate';
|
||||
import getToggledRange from 'Utilities/Table/getToggledRange';
|
||||
import EpisodeRowConnector from './EpisodeRowConnector';
|
||||
import SeasonInfo from './SeasonInfo';
|
||||
import SeasonProgressLabel from './SeasonProgressLabel';
|
||||
import styles from './SeriesDetailsSeason.css';
|
||||
|
||||
function getSeasonStatistics(episodes) {
|
||||
@@ -64,18 +64,6 @@ function getSeasonStatistics(episodes) {
|
||||
};
|
||||
}
|
||||
|
||||
function getEpisodeCountKind(monitored, episodeFileCount, episodeCount) {
|
||||
if (episodeFileCount === episodeCount && episodeCount > 0) {
|
||||
return kinds.SUCCESS;
|
||||
}
|
||||
|
||||
if (!monitored) {
|
||||
return kinds.WARNING;
|
||||
}
|
||||
|
||||
return kinds.DANGER;
|
||||
}
|
||||
|
||||
class SeriesDetailsSeason extends Component {
|
||||
|
||||
//
|
||||
@@ -265,12 +253,13 @@ class SeriesDetailsSeason extends Component {
|
||||
className={styles.episodeCountTooltip}
|
||||
canFlip={true}
|
||||
anchor={
|
||||
<Label
|
||||
kind={getEpisodeCountKind(monitored, episodeFileCount, episodeCount)}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
<span>{episodeFileCount} / {episodeCount}</span>
|
||||
</Label>
|
||||
<SeasonProgressLabel
|
||||
seriesId={seriesId}
|
||||
seasonNumber={seasonNumber}
|
||||
monitored={monitored}
|
||||
episodeCount={episodeCount}
|
||||
episodeFileCount={episodeFileCount}
|
||||
/>
|
||||
}
|
||||
title={translate('SeasonInformation')}
|
||||
body={
|
||||
|
||||
@@ -88,8 +88,7 @@ function DeleteSeriesModalContent(props: DeleteSeriesModalContentProps) {
|
||||
|
||||
const { totalEpisodeFileCount, totalSizeOnDisk } = useMemo(() => {
|
||||
return series.reduce(
|
||||
(acc, s) => {
|
||||
const { statistics = { episodeFileCount: 0, sizeOnDisk: 0 } } = s;
|
||||
(acc, { statistics = {} }) => {
|
||||
const { episodeFileCount = 0, sizeOnDisk = 0 } = statistics;
|
||||
|
||||
acc.totalEpisodeFileCount += episodeFileCount;
|
||||
@@ -155,17 +154,17 @@ function DeleteSeriesModalContent(props: DeleteSeriesModalContentProps) {
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
{series.map((s) => {
|
||||
const { episodeFileCount = 0, sizeOnDisk = 0 } = s.statistics;
|
||||
{series.map(({ title, path, statistics = {} }) => {
|
||||
const { episodeFileCount = 0, sizeOnDisk = 0 } = statistics;
|
||||
|
||||
return (
|
||||
<li key={s.title}>
|
||||
<span>{s.title}</span>
|
||||
<li key={title}>
|
||||
<span>{title}</span>
|
||||
|
||||
{deleteFiles && (
|
||||
<span>
|
||||
<span className={styles.pathContainer}>
|
||||
-<span className={styles.path}>{s.path}</span>
|
||||
-<span className={styles.path}>{path}</span>
|
||||
</span>
|
||||
|
||||
{!!episodeFileCount && (
|
||||
|
||||
@@ -138,7 +138,8 @@ class Naming extends Component {
|
||||
{ key: 1, value: translate('ReplaceWithDash') },
|
||||
{ key: 2, value: translate('ReplaceWithSpaceDash') },
|
||||
{ key: 3, value: translate('ReplaceWithSpaceDashSpace') },
|
||||
{ key: 4, value: translate('SmartReplace'), hint: translate('SmartReplaceHint') }
|
||||
{ key: 4, value: translate('SmartReplace'), hint: translate('SmartReplaceHint') },
|
||||
{ key: 5, value: translate('Custom'), hint: translate('CustomColonReplacementFormatHint') }
|
||||
];
|
||||
|
||||
const standardEpisodeFormatHelpTexts = [];
|
||||
@@ -262,6 +263,22 @@ class Naming extends Component {
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
replaceIllegalCharacters && settings.colonReplacementFormat.value === 5 ?
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('ColonReplacement')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
name="customColonReplacementFormat"
|
||||
helpText={translate('CustomColonReplacementFormatHelpText')}
|
||||
onChange={onInputChange}
|
||||
{...settings.customColonReplacementFormat}
|
||||
/>
|
||||
</FormGroup> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
renameEpisodes &&
|
||||
<div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import {
|
||||
saveNotification,
|
||||
setNotificationFieldValue,
|
||||
setNotificationFieldValues,
|
||||
setNotificationValue,
|
||||
testNotification,
|
||||
toggleAdvancedSettings
|
||||
@@ -27,7 +27,7 @@ function createMapStateToProps() {
|
||||
|
||||
const mapDispatchToProps = {
|
||||
setNotificationValue,
|
||||
setNotificationFieldValue,
|
||||
setNotificationFieldValues,
|
||||
saveNotification,
|
||||
testNotification,
|
||||
toggleAdvancedSettings
|
||||
@@ -51,8 +51,8 @@ class EditNotificationModalContentConnector extends Component {
|
||||
this.props.setNotificationValue({ name, value });
|
||||
};
|
||||
|
||||
onFieldChange = ({ name, value }) => {
|
||||
this.props.setNotificationFieldValue({ name, value });
|
||||
onFieldChange = ({ name, value, additionalProperties = {} }) => {
|
||||
this.props.setNotificationFieldValues({ properties: { ...additionalProperties, [name]: value } });
|
||||
};
|
||||
|
||||
onSavePress = () => {
|
||||
@@ -91,7 +91,7 @@ EditNotificationModalContentConnector.propTypes = {
|
||||
saveError: PropTypes.object,
|
||||
item: PropTypes.object.isRequired,
|
||||
setNotificationValue: PropTypes.func.isRequired,
|
||||
setNotificationFieldValue: PropTypes.func.isRequired,
|
||||
setNotificationFieldValues: PropTypes.func.isRequired,
|
||||
saveNotification: PropTypes.func.isRequired,
|
||||
testNotification: PropTypes.func.isRequired,
|
||||
toggleAdvancedSettings: PropTypes.func.isRequired,
|
||||
|
||||
@@ -59,6 +59,7 @@ class Notification extends Component {
|
||||
onGrab,
|
||||
onDownload,
|
||||
onUpgrade,
|
||||
onImportComplete,
|
||||
onRename,
|
||||
onSeriesAdd,
|
||||
onSeriesDelete,
|
||||
@@ -71,6 +72,7 @@ class Notification extends Component {
|
||||
supportsOnGrab,
|
||||
supportsOnDownload,
|
||||
supportsOnUpgrade,
|
||||
supportsOnImportComplete,
|
||||
supportsOnRename,
|
||||
supportsOnSeriesAdd,
|
||||
supportsOnSeriesDelete,
|
||||
@@ -105,7 +107,7 @@ class Notification extends Component {
|
||||
{
|
||||
supportsOnDownload && onDownload ?
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
{translate('OnImport')}
|
||||
{translate('OnFileImport')}
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
@@ -113,7 +115,15 @@ class Notification extends Component {
|
||||
{
|
||||
supportsOnUpgrade && onDownload && onUpgrade ?
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
{translate('OnUpgrade')}
|
||||
{translate('OnFileUpgrade')}
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
supportsOnImportComplete && onImportComplete ?
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
{translate('OnImportComplete')}
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
@@ -191,7 +201,7 @@ class Notification extends Component {
|
||||
}
|
||||
|
||||
{
|
||||
!onGrab && !onDownload && !onRename && !onHealthIssue && !onHealthRestored && !onApplicationUpdate && !onSeriesAdd && !onSeriesDelete && !onEpisodeFileDelete && !onManualInteractionRequired ?
|
||||
!onGrab && !onDownload && !onRename && !onImportComplete && !onHealthIssue && !onHealthRestored && !onApplicationUpdate && !onSeriesAdd && !onSeriesDelete && !onEpisodeFileDelete && !onManualInteractionRequired ?
|
||||
<Label
|
||||
kind={kinds.DISABLED}
|
||||
outline={true}
|
||||
@@ -233,6 +243,7 @@ Notification.propTypes = {
|
||||
onGrab: PropTypes.bool.isRequired,
|
||||
onDownload: PropTypes.bool.isRequired,
|
||||
onUpgrade: PropTypes.bool.isRequired,
|
||||
onImportComplete: PropTypes.bool.isRequired,
|
||||
onRename: PropTypes.bool.isRequired,
|
||||
onSeriesAdd: PropTypes.bool.isRequired,
|
||||
onSeriesDelete: PropTypes.bool.isRequired,
|
||||
@@ -244,6 +255,7 @@ Notification.propTypes = {
|
||||
onManualInteractionRequired: PropTypes.bool.isRequired,
|
||||
supportsOnGrab: PropTypes.bool.isRequired,
|
||||
supportsOnDownload: PropTypes.bool.isRequired,
|
||||
supportsOnImportComplete: PropTypes.bool.isRequired,
|
||||
supportsOnSeriesAdd: PropTypes.bool.isRequired,
|
||||
supportsOnSeriesDelete: PropTypes.bool.isRequired,
|
||||
supportsOnEpisodeFileDelete: PropTypes.bool.isRequired,
|
||||
|
||||
@@ -18,6 +18,7 @@ function NotificationEventItems(props) {
|
||||
onGrab,
|
||||
onDownload,
|
||||
onUpgrade,
|
||||
onImportComplete,
|
||||
onRename,
|
||||
onSeriesAdd,
|
||||
onSeriesDelete,
|
||||
@@ -30,6 +31,7 @@ function NotificationEventItems(props) {
|
||||
supportsOnGrab,
|
||||
supportsOnDownload,
|
||||
supportsOnUpgrade,
|
||||
supportsOnImportComplete,
|
||||
supportsOnRename,
|
||||
supportsOnSeriesAdd,
|
||||
supportsOnSeriesDelete,
|
||||
@@ -66,7 +68,7 @@ function NotificationEventItems(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="onDownload"
|
||||
helpText={translate('OnImport')}
|
||||
helpText={translate('OnFileImport')}
|
||||
isDisabled={!supportsOnDownload.value}
|
||||
{...onDownload}
|
||||
onChange={onInputChange}
|
||||
@@ -79,7 +81,7 @@ function NotificationEventItems(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="onUpgrade"
|
||||
helpText={translate('OnUpgrade')}
|
||||
helpText={translate('OnFileUpgrade')}
|
||||
isDisabled={!supportsOnUpgrade.value}
|
||||
{...onUpgrade}
|
||||
onChange={onInputChange}
|
||||
@@ -87,6 +89,17 @@ function NotificationEventItems(props) {
|
||||
</div>
|
||||
}
|
||||
|
||||
<div>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="onImportComplete"
|
||||
helpText={translate('OnImportComplete')}
|
||||
isDisabled={!supportsOnImportComplete.value}
|
||||
{...onImportComplete}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import getSectionState from 'Utilities/State/getSectionState';
|
||||
import updateSectionState from 'Utilities/State/updateSectionState';
|
||||
|
||||
function createSetProviderFieldValuesReducer(section) {
|
||||
return (state, { payload }) => {
|
||||
if (section === payload.section) {
|
||||
const { properties } = payload;
|
||||
const newState = getSectionState(state, section);
|
||||
newState.pendingChanges = Object.assign({}, newState.pendingChanges);
|
||||
const fields = Object.assign({}, newState.pendingChanges.fields || {});
|
||||
|
||||
Object.keys(properties).forEach((name) => {
|
||||
fields[name] = properties[name];
|
||||
});
|
||||
|
||||
newState.pendingChanges.fields = fields;
|
||||
|
||||
return updateSectionState(state, section, newState);
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
}
|
||||
|
||||
export default createSetProviderFieldValuesReducer;
|
||||
@@ -5,6 +5,7 @@ import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHand
|
||||
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
|
||||
import createTestProviderHandler, { createCancelTestProviderHandler } from 'Store/Actions/Creators/createTestProviderHandler';
|
||||
import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer';
|
||||
import createSetProviderFieldValuesReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValuesReducer';
|
||||
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
|
||||
import { createThunk } from 'Store/thunks';
|
||||
import selectProviderSchema from 'Utilities/State/selectProviderSchema';
|
||||
@@ -22,6 +23,7 @@ export const FETCH_NOTIFICATION_SCHEMA = 'settings/notifications/fetchNotificati
|
||||
export const SELECT_NOTIFICATION_SCHEMA = 'settings/notifications/selectNotificationSchema';
|
||||
export const SET_NOTIFICATION_VALUE = 'settings/notifications/setNotificationValue';
|
||||
export const SET_NOTIFICATION_FIELD_VALUE = 'settings/notifications/setNotificationFieldValue';
|
||||
export const SET_NOTIFICATION_FIELD_VALUES = 'settings/notifications/setNotificationFieldValues';
|
||||
export const SAVE_NOTIFICATION = 'settings/notifications/saveNotification';
|
||||
export const CANCEL_SAVE_NOTIFICATION = 'settings/notifications/cancelSaveNotification';
|
||||
export const DELETE_NOTIFICATION = 'settings/notifications/deleteNotification';
|
||||
@@ -55,6 +57,13 @@ export const setNotificationFieldValue = createAction(SET_NOTIFICATION_FIELD_VAL
|
||||
};
|
||||
});
|
||||
|
||||
export const setNotificationFieldValues = createAction(SET_NOTIFICATION_FIELD_VALUES, (payload) => {
|
||||
return {
|
||||
section,
|
||||
...payload
|
||||
};
|
||||
});
|
||||
|
||||
//
|
||||
// Details
|
||||
|
||||
@@ -99,6 +108,7 @@ export default {
|
||||
reducers: {
|
||||
[SET_NOTIFICATION_VALUE]: createSetSettingValueReducer(section),
|
||||
[SET_NOTIFICATION_FIELD_VALUE]: createSetProviderFieldValueReducer(section),
|
||||
[SET_NOTIFICATION_FIELD_VALUES]: createSetProviderFieldValuesReducer(section),
|
||||
|
||||
[SELECT_NOTIFICATION_SCHEMA]: (state, { payload }) => {
|
||||
return selectProviderSchema(state, section, payload, (selectedSchema) => {
|
||||
@@ -106,6 +116,7 @@ export default {
|
||||
selectedSchema.onGrab = selectedSchema.supportsOnGrab;
|
||||
selectedSchema.onDownload = selectedSchema.supportsOnDownload;
|
||||
selectedSchema.onUpgrade = selectedSchema.supportsOnUpgrade;
|
||||
selectedSchema.onImportComplete = selectedSchema.supportsOnImportComplete;
|
||||
selectedSchema.onRename = selectedSchema.supportsOnRename;
|
||||
selectedSchema.onSeriesAdd = selectedSchema.supportsOnSeriesAdd;
|
||||
selectedSchema.onSeriesDelete = selectedSchema.supportsOnSeriesDelete;
|
||||
|
||||
@@ -71,6 +71,10 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
|
||||
Mocker.GetMock<IProvideImportItemService>()
|
||||
.Setup(s => s.ProvideImportItem(It.IsAny<DownloadClientItem>(), It.IsAny<DownloadClientItem>()))
|
||||
.Returns<DownloadClientItem, DownloadClientItem>((i, p) => i);
|
||||
|
||||
Mocker.GetMock<IEpisodeService>()
|
||||
.Setup(s => s.GetEpisodes(It.IsAny<IEnumerable<int>>()))
|
||||
.Returns(new List<Episode>());
|
||||
}
|
||||
|
||||
private RemoteEpisode BuildRemoteEpisode()
|
||||
@@ -366,7 +370,7 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
|
||||
Mocker.GetMock<IEventAggregator>()
|
||||
.Verify(v => v.PublishEvent(It.IsAny<DownloadCompletedEvent>()), Times.Never());
|
||||
|
||||
_trackedDownload.State.Should().Be(TrackedDownloadState.ImportPending);
|
||||
_trackedDownload.State.Should().Be(TrackedDownloadState.ImportBlocked);
|
||||
}
|
||||
|
||||
private void AssertImported()
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc
|
||||
Subject.Definition = new NotificationDefinition();
|
||||
Subject.Definition.Settings = new XbmcSettings
|
||||
{
|
||||
Host = "localhost",
|
||||
UpdateLibrary = true
|
||||
};
|
||||
}
|
||||
@@ -49,6 +50,7 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc
|
||||
|
||||
Subject.Definition.Settings = new XbmcSettings
|
||||
{
|
||||
Host = "localhost",
|
||||
UpdateLibrary = true,
|
||||
CleanLibrary = true
|
||||
};
|
||||
@@ -58,6 +60,7 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc
|
||||
public void should_not_clean_if_no_episode_was_replaced()
|
||||
{
|
||||
Subject.OnDownload(_downloadMessage);
|
||||
Subject.ProcessQueue();
|
||||
|
||||
Mocker.GetMock<IXbmcService>().Verify(v => v.Clean(It.IsAny<XbmcSettings>()), Times.Never());
|
||||
}
|
||||
@@ -67,6 +70,7 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc
|
||||
{
|
||||
GivenOldFiles();
|
||||
Subject.OnDownload(_downloadMessage);
|
||||
Subject.ProcessQueue();
|
||||
|
||||
Mocker.GetMock<IXbmcService>().Verify(v => v.Clean(It.IsAny<XbmcSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
@@ -90,5 +90,18 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
|
||||
.Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("Series: Title", ColonReplacementFormat.Custom, "\ua789", "Series\ua789 Title")]
|
||||
[TestCase("Series: Title", ColonReplacementFormat.Custom, "∶", "Series∶ Title")]
|
||||
public void should_replace_colon_with_custom_format(string seriesName, ColonReplacementFormat replacementFormat, string customFormat, string expected)
|
||||
{
|
||||
_series.Title = seriesName;
|
||||
_namingConfig.StandardEpisodeFormat = "{Series Title}";
|
||||
_namingConfig.ColonReplacementFormat = replacementFormat;
|
||||
_namingConfig.CustomColonReplacementFormat = customFormat;
|
||||
|
||||
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
|
||||
.Should().Be(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,13 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
ExceptionVerification.IgnoreWarns();
|
||||
}
|
||||
|
||||
[TestCase("علم نف) أ.دعادل الأبيض ٢٠٢٤ ٣ ٣")]
|
||||
[TestCase("ror-240618_1007-1022-")]
|
||||
public void should_parse_unknown_formats_without_error(string title)
|
||||
{
|
||||
Parser.Parser.ParseTitle(title).Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_parse_md5()
|
||||
{
|
||||
|
||||
@@ -64,6 +64,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Title.S01.720p.VFF.WEB-DL.AAC2.0.H.264-BTN")]
|
||||
[TestCase("Title.S01.720p.VFQ.WEB-DL.AAC2.0.H.264-BTN")]
|
||||
[TestCase("Title.S01.720p.TRUEFRENCH.WEB-DL.AAC2.0.H.264-BTN")]
|
||||
[TestCase("Series In The Middle S01 Multi VFI VO 1080p WEB x265 HEVC AAC 5.1-Papaya")]
|
||||
public void should_parse_language_french(string postTitle)
|
||||
{
|
||||
var result = LanguageParser.ParseLanguages(postTitle);
|
||||
|
||||
@@ -254,6 +254,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Series Title Season 2 (WEB 1080p HEVC Opus) [Netaro]", false)]
|
||||
[TestCase("Series.Title.S01E01.Erste.Begegnungen.German.DD51.Synced.DL.1080p.HBOMaxHD.AVC-TVS", false)]
|
||||
[TestCase("Series.Title.S01E05.Tavora.greift.an.German.DL.1080p.DisneyHD.h264-4SF", false)]
|
||||
[TestCase("Series.Title.S02E04.German.Dubbed.DL.AAC.1080p.WEB.AVC-GROUP", false)]
|
||||
public void should_parse_webdl1080p_quality(string title, bool proper)
|
||||
{
|
||||
ParseAndVerifyQuality(title, Quality.WEBDL1080p, proper);
|
||||
@@ -279,6 +280,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("[HorribleSubs] Series Title! S01 [Web][MKV][h264][2160p][AAC 2.0][Softsubs (HorribleSubs)]", false)]
|
||||
[TestCase("Series Title S02 2013 WEB-DL 4k H265 AAC 2Audio-HDSWEB", false)]
|
||||
[TestCase("Series.Title.S02E02.This.Year.Will.Be.Different.2160p.WEB.H.265", false)]
|
||||
[TestCase("Series.Title.S02E04.German.Dubbed.DL.AAC.2160p.DV.HDR.WEB.HEVC-GROUP", false)]
|
||||
public void should_parse_webdl2160p_quality(string title, bool proper)
|
||||
{
|
||||
ParseAndVerifyQuality(title, Quality.WEBDL2160p, proper);
|
||||
|
||||
@@ -33,6 +33,9 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Series No More S01 2023 1080p WEB-DL AVC AC3 2.0 Dual Audio -ZR-", "Series No More", 1)]
|
||||
[TestCase("Series Title / S1E1-8 of 8 [2024, WEB-DL 1080p] + Original + RUS", "Series Title", 1)]
|
||||
[TestCase("Series Title / S2E1-16 of 16 [2022, WEB-DL] RUS", "Series Title", 2)]
|
||||
[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("[Zoombie] Zom 100: Bucket List of the Dead S01 [Web][MKV][h265 10-bit][1080p][AC3 2.0][Softsubs (Zoombie)]", "Zom 100: Bucket List of the Dead", 1)]
|
||||
public void should_parse_full_season_release(string postTitle, string title, int season)
|
||||
{
|
||||
var result = Parser.Parser.ParseTitle(postTitle);
|
||||
|
||||
@@ -168,6 +168,10 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Босх: Спадок / Series: Legacy / S2E1 of 10 (2023) WEB-DL 1080p Ukr/Eng | sub Eng", "Series: Legacy", 2, 1)]
|
||||
[TestCase("Titles.s06e01.1999.BDRip.1080p.Ukr.Eng.AC3.Hurtom.TNU.Tenax555", "Titles", 6, 1)]
|
||||
[TestCase("Titles.s06.01.1999.BDRip.1080p.Ukr.Eng.AC3.Hurtom.TNU.Tenax555", "Titles", 6, 1)]
|
||||
[TestCase("[Judas] Series Title (2024) - S01E14", "Series Title (2024)", 1, 14)]
|
||||
[TestCase("[ReleaseGroup] SeriesTitle S01E1 Webdl 1080p", "SeriesTitle", 1, 1)]
|
||||
[TestCase("[SubsPlus+] Series no Chill - S02E01 (NF WEB 1080p AVC AAC)", "Series no Chill", 2, 1)]
|
||||
[TestCase("[SubsPlus+] Series no Chill - S02E01v2 (NF WEB 1080p AVC AAC)", "Series no Chill", 2, 1)]
|
||||
|
||||
// [TestCase("", "", 0, 0)]
|
||||
public void should_parse_single_episode(string postTitle, string title, int seasonNumber, int episodeNumber)
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("[www.test-hyphen.ca] - Series (2011) S01", "Series (2011)")]
|
||||
[TestCase("test123.ca - Series Time S02 720p HDTV x264 CRON", "Series Time")]
|
||||
[TestCase("[www.test-hyphen123.co.za] - Series Title S01E01", "Series Title")]
|
||||
[TestCase("(seriesawake.com) Series Super - 57 [720p] [English Subbed]", "Series Super")]
|
||||
|
||||
public void should_not_parse_url_in_name(string postTitle, string title)
|
||||
{
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace NzbDrone.Core.Test.QueueTests
|
||||
|
||||
_trackedDownloads = Builder<TrackedDownload>.CreateListOfSize(1)
|
||||
.All()
|
||||
.With(v => v.IsTrackable = true)
|
||||
.With(v => v.DownloadItem = downloadItem)
|
||||
.With(v => v.RemoteEpisode = remoteEpisode)
|
||||
.Build()
|
||||
|
||||
@@ -5,6 +5,7 @@ using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.AutoTagging;
|
||||
using NzbDrone.Core.Organizer;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Tv;
|
||||
@@ -28,6 +29,10 @@ namespace NzbDrone.Core.Test.TvTests.SeriesServiceTests
|
||||
.With(s => s.Path = @"C:\Test\name".AsOsAgnostic())
|
||||
.With(s => s.RootFolderPath = "")
|
||||
.Build().ToList();
|
||||
|
||||
Mocker.GetMock<IAutoTaggingService>()
|
||||
.Setup(s => s.GetTagChanges(It.IsAny<Series>()))
|
||||
.Returns(new AutoTaggingChanges());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -79,5 +84,23 @@ namespace NzbDrone.Core.Test.TvTests.SeriesServiceTests
|
||||
|
||||
Subject.UpdateSeries(series, false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_add_and_remove_tags()
|
||||
{
|
||||
_series[0].Tags = new HashSet<int> { 1, 2 };
|
||||
|
||||
Mocker.GetMock<IAutoTaggingService>()
|
||||
.Setup(s => s.GetTagChanges(_series[0]))
|
||||
.Returns(new AutoTaggingChanges
|
||||
{
|
||||
TagsToAdd = new HashSet<int> { 3 },
|
||||
TagsToRemove = new HashSet<int> { 1 }
|
||||
});
|
||||
|
||||
var result = Subject.UpdateSeries(_series, false);
|
||||
|
||||
result[0].Tags.Should().BeEquivalentTo(new[] { 2, 3 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.AutoTagging;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
@@ -31,6 +33,14 @@ namespace NzbDrone.Core.Test.TvTests.SeriesServiceTests
|
||||
new Season { SeasonNumber = 1, Monitored = true },
|
||||
new Season { SeasonNumber = 2, Monitored = true }
|
||||
};
|
||||
|
||||
Mocker.GetMock<IAutoTaggingService>()
|
||||
.Setup(s => s.GetTagChanges(It.IsAny<Series>()))
|
||||
.Returns(new AutoTaggingChanges());
|
||||
|
||||
Mocker.GetMock<ISeriesRepository>()
|
||||
.Setup(s => s.Update(It.IsAny<Series>()))
|
||||
.Returns<Series>(r => r);
|
||||
}
|
||||
|
||||
private void GivenExistingSeries()
|
||||
@@ -68,5 +78,28 @@ namespace NzbDrone.Core.Test.TvTests.SeriesServiceTests
|
||||
Mocker.GetMock<IEpisodeService>()
|
||||
.Verify(v => v.SetEpisodeMonitoredBySeason(_fakeSeries.Id, It.IsAny<int>(), It.IsAny<bool>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_add_and_remove_tags()
|
||||
{
|
||||
GivenExistingSeries();
|
||||
var seasonNumber = 1;
|
||||
var monitored = false;
|
||||
|
||||
_fakeSeries.Tags = new HashSet<int> { 1, 2 };
|
||||
_fakeSeries.Seasons.Single(s => s.SeasonNumber == seasonNumber).Monitored = monitored;
|
||||
|
||||
Mocker.GetMock<IAutoTaggingService>()
|
||||
.Setup(s => s.GetTagChanges(_fakeSeries))
|
||||
.Returns(new AutoTaggingChanges
|
||||
{
|
||||
TagsToAdd = new HashSet<int> { 3 },
|
||||
TagsToRemove = new HashSet<int> { 1 }
|
||||
});
|
||||
|
||||
var result = Subject.UpdateSeries(_fakeSeries);
|
||||
|
||||
result.Tags.Should().BeEquivalentTo(new[] { 2, 3 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace NzbDrone.Core.Annotations
|
||||
@@ -59,13 +60,27 @@ namespace NzbDrone.Core.Annotations
|
||||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
public class FieldSelectOption
|
||||
public class FieldSelectOption<T>
|
||||
where T : struct
|
||||
{
|
||||
public int Value { get; set; }
|
||||
public T Value { get; set; }
|
||||
public string Name { get; set; }
|
||||
public int Order { get; set; }
|
||||
public string Hint { get; set; }
|
||||
public int? ParentValue { get; set; }
|
||||
public T? ParentValue { get; set; }
|
||||
public bool? IsDisabled { get; set; }
|
||||
public Dictionary<string, object> AdditionalProperties { get; set; }
|
||||
}
|
||||
|
||||
public class FieldSelectStringOption
|
||||
{
|
||||
public string Value { get; set; }
|
||||
public string Name { get; set; }
|
||||
public int Order { get; set; }
|
||||
public string Hint { get; set; }
|
||||
public string ParentValue { get; set; }
|
||||
public bool? IsDisabled { get; set; }
|
||||
public Dictionary<string, object> AdditionalProperties { get; set; }
|
||||
}
|
||||
|
||||
public enum FieldType
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(209)]
|
||||
public class add_on_import_complete_to_notifications : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Alter.Table("Notifications").AddColumn("OnImportComplete").AsBoolean().WithDefaultValue(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(211)]
|
||||
public class add_custom_colon_replacement_to_naming_config : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Alter.Table("NamingConfig").AddColumn("CustomColonReplacementFormat").AsString().WithDefaultValue("");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,7 @@ namespace NzbDrone.Core.Datastore
|
||||
.Ignore(x => x.ImplementationName)
|
||||
.Ignore(i => i.SupportsOnGrab)
|
||||
.Ignore(i => i.SupportsOnDownload)
|
||||
.Ignore(i => i.SupportsOnImportComplete)
|
||||
.Ignore(i => i.SupportsOnUpgrade)
|
||||
.Ignore(i => i.SupportsOnRename)
|
||||
.Ignore(i => i.SupportsOnSeriesAdd)
|
||||
|
||||
@@ -124,14 +124,23 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
}
|
||||
|
||||
var items = new List<DownloadClientItem>();
|
||||
var ignoredCount = 0;
|
||||
|
||||
foreach (var torrent in torrents)
|
||||
{
|
||||
if (torrent.Hash == null)
|
||||
// Silently ignore torrents with no hash
|
||||
if (torrent.Hash.IsNullOrWhiteSpace())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore torrents without a name, but track to log a single warning for all invalid torrents.
|
||||
if (torrent.Name.IsNullOrWhiteSpace())
|
||||
{
|
||||
ignoredCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
var item = new DownloadClientItem();
|
||||
item.DownloadId = torrent.Hash.ToUpper();
|
||||
item.Title = torrent.Name;
|
||||
@@ -189,6 +198,11 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
items.Add(item);
|
||||
}
|
||||
|
||||
if (ignoredCount > 0)
|
||||
{
|
||||
_logger.Warn("{0} torrent(s) were ignored becuase they did not have a title, check Deluge and remove any invalid torrents");
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ namespace NzbDrone.Core.Download
|
||||
private readonly IParsingService _parsingService;
|
||||
private readonly ISeriesService _seriesService;
|
||||
private readonly ITrackedDownloadAlreadyImported _trackedDownloadAlreadyImported;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public CompletedDownloadService(IEventAggregator eventAggregator,
|
||||
@@ -43,6 +45,8 @@ namespace NzbDrone.Core.Download
|
||||
IParsingService parsingService,
|
||||
ISeriesService seriesService,
|
||||
ITrackedDownloadAlreadyImported trackedDownloadAlreadyImported,
|
||||
IEpisodeService episodeService,
|
||||
IMediaFileService mediaFileService,
|
||||
Logger logger)
|
||||
{
|
||||
_eventAggregator = eventAggregator;
|
||||
@@ -52,6 +56,8 @@ namespace NzbDrone.Core.Download
|
||||
_parsingService = parsingService;
|
||||
_seriesService = seriesService;
|
||||
_trackedDownloadAlreadyImported = trackedDownloadAlreadyImported;
|
||||
_episodeService = episodeService;
|
||||
_mediaFileService = mediaFileService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -64,8 +70,8 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
SetImportItem(trackedDownload);
|
||||
|
||||
// Only process tracked downloads that are still downloading
|
||||
if (trackedDownload.State != TrackedDownloadState.Downloading)
|
||||
// Only process tracked downloads that are still downloading or have been blocked for importing due to an issue with matching
|
||||
if (trackedDownload.State != TrackedDownloadState.Downloading && trackedDownload.State != TrackedDownloadState.ImportBlocked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -96,7 +102,7 @@ namespace NzbDrone.Core.Download
|
||||
if (series == null)
|
||||
{
|
||||
trackedDownload.Warn("Series title mismatch; automatic import is not possible. Check the download troubleshooting entry on the wiki for common causes.");
|
||||
SendManualInteractionRequiredNotification(trackedDownload);
|
||||
SetStateToImportBlocked(trackedDownload);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -108,7 +114,7 @@ namespace NzbDrone.Core.Download
|
||||
if (seriesMatchType == SeriesMatchType.Id && releaseSource != ReleaseSourceType.InteractiveSearch)
|
||||
{
|
||||
trackedDownload.Warn("Found matching series via grab history, but release was matched to series by ID. Automatic import is not possible. See the FAQ for details.");
|
||||
SendManualInteractionRequiredNotification(trackedDownload);
|
||||
SetStateToImportBlocked(trackedDownload);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -129,7 +135,7 @@ namespace NzbDrone.Core.Download
|
||||
if (trackedDownload.RemoteEpisode == null)
|
||||
{
|
||||
trackedDownload.Warn("Unable to parse download, automatic import is not possible.");
|
||||
SendManualInteractionRequiredNotification(trackedDownload);
|
||||
SetStateToImportBlocked(trackedDownload);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -187,7 +193,7 @@ namespace NzbDrone.Core.Download
|
||||
if (statusMessages.Any())
|
||||
{
|
||||
trackedDownload.Warn(statusMessages.ToArray());
|
||||
SendManualInteractionRequiredNotification(trackedDownload);
|
||||
SetStateToImportBlocked(trackedDownload);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,11 +204,23 @@ namespace NzbDrone.Core.Download
|
||||
.Count() >= Math.Max(1,
|
||||
trackedDownload.RemoteEpisode.Episodes.Count);
|
||||
|
||||
var historyItems = _historyService.FindByDownloadId(trackedDownload.DownloadItem.DownloadId)
|
||||
.OrderByDescending(h => h.Date)
|
||||
.ToList();
|
||||
|
||||
var grabbedHistory = historyItems.Where(h => h.EventType == EpisodeHistoryEventType.Grabbed).ToList();
|
||||
var releaseInfo = grabbedHistory.Count > 0 ? new GrabbedReleaseInfo(grabbedHistory) : null;
|
||||
|
||||
if (allEpisodesImported)
|
||||
{
|
||||
_logger.Debug("All episodes were imported for {0}", trackedDownload.DownloadItem.Title);
|
||||
trackedDownload.State = TrackedDownloadState.Imported;
|
||||
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload, trackedDownload.RemoteEpisode.Series.Id));
|
||||
|
||||
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload,
|
||||
trackedDownload.RemoteEpisode.Series.Id,
|
||||
importResults.Where(c => c.Result == ImportResultType.Imported).Select(c => c.EpisodeFile).ToList(),
|
||||
releaseInfo));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -216,11 +234,6 @@ namespace NzbDrone.Core.Download
|
||||
// safe, but commenting for future benefit.
|
||||
|
||||
var atLeastOneEpisodeImported = importResults.Any(c => c.Result == ImportResultType.Imported);
|
||||
|
||||
var historyItems = _historyService.FindByDownloadId(trackedDownload.DownloadItem.DownloadId)
|
||||
.OrderByDescending(h => h.Date)
|
||||
.ToList();
|
||||
|
||||
var allEpisodesImportedInHistory = _trackedDownloadAlreadyImported.IsImported(trackedDownload, historyItems);
|
||||
|
||||
if (allEpisodesImportedInHistory)
|
||||
@@ -244,8 +257,11 @@ namespace NzbDrone.Core.Download
|
||||
.Write();
|
||||
}
|
||||
|
||||
var episodes = _episodeService.GetEpisodes(trackedDownload.RemoteEpisode.Episodes.Select(e => e.Id));
|
||||
var files = _mediaFileService.GetFiles(episodes.Select(e => e.EpisodeFileId).Where(i => i > 0).Distinct());
|
||||
|
||||
trackedDownload.State = TrackedDownloadState.Imported;
|
||||
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload, trackedDownload.RemoteEpisode.Series.Id));
|
||||
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload, trackedDownload.RemoteEpisode.Series.Id, files, releaseInfo));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -254,8 +270,10 @@ namespace NzbDrone.Core.Download
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SendManualInteractionRequiredNotification(TrackedDownload trackedDownload)
|
||||
private void SetStateToImportBlocked(TrackedDownload trackedDownload)
|
||||
{
|
||||
trackedDownload.State = TrackedDownloadState.ImportBlocked;
|
||||
|
||||
if (!trackedDownload.HasNotifiedManualInteractionRequired)
|
||||
{
|
||||
var grabbedHistories = _historyService.FindByDownloadId(trackedDownload.DownloadItem.DownloadId).Where(h => h.EventType == EpisodeHistoryEventType.Grabbed).ToList();
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
using NzbDrone.Common.Messaging;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Messaging;
|
||||
using NzbDrone.Core.Download.TrackedDownloads;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Download
|
||||
{
|
||||
public class DownloadCompletedEvent : IEvent
|
||||
{
|
||||
public TrackedDownload TrackedDownload { get; private set; }
|
||||
public int SeriesId { get; set; }
|
||||
public int SeriesId { get; private set; }
|
||||
public List<EpisodeFile> EpisodeFiles { get; private set; }
|
||||
public GrabbedReleaseInfo Release { get; private set; }
|
||||
|
||||
public DownloadCompletedEvent(TrackedDownload trackedDownload, int seriesId)
|
||||
public DownloadCompletedEvent(TrackedDownload trackedDownload, int seriesId, List<EpisodeFile> episodeFiles, GrabbedReleaseInfo release)
|
||||
{
|
||||
TrackedDownload = trackedDownload;
|
||||
SeriesId = seriesId;
|
||||
EpisodeFiles = episodeFiles;
|
||||
Release = release;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,8 +73,8 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
public void Check(TrackedDownload trackedDownload)
|
||||
{
|
||||
// Only process tracked downloads that are still downloading
|
||||
if (trackedDownload.State != TrackedDownloadState.Downloading)
|
||||
// Only process tracked downloads that are still downloading or import is blocked (if they fail after attempting to be processed)
|
||||
if (trackedDownload.State != TrackedDownloadState.Downloading && trackedDownload.State != TrackedDownloadState.ImportBlocked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||
_trackedDownloadService.TrackDownload((DownloadClientDefinition)downloadClient.Definition,
|
||||
downloadItem);
|
||||
|
||||
if (trackedDownload != null && trackedDownload.State == TrackedDownloadState.Downloading)
|
||||
if (trackedDownload is { State: TrackedDownloadState.Downloading or TrackedDownloadState.ImportBlocked })
|
||||
{
|
||||
_failedDownloadService.Check(trackedDownload);
|
||||
_completedDownloadService.Check(trackedDownload);
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||
public enum TrackedDownloadState
|
||||
{
|
||||
Downloading,
|
||||
ImportBlocked,
|
||||
ImportPending,
|
||||
Importing,
|
||||
Imported,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Messaging;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Download
|
||||
{
|
||||
public class UntrackedDownloadCompletedEvent : IEvent
|
||||
{
|
||||
public Series Series { get; private set; }
|
||||
public List<Episode> Episodes { get; private set; }
|
||||
public List<EpisodeFile> EpisodeFiles { get; private set; }
|
||||
public ParsedEpisodeInfo ParsedEpisodeInfo { get; private set; }
|
||||
public string SourcePath { get; private set; }
|
||||
|
||||
public UntrackedDownloadCompletedEvent(Series series, List<Episode> episodes, List<EpisodeFile> episodeFiles, ParsedEpisodeInfo parsedEpisodeInfo, string sourcePath)
|
||||
{
|
||||
Series = series;
|
||||
Episodes = episodes;
|
||||
EpisodeFiles = episodeFiles;
|
||||
ParsedEpisodeInfo = parsedEpisodeInfo;
|
||||
SourcePath = sourcePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,26 @@
|
||||
using NzbDrone.Core.Instrumentation;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Instrumentation;
|
||||
|
||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
{
|
||||
public class TrimLogDatabase : IHousekeepingTask
|
||||
{
|
||||
private readonly ILogRepository _logRepo;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
|
||||
public TrimLogDatabase(ILogRepository logRepo)
|
||||
public TrimLogDatabase(ILogRepository logRepo, IConfigFileProvider configFileProvider)
|
||||
{
|
||||
_logRepo = logRepo;
|
||||
_configFileProvider = configFileProvider;
|
||||
}
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
if (!_configFileProvider.LogDbEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_logRepo.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ namespace NzbDrone.Core.ImportLists.Custom
|
||||
{
|
||||
public class CustomSeries
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public int TvdbId { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ namespace NzbDrone.Core.ImportLists.Custom
|
||||
{
|
||||
series.Add(new ImportListItemInfo
|
||||
{
|
||||
Title = item.Title.IsNullOrWhiteSpace() ? $"TvdbId: {item.TvdbId}" : item.Title,
|
||||
TvdbId = item.TvdbId
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
{
|
||||
public static class NewznabCategoryFieldOptionsConverter
|
||||
{
|
||||
public static List<FieldSelectOption> GetFieldSelectOptions(List<NewznabCategory> categories)
|
||||
public static List<FieldSelectOption<int>> GetFieldSelectOptions(List<NewznabCategory> categories)
|
||||
{
|
||||
// Categories not relevant for Sonarr
|
||||
var ignoreCategories = new[] { 1000, 3000, 4000, 6000, 7000 };
|
||||
@@ -14,7 +14,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
// And maybe relevant for specific users
|
||||
var unimportantCategories = new[] { 0, 2000 };
|
||||
|
||||
var result = new List<FieldSelectOption>();
|
||||
var result = new List<FieldSelectOption<int>>();
|
||||
|
||||
if (categories == null)
|
||||
{
|
||||
@@ -41,7 +41,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
|
||||
foreach (var category in categories.Where(cat => !ignoreCategories.Contains(cat.Id)).OrderBy(cat => unimportantCategories.Contains(cat.Id)).ThenBy(cat => cat.Id))
|
||||
{
|
||||
result.Add(new FieldSelectOption
|
||||
result.Add(new FieldSelectOption<int>
|
||||
{
|
||||
Value = category.Id,
|
||||
Name = category.Name,
|
||||
@@ -52,7 +52,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
{
|
||||
foreach (var subcat in category.Subcategories.OrderBy(cat => cat.Id))
|
||||
{
|
||||
result.Add(new FieldSelectOption
|
||||
result.Add(new FieldSelectOption<int>
|
||||
{
|
||||
Value = subcat.Id,
|
||||
Name = subcat.Name,
|
||||
|
||||
@@ -68,16 +68,17 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
protected override bool PostProcess(IndexerResponse indexerResponse, List<XElement> items, List<ReleaseInfo> releases)
|
||||
{
|
||||
var enclosureTypes = items.SelectMany(GetEnclosures).Select(v => v.Type).Distinct().ToArray();
|
||||
|
||||
if (enclosureTypes.Any() && enclosureTypes.Intersect(PreferredEnclosureMimeTypes).Empty())
|
||||
{
|
||||
if (enclosureTypes.Intersect(TorrentEnclosureMimeTypes).Any())
|
||||
{
|
||||
_logger.Warn("{0} does not contain {1}, found {2}, did you intend to add a Torznab indexer?", indexerResponse.Request.Url, NzbEnclosureMimeType, enclosureTypes[0]);
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("{1} does not contain {1}, found {2}.", indexerResponse.Request.Url, NzbEnclosureMimeType, enclosureTypes[0]);
|
||||
}
|
||||
|
||||
_logger.Warn("{0} does not contain {1}, found {2}.", indexerResponse.Request.Url, NzbEnclosureMimeType, enclosureTypes[0]);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -262,26 +262,26 @@ namespace NzbDrone.Core.Indexers
|
||||
protected virtual RssEnclosure[] GetEnclosures(XElement item)
|
||||
{
|
||||
var enclosures = item.Elements("enclosure")
|
||||
.Select(v =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return new RssEnclosure
|
||||
{
|
||||
Url = v.Attribute("url")?.Value,
|
||||
Type = v.Attribute("type")?.Value,
|
||||
Length = v.Attribute("length")?.Value?.ParseInt64() ?? 0
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warn(e, "Failed to get enclosure for: {0}", item.Title());
|
||||
}
|
||||
.Select(v =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return new RssEnclosure
|
||||
{
|
||||
Url = v.Attribute("url")?.Value,
|
||||
Type = v.Attribute("type")?.Value,
|
||||
Length = v.Attribute("length")?.Value?.ParseInt64() ?? 0
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn(ex, "Failed to get enclosure for: {0}", item.Title());
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
.Where(v => v != null)
|
||||
.ToArray();
|
||||
return null;
|
||||
})
|
||||
.Where(v => v != null)
|
||||
.ToArray();
|
||||
|
||||
return enclosures;
|
||||
}
|
||||
|
||||
@@ -59,16 +59,17 @@ namespace NzbDrone.Core.Indexers.Torznab
|
||||
protected override bool PostProcess(IndexerResponse indexerResponse, List<XElement> items, List<ReleaseInfo> releases)
|
||||
{
|
||||
var enclosureTypes = items.SelectMany(GetEnclosures).Select(v => v.Type).Distinct().ToArray();
|
||||
|
||||
if (enclosureTypes.Any() && enclosureTypes.Intersect(PreferredEnclosureMimeTypes).Empty())
|
||||
{
|
||||
if (enclosureTypes.Intersect(UsenetEnclosureMimeTypes).Any())
|
||||
{
|
||||
_logger.Warn("{0} does not contain {1}, found {2}, did you intend to add a Newznab indexer?", indexerResponse.Request.Url, TorrentEnclosureMimeType, enclosureTypes[0]);
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("{1} does not contain {1}, found {2}.", indexerResponse.Request.Url, TorrentEnclosureMimeType, enclosureTypes[0]);
|
||||
}
|
||||
|
||||
_logger.Warn("{0} does not contain {1}, found {2}.", indexerResponse.Request.Url, TorrentEnclosureMimeType, enclosureTypes[0]);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
"CountSeasons": "{count} Řad",
|
||||
"CustomFormat": "Vlastní formát",
|
||||
"CustomFormats": "Vlastní formáty",
|
||||
"CutoffUnmet": "Vynechat nevyhovující",
|
||||
"CutoffUnmet": "Mezní hodnota nesplněna",
|
||||
"Cutoff": "Odříznout",
|
||||
"DailyEpisodeFormat": "Formát denní epizody",
|
||||
"DailyEpisodeTypeDescription": "Epizody vydávané denně nebo méně často, které používají rok-měsíc-den (2023-08-04)",
|
||||
@@ -219,7 +219,7 @@
|
||||
"CreateGroup": "Vytvořit skupinu",
|
||||
"Custom": "Vlastní",
|
||||
"CustomFormatUnknownCondition": "Neznámá podmínka vlastního formátu „{implementation}“",
|
||||
"CustomFormatUnknownConditionOption": "Neznámá možnost '{key}' pro podmínku '{implementation}'",
|
||||
"CustomFormatUnknownConditionOption": "Neznámá možnost „{key}“ pro podmínku „{implementation}“",
|
||||
"CustomFormatsLoadError": "Nelze načíst vlastní formáty",
|
||||
"CustomFormatsSettingsSummary": "Vlastní formáty a nastavení",
|
||||
"CopyUsingHardlinksSeriesHelpText": "Pevné odkazy umožňují aplikaci {appName} importovat odesílané torrenty do složky seriálu, aniž by zabíraly další místo na disku nebo kopírovaly celý obsah souboru. Hardlinky budou fungovat pouze v případě, že zdrojový a cílový soubor jsou na stejném svazku",
|
||||
@@ -319,5 +319,17 @@
|
||||
"EditSelectedImportLists": "Upravit vybrané seznamy k importu",
|
||||
"FormatDateTime": "{formattedDate} {formattedTime}",
|
||||
"AddRootFolderError": "Nepodařilo se přidat kořenový adresář",
|
||||
"DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "Klient stahování {downloadClientName} je nastaven na odstranění dokončených stahování. To může vést k tomu, že stahování budou z klienta odstraněna dříve, než je bude moci importovat {appName}."
|
||||
"DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "Klient stahování {downloadClientName} je nastaven na odstranění dokončených stahování. To může vést k tomu, že stahování budou z klienta odstraněna dříve, než je bude moci importovat {appName}.",
|
||||
"ConnectionSettingsUrlBaseHelpText": "Přidá předponu do {connectionName} url, jako např. {url}",
|
||||
"CustomFormatsSpecificationRegularExpressionHelpText": "Vlastní formát RegEx nerozlišuje velká a malá písmena",
|
||||
"CustomFormatsSpecificationFlag": "Vlajka",
|
||||
"BlackholeFolderHelpText": "Složka do které {appName} uloží {extension} soubor",
|
||||
"BlackholeWatchFolder": "Složka sledování",
|
||||
"Category": "Kategorie",
|
||||
"BlocklistAndSearch": "Seznam blokovaných a vyhledávání",
|
||||
"BlackholeWatchFolderHelpText": "Složka ze které {appName} má importovat stažené soubory",
|
||||
"BlocklistReleaseHelpText": "Zabránit {appName} v opětovném sebrání tohoto vydání pomocí RSS nebo automatického vyhledávání",
|
||||
"BlocklistMultipleOnlyHint": "Blokovat a nehledat náhradu",
|
||||
"CustomFormatsSettingsTriggerInfo": "Vlastní formát se použije na vydání nebo soubor, pokud odpovídá alespoň jednomu z různých typů zvolených podmínek.",
|
||||
"ChangeCategory": "Změnit kategorii"
|
||||
}
|
||||
|
||||
@@ -270,6 +270,9 @@
|
||||
"CreateGroup": "Create Group",
|
||||
"CurrentlyInstalled": "Currently Installed",
|
||||
"Custom": "Custom",
|
||||
"CustomColonReplacement": "Custom Colon Replacement",
|
||||
"CustomColonReplacementFormatHelpText": "Characters to be used as a replacement for colons",
|
||||
"CustomColonReplacementFormatHint": "Valid file system character such as Colon (Letter)",
|
||||
"CustomFilter": "Custom Filter",
|
||||
"CustomFilters": "Custom Filters",
|
||||
"CustomFormat": "Custom Format",
|
||||
@@ -1361,6 +1364,8 @@
|
||||
"NotificationsNtfyValidationAuthorizationRequired": "Authorization is required",
|
||||
"NotificationsPlexSettingsAuthToken": "Auth Token",
|
||||
"NotificationsPlexSettingsAuthenticateWithPlexTv": "Authenticate with Plex.tv",
|
||||
"NotificationsPlexSettingsServer": "Server",
|
||||
"NotificationsPlexSettingsServerHelpText": "Select server from plex.tv account after authenticating",
|
||||
"NotificationsPlexValidationNoTvLibraryFound": "At least one TV library is required",
|
||||
"NotificationsPushBulletSettingSenderId": "Sender ID",
|
||||
"NotificationsPushBulletSettingSenderIdHelpText": "The device ID to send notifications from, use device_iden in the device's URL on pushbullet.com (leave blank to send from yourself)",
|
||||
@@ -1454,16 +1459,17 @@
|
||||
"OnApplicationUpdate": "On Application Update",
|
||||
"OnEpisodeFileDelete": "On Episode File Delete",
|
||||
"OnEpisodeFileDeleteForUpgrade": "On Episode File Delete For Upgrade",
|
||||
"OnFileImport": "On File Import",
|
||||
"OnFileUpgrade": "On File Upgrade",
|
||||
"OnGrab": "On Grab",
|
||||
"OnHealthIssue": "On Health Issue",
|
||||
"OnHealthRestored": "On Health Restored",
|
||||
"OnImport": "On Import",
|
||||
"OnImportComplete": "On Import Complete",
|
||||
"OnLatestVersion": "The latest version of {appName} is already installed",
|
||||
"OnManualInteractionRequired": "On Manual Interaction Required",
|
||||
"OnRename": "On Rename",
|
||||
"OnSeriesAdd": "On Series Add",
|
||||
"OnSeriesDelete": "On Series Delete",
|
||||
"OnUpgrade": "On Upgrade",
|
||||
"OneMinute": "1 Minute",
|
||||
"OneSeason": "1 Season",
|
||||
"OnlyForBulkSeasonReleases": "Only for Bulk Season Releases",
|
||||
@@ -1992,6 +1998,7 @@
|
||||
"Umask770Description": "{octal} - Owner & Group write",
|
||||
"Umask775Description": "{octal} - Owner & Group write, Other read",
|
||||
"Umask777Description": "{octal} - Everyone write",
|
||||
"UnableToImportAutomatically": "Unable to Import Automatically",
|
||||
"UnableToLoadAutoTagging": "Unable to load auto tagging",
|
||||
"UnableToLoadBackups": "Unable to load backups",
|
||||
"UnableToUpdateSonarrDirectly": "Unable to update {appName} directly,",
|
||||
|
||||
@@ -128,7 +128,7 @@
|
||||
"AudioLanguages": "Idiomas de Audio",
|
||||
"Episode": "Episodio",
|
||||
"Activity": "Actividad",
|
||||
"AddNew": "Añadir Nuevo",
|
||||
"AddNew": "Añadir nuevo",
|
||||
"ApplyTagsHelpTextAdd": "Añadir: Añade las etiquetas a la lista de etiquetas existente",
|
||||
"ApplyTagsHelpTextRemove": "Eliminar: Elimina las etiquetas introducidas",
|
||||
"Blocklist": "Lista de bloqueos",
|
||||
@@ -287,7 +287,7 @@
|
||||
"History": "Historial",
|
||||
"MonitorNoNewSeasonsDescription": "No monitorizar automáticamente ninguna temporada nueva",
|
||||
"HistoryLoadError": "No se pudo cargar el historial",
|
||||
"LibraryImport": "Importar Librería",
|
||||
"LibraryImport": "Importar biblioteca",
|
||||
"RescanSeriesFolderAfterRefresh": "Volver a escanear la carpeta de series tras actualizar",
|
||||
"Wanted": "Buscado",
|
||||
"MonitorPilotEpisodeDescription": "Sólo monitorizar el primer episodio de la primera temporada",
|
||||
@@ -1526,7 +1526,6 @@
|
||||
"ShowQualityProfile": "Mostrar perfil de calidad",
|
||||
"ShowQualityProfileHelpText": "Muestra el perfil de calidad bajo el póster",
|
||||
"ShowRelativeDates": "Mostrar fechas relativas",
|
||||
"OnImport": "Al importar",
|
||||
"Other": "Otro",
|
||||
"ShowRelativeDatesHelpText": "Muestra fechas absolutas o relativas (Hoy/Ayer/etc)",
|
||||
"Proxy": "Proxy",
|
||||
@@ -1849,7 +1848,6 @@
|
||||
"VersionNumber": "Versión {version}",
|
||||
"OnManualInteractionRequired": "Cuando se requiera interacción manual",
|
||||
"OnLatestVersion": "La última versión de {appName} ya está instalada",
|
||||
"OnUpgrade": "Al actualizar",
|
||||
"RootFolders": "Carpetas raíz",
|
||||
"SeasonPremiere": "Estreno de temporada",
|
||||
"UnableToUpdateSonarrDirectly": "No se pudo actualizar {appName} directamente,",
|
||||
@@ -2078,5 +2076,13 @@
|
||||
"TomorrowAt": "Mañana a las {time}",
|
||||
"YesterdayAt": "Ayer a las {time}",
|
||||
"TodayAt": "Hoy a las {time}",
|
||||
"DayOfWeekAt": "{day} a las {time}"
|
||||
"DayOfWeekAt": "{day} a las {time}",
|
||||
"UnableToImportAutomatically": "No se pudo importar automáticamente",
|
||||
"NotificationsPlexSettingsServer": "Servidor",
|
||||
"NotificationsPlexSettingsServerHelpText": "Selecciona el servidor desde una cuenta de plex.tv después de autenticarse",
|
||||
"CustomColonReplacement": "Reemplazo personalizado de dos puntos",
|
||||
"CustomColonReplacementFormatHelpText": "Caracteres que serán usados como reemplazo para los dos puntos",
|
||||
"CustomColonReplacementFormatHint": "Caracteres válidos del sistema de archivos como dos puntos (letra)",
|
||||
"OnFileImport": "Al importar un archivo",
|
||||
"OnImportComplete": "Al completar la importación"
|
||||
}
|
||||
|
||||
@@ -961,7 +961,7 @@
|
||||
"Global": "Yleiset",
|
||||
"GeneralSettings": "Yleiset asetukset",
|
||||
"ImportListsLoadError": "Tuontilistojen lataus epäonnistui",
|
||||
"Importing": "Tuonti",
|
||||
"Importing": "Tuodaan",
|
||||
"IndexerDownloadClientHealthCheckMessage": "Tietolähteet virheellisillä lataustyökaluilla: {indexerNames}.",
|
||||
"Indexer": "Tietolähde",
|
||||
"Location": "Sijainti",
|
||||
@@ -1304,7 +1304,6 @@
|
||||
"Save": "Tallenna",
|
||||
"Seeders": "Jakajat",
|
||||
"UpgradesAllowed": "Päivitykset sallitaan",
|
||||
"OnUpgrade": "Päivitettäessä",
|
||||
"UnmappedFilesOnly": "Vain kohdistamattomat tiedostot",
|
||||
"Ignored": "Ohitettu",
|
||||
"PreferAndUpgrade": "Suosi ja päivitä",
|
||||
|
||||
@@ -299,7 +299,7 @@
|
||||
"SkipFreeSpaceCheck": "Ignorer la vérification de l'espace libre",
|
||||
"Sunday": "Dimanche",
|
||||
"TorrentDelay": "Retard du torrent",
|
||||
"DownloadClients": "Clients de téléchargement",
|
||||
"DownloadClients": "Clients de télécharg.",
|
||||
"CustomFormats": "Formats perso.",
|
||||
"NoIndexersFound": "Aucun indexeur n'a été trouvé",
|
||||
"Profiles": "Profils",
|
||||
@@ -550,7 +550,7 @@
|
||||
"LastWriteTime": "Heure de la dernière écriture",
|
||||
"LatestSeason": "Dernière saison",
|
||||
"LibraryImportTipsDontUseDownloadsFolder": "Ne l'utilisez pas pour importer des téléchargements à partir de votre client de téléchargement, cela concerne uniquement les bibliothèques organisées existantes, pas les fichiers non triés.",
|
||||
"ListWillRefreshEveryInterval": "La liste sera actualisée tous les {refreshInterval}",
|
||||
"ListWillRefreshEveryInterval": "La liste se rafraîchira toutes les {refreshInterval}",
|
||||
"ListsLoadError": "Impossible de charger les listes",
|
||||
"Local": "Locale",
|
||||
"LocalPath": "Chemin local",
|
||||
@@ -849,7 +849,7 @@
|
||||
"MinimumFreeSpaceHelpText": "Empêcher l'importation si elle laisse moins d'espace disque disponible",
|
||||
"MinimumLimits": "Limites minimales",
|
||||
"MinutesFortyFive": "45 Minutes : {fortyFive}",
|
||||
"Monitor": "Surveillé",
|
||||
"Monitor": "Surveiller",
|
||||
"MonitorAllEpisodesDescription": "Surveillez tous les épisodes sauf les spéciaux",
|
||||
"MonitorExistingEpisodes": "Épisodes existants",
|
||||
"MonitorExistingEpisodesDescription": "Surveiller les épisodes contenant des fichiers ou qui n'ont pas encore été diffusés",
|
||||
@@ -946,7 +946,6 @@
|
||||
"MatchedToEpisodes": "Adapté aux épisodes",
|
||||
"NoEpisodesFoundForSelectedSeason": "Aucun épisode n'a été trouvé pour la saison sélectionnée",
|
||||
"OnHealthRestored": "Sur la santé restaurée",
|
||||
"OnImport": "À l'importation",
|
||||
"OnLatestVersion": "La dernière version de {appName} est déjà installée",
|
||||
"PreferAndUpgrade": "Préférer et mettre à niveau",
|
||||
"RejectionCount": "Nombre de rejets",
|
||||
@@ -999,7 +998,6 @@
|
||||
"NoEpisodeInformation": "Aucune information sur l'épisode n'est disponible.",
|
||||
"NoResultsFound": "Aucun résultat trouvé",
|
||||
"NotificationTriggers": "Déclencheurs de notifications",
|
||||
"OnUpgrade": "Lors de la mise à niveau",
|
||||
"Other": "Autre",
|
||||
"OutputPath": "Chemin de sortie",
|
||||
"OverrideGrabNoEpisode": "Au moins un épisode doit être sélectionné",
|
||||
@@ -1131,7 +1129,7 @@
|
||||
"NotSeasonPack": "Pas de pack saisonnier",
|
||||
"NotificationTriggersHelpText": "Sélectionnez les événements qui doivent déclencher cette notification",
|
||||
"NotificationsTagsSeriesHelpText": "N'envoyer des notifications que pour les séries avec au moins une balise correspondante",
|
||||
"OnApplicationUpdate": "Sur la mise à jour de l'application",
|
||||
"OnApplicationUpdate": "Lors de la mise à jour de l'application",
|
||||
"OnEpisodeFileDelete": "Lors de la suppression du fichier de l'épisode",
|
||||
"OnHealthIssue": "Sur la question de la santé",
|
||||
"OnManualInteractionRequired": "Sur l'interaction manuelle requise",
|
||||
@@ -1779,7 +1777,7 @@
|
||||
"NotificationsPushoverSettingsDevicesHelpText": "Liste des noms des appareils (laisser vide pour envoyer à tous les appareils)",
|
||||
"NotificationsPushoverSettingsDevices": "Appareils",
|
||||
"NotificationsPushcutSettingsTimeSensitiveHelpText": "Activer pour marquer la notification comme « Time Sensitive »",
|
||||
"NotificationsPushcutSettingsTimeSensitive": "Time Sensitive",
|
||||
"NotificationsPushcutSettingsTimeSensitive": "Sensible au temps",
|
||||
"NotificationsPushcutSettingsNotificationNameHelpText": "Nom de la notification de l'onglet Notifications de l'app Pushcut",
|
||||
"NotificationsPushcutSettingsNotificationName": "Nom de la notification",
|
||||
"NotificationsPushcutSettingsApiKeyHelpText": "Les clés API peuvent être gérées dans la vue Compte de l'app Pushcut",
|
||||
@@ -2071,5 +2069,13 @@
|
||||
"NotificationsTelegramSettingsIncludeAppName": "Inclure {appName} dans le Titre",
|
||||
"NotificationsTelegramSettingsIncludeAppNameHelpText": "Préfixer éventuellement le titre du message par {appName} pour différencier les notifications des différentes applications",
|
||||
"IndexerSettingsMultiLanguageRelease": "Multilingue",
|
||||
"IndexerSettingsMultiLanguageReleaseHelpText": "Quelles langues sont normalement présentes dans une version multiple de l'indexeur ?"
|
||||
"IndexerSettingsMultiLanguageReleaseHelpText": "Quelles langues sont normalement présentes dans une version multiple de l'indexeur ?",
|
||||
"DownloadClientQbittorrentTorrentStateMissingFiles": "qBittorrent signale des fichiers manquants",
|
||||
"BlocklistFilterHasNoItems": "La liste de blocage sélectionnée ne contient aucun élément",
|
||||
"HasUnmonitoredSeason": "A une saison non surveillée",
|
||||
"YesterdayAt": "Hier à {time}",
|
||||
"UnableToImportAutomatically": "Impossible d'importer automatiquement",
|
||||
"DayOfWeekAt": "{day} à {time}",
|
||||
"TomorrowAt": "Demain à {time}",
|
||||
"TodayAt": "Aujourd'hui à {time}"
|
||||
}
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
{}
|
||||
{
|
||||
"About": "के बारे में",
|
||||
"Absolute": "पूर्ण"
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"ApplyChanges": "Applica Cambiamenti",
|
||||
"ApplyTags": "Applica Etichette",
|
||||
"BackupNow": "Esegui backup ora",
|
||||
"Backups": "Backups",
|
||||
"Backups": "Backup",
|
||||
"Blocklist": "Lista dei Blocchi",
|
||||
"Activity": "Attività",
|
||||
"About": "Info",
|
||||
@@ -59,12 +59,12 @@
|
||||
"AppUpdated": "{appName} Aggiornato",
|
||||
"AppUpdatedVersion": "{appName} è stato aggiornato alla versione `{version}`, per vedere le modifiche devi ricaricare {appName} ",
|
||||
"ApplicationURL": "URL Applicazione",
|
||||
"AuthenticationMethodHelpText": "Inserisci Username e Password per accedere a {appName}",
|
||||
"AuthenticationMethodHelpText": "Utilizza nome utente e password per accedere a {appName}",
|
||||
"BindAddressHelpText": "Indirizzi IP validi, localhost o '*' per tutte le interfacce",
|
||||
"BeforeUpdate": "Prima dell'aggiornamento",
|
||||
"CalendarFeed": "Feed calendario {appName}",
|
||||
"CalendarOptions": "Opzioni del Calendario",
|
||||
"ChooseImportMode": "Selezionare Metodo di Importazione",
|
||||
"ChooseImportMode": "Seleziona Metodo di Importazione",
|
||||
"CollapseMultipleEpisodes": "Collassa Episodi Multipli",
|
||||
"Conditions": "Condizioni",
|
||||
"Continuing": "In Corso",
|
||||
@@ -147,7 +147,7 @@
|
||||
"CreateGroup": "Crea gruppo",
|
||||
"DeleteEmptyFolders": "Cancella le cartelle vuote",
|
||||
"Enabled": "Abilitato",
|
||||
"UpdateMechanismHelpText": "Usa il sistema di aggiornamento interno di {appName} o uno script",
|
||||
"UpdateMechanismHelpText": "Usa il sistema di aggiornamento incorporato di {appName} o uno script",
|
||||
"AllResultsAreHiddenByTheAppliedFilter": "Tutti i risultati sono nascosti dal filtro applicato",
|
||||
"EditSelectedDownloadClients": "Modifica i Client di Download Selezionati",
|
||||
"EditSelectedImportLists": "Modifica le Liste di Importazione Selezionate",
|
||||
@@ -250,5 +250,682 @@
|
||||
"AutoRedownloadFailed": "Download fallito",
|
||||
"AddDelayProfileError": "Impossibile aggiungere un nuovo profilo di ritardo, riprova.",
|
||||
"Cutoff": "Taglio",
|
||||
"AddListExclusion": "Aggiungi elenco esclusioni"
|
||||
"AddListExclusion": "Aggiungi Lista esclusioni",
|
||||
"DownloadClientValidationApiKeyRequired": "API Key Richiesta",
|
||||
"Donate": "Dona",
|
||||
"DownloadClientDownloadStationValidationNoDefaultDestination": "Nessuna destinazione predefinita",
|
||||
"ImportListSettings": "Impostazioni delle Liste",
|
||||
"DownloadClientFreeboxSettingsAppId": "ID App",
|
||||
"DownloadClientFreeboxSettingsAppToken": "Token App",
|
||||
"DownloadClientPneumaticSettingsNzbFolderHelpText": "Questa cartella dovrà essere raggiungibile da XBMC",
|
||||
"DownloadClientPneumaticSettingsNzbFolder": "Cartella Nzb",
|
||||
"DownloadClientSabnzbdValidationEnableDisableDateSorting": "Disattiva ordinamento per data",
|
||||
"DownloadClientQbittorrentValidationCategoryUnsupported": "Categoria non è supportata",
|
||||
"DownloadClientValidationCategoryMissing": "Categoria non esiste",
|
||||
"DownloadClientRTorrentSettingsUrlPath": "Percorso Url",
|
||||
"Default": "Predefinito",
|
||||
"DownloadClientFreeboxSettingsApiUrl": "API URL",
|
||||
"DownloadClientQbittorrentValidationCategoryRecommended": "Categoria è raccomandata",
|
||||
"Discord": "Discord",
|
||||
"DownloadClientDownloadStationValidationFolderMissing": "Cartella non esiste",
|
||||
"DownloadClientValidationAuthenticationFailure": "Autenticazione Fallita",
|
||||
"DownloadClientDownloadStationValidationFolderMissingDetail": "La cartella '{downloadDir}' non esiste, deve essere creata manualmente all'interno della Cartella Condivisa '{sharedFolder}'.",
|
||||
"DownloadClientSabnzbdValidationUnknownVersion": "Versione sconosciuta: {rawVersion}",
|
||||
"DownloadClientValidationVerifySsl": "Verifica impostazioni SSL",
|
||||
"ChangeCategory": "Cambia Categoria",
|
||||
"DownloadClientPneumaticSettingsStrmFolder": "Cartella Strm",
|
||||
"Destination": "Destinazione",
|
||||
"DownloadClientDownloadStationValidationSharedFolderMissing": "Cartella condivisa non esiste",
|
||||
"DownloadClientQbittorrentValidationCategoryRecommendedDetail": "{appName} non tenterà di importare i download completati senza una categoria.",
|
||||
"DownloadClientValidationGroupMissing": "Gruppo non esistente",
|
||||
"DownloadWarning": "Avviso di download: {warningMessage}",
|
||||
"IndexerSettingsAdditionalParameters": "Parametri Addizionali",
|
||||
"IndexerSettingsCookie": "Cookie",
|
||||
"BlackholeWatchFolderHelpText": "Cartella da cui {appName} dovrebbe importare i download completati",
|
||||
"NotificationsEmailSettingsServer": "Server",
|
||||
"NotificationsNtfySettingsPasswordHelpText": "Password opzionale",
|
||||
"NotificationsPlexSettingsAuthenticateWithPlexTv": "Autentica con Plex.tv",
|
||||
"NotificationsPushcutSettingsNotificationName": "Nome Notifica",
|
||||
"NotificationsTraktSettingsExpires": "Scadenze",
|
||||
"NotificationsTraktSettingsAuthenticateWithTrakt": "Autentica con Trakt",
|
||||
"NotificationsValidationUnableToConnectToApi": "Impossibile connettersi alle API di {service}. Connessione al server fallita: ({responseCode}) {exceptionMessage}",
|
||||
"InteractiveImportNoFilesFound": "Nessun video trovato nella castella selezionata",
|
||||
"Or": "o",
|
||||
"ManageLists": "Gestisci Liste",
|
||||
"OriginalLanguage": "Lingua Originale",
|
||||
"OverrideGrabNoQuality": "Qualità deve essere selezionata",
|
||||
"XmlRpcPath": "Percorso XML RPC",
|
||||
"WouldYouLikeToRestoreBackup": "Vuoi ripristinare il backup '{name}'?",
|
||||
"PendingDownloadClientUnavailable": "In Attesa - Client di Download in attesa",
|
||||
"PreviouslyInstalled": "Precedentemente Installato",
|
||||
"MissingLoadError": "Errore caricando elementi mancanti",
|
||||
"MonitorSelected": "Monitora Selezionati",
|
||||
"Period": "Periodo",
|
||||
"RemoveFailedDownloads": "Rimuovi Download Falliti",
|
||||
"RemoveMultipleFromDownloadClientHint": "Rimuovi i download e i file dal client di download",
|
||||
"RemoveSelectedItemQueueMessageText": "Sei sicuro di voler rimuovere 1 elemento dalla coda?",
|
||||
"TagDetails": "Dettagli Etichetta - {label}",
|
||||
"BranchUpdate": "Branca da usare per aggiornare {appName}",
|
||||
"DefaultNotFoundMessage": "Ti devi essere perso, non c'è nulla da vedere qui.",
|
||||
"DeleteIndexerMessageText": "Sicuro di voler eliminare l'indicizzatore '{name}'?",
|
||||
"Socks5": "Socks5 (Supporto TOR)",
|
||||
"DeleteEpisodeFileMessage": "Sei sicuro di volere eliminare '{path}'?",
|
||||
"NotificationsKodiSettingsCleanLibraryHelpText": "Pulisci libreria dopo l'aggiornamento",
|
||||
"PreferProtocol": "Preferisci {preferredProtocol}",
|
||||
"RetryingDownloadOn": "Riprovando il download il {date} alle {time}",
|
||||
"DeleteNotificationMessageText": "Sei sicuro di voler eliminare la notifica '{name}'?",
|
||||
"RemoveCompletedDownloads": "Rimuovi Download Completati",
|
||||
"DownloadClientFloodSettingsAdditionalTags": "Tag addizionali",
|
||||
"DelayingDownloadUntil": "Ritardare il download fino al {date} alle {time}",
|
||||
"DeleteDownloadClientMessageText": "Sei sicuro di voler eliminare il client di download '{name}'?",
|
||||
"NoHistoryFound": "Nessun storico trovato",
|
||||
"OneMinute": "1 Minuto",
|
||||
"OptionalName": "Nome opzionale",
|
||||
"DeleteSelectedIndexers": "Elimina Indicizzatore/i",
|
||||
"Branch": "Branca",
|
||||
"Debug": "Debug",
|
||||
"Never": "Mai",
|
||||
"UsenetDelayTime": "Ritardo Usenet: {usenetDelay}",
|
||||
"OrganizeModalHeader": "Organizza & Rinomina",
|
||||
"Parse": "Analizza",
|
||||
"RemoveFromDownloadClient": "Rimuovi dal client di download",
|
||||
"RemoveQueueItemConfirmation": "Sei sicuro di voler rimuovere '{sourceTitle}' dalla coda?",
|
||||
"NoIndexersFound": "Nessun indicizzatore trovato",
|
||||
"DeleteImportListMessageText": "Sei sicuro di volere eliminare la lista '{name}'?",
|
||||
"DeleteDelayProfile": "Elimina Profilo di Ritardo",
|
||||
"DeleteTagMessageText": "Sei sicuro di voler eliminare l'etichetta '{label}'?",
|
||||
"MinutesSixty": "60 Minuti: {sixty}",
|
||||
"NotificationsCustomScriptSettingsName": "Script personalizzato",
|
||||
"NotificationsCustomScriptValidationFileDoesNotExist": "File non esiste",
|
||||
"Database": "Database",
|
||||
"NotificationsPushoverSettingsExpire": "Scadenza",
|
||||
"NotificationsSettingsWebhookMethod": "Metodo",
|
||||
"NotificationsSynologyValidationInvalidOs": "Deve essere un Synology",
|
||||
"NotificationsTraktSettingsRefreshToken": "Refresh Token",
|
||||
"CouldNotFindResults": "Nessun risultato trovato per '{term}'",
|
||||
"IndexerSettingsApiPath": "Percorso API",
|
||||
"AutoTaggingSpecificationMaximumYear": "Anno Massimo",
|
||||
"AutoTaggingSpecificationGenre": "Genere/i",
|
||||
"AutoTaggingSpecificationMinimumYear": "Anno Minimo",
|
||||
"AutoTaggingSpecificationOriginalLanguage": "Lingua",
|
||||
"AutoTaggingSpecificationQualityProfile": "Profilo Qualità",
|
||||
"AutoTaggingSpecificationRootFolder": "Cartella Radice",
|
||||
"AutoTaggingSpecificationStatus": "Stato",
|
||||
"CustomFormatsSpecificationLanguage": "Linguaggio",
|
||||
"CustomFormatsSpecificationMaximumSize": "Dimensione Massima",
|
||||
"CustomFormatsSpecificationMinimumSize": "Dimensione Minima",
|
||||
"DelayProfile": "Profilo di Ritardo",
|
||||
"DeleteBackupMessageText": "Sei sicuro di voler cancellare il backup '{name}'?",
|
||||
"DeleteDelayProfileMessageText": "Sei sicuro di volere eliminare questo profilo di ritardo?",
|
||||
"NotificationsTelegramSettingsSendSilentlyHelpText": "Invia il messaggio silenziosamente. L'utente riceverà una notifica senza suono",
|
||||
"NotificationsPushoverSettingsRetry": "Riprova",
|
||||
"NotificationsSettingsWebhookUrl": "URL Webhook",
|
||||
"PackageVersionInfo": "{packageVersion} di {packageAuthor}",
|
||||
"RemoveQueueItem": "Rimuovi - {sourceTitle}",
|
||||
"ParseModalErrorParsing": "Errore durante l'analisi, per favore prova di nuovo.",
|
||||
"OverrideGrabNoLanguage": "Almeno una lingua deve essere selezionata",
|
||||
"PasswordConfirmation": "Conferma Password",
|
||||
"RemoveSelectedItemsQueueMessageText": "Sei sicuro di voler rimuovere {selectedCount} elementi dalla coda?",
|
||||
"ConnectionLostToBackend": "{appName} ha perso la connessione al backend e dovrà essere ricaricato per ripristinare la funzionalità.",
|
||||
"CountIndexersSelected": "{count} indicizzatore(i) selezionato(i)",
|
||||
"CountDownloadClientsSelected": "{count} client di download selezionato/i",
|
||||
"PrioritySettings": "Priorità: {priority}",
|
||||
"OverrideAndAddToDownloadQueue": "Sovrascrivi e aggiungi alla coda di download",
|
||||
"NotificationsSettingsUseSslHelpText": "Connetti a {serviceName} tramite HTTPS indece di HTTP",
|
||||
"OrganizeRelativePaths": "Tutti i percorsi sono relativi a: `{path}`",
|
||||
"CurrentlyInstalled": "Attualmente Installato",
|
||||
"NotificationsEmailSettingsName": "Email",
|
||||
"NotificationsNtfySettingsServerUrl": "URL Server",
|
||||
"NotificationsPushoverSettingsSound": "Suono",
|
||||
"NotificationsSignalValidationSslRequired": "SSL sembra essere richiesto",
|
||||
"TorrentDelayTime": "Ritardo torrent: {torrentDelay}",
|
||||
"NotificationsTwitterSettingsMention": "Menziona",
|
||||
"NotificationsPushoverSettingsDevices": "Dispositivi",
|
||||
"NotificationsTelegramSettingsSendSilently": "Invia Silenziosamente",
|
||||
"DatabaseMigration": "Migrazione Database",
|
||||
"AutoTaggingSpecificationTag": "Etichetta",
|
||||
"CustomFormatUnknownConditionOption": "Opzione sconosciuta '{key}' per la condizione '{implementation}'",
|
||||
"CustomFormatsSpecificationResolution": "Risoluzione",
|
||||
"CustomFormatsSpecificationSource": "Fonte",
|
||||
"BlocklistAndSearch": "Lista dei Blocchi e Ricerca",
|
||||
"NotificationsEmbySettingsSendNotifications": "Invia Notifiche",
|
||||
"IndexerHDBitsSettingsMediumsHelpText": "Se non specificato, saranno utilizzate tutte le opzioni.",
|
||||
"DeleteQualityProfile": "Elimina Profilo Qualità",
|
||||
"DeleteSelectedEpisodeFiles": "Elimina i File degli Episodi Selezionati",
|
||||
"DeleteEpisodesFiles": "Elimina i File di {episodeFileCount} Episodi",
|
||||
"CustomFilter": "Filtro Personalizzato",
|
||||
"NotificationsTelegramSettingsIncludeAppName": "Includi {appName} nel Titolo",
|
||||
"IndexerHDBitsSettingsCodecsHelpText": "Se non specificato, saranno utilizzate tutte le opzioni.",
|
||||
"NotificationsGotifySettingsAppToken": "App Token",
|
||||
"InfoUrl": "URL Info",
|
||||
"ConnectionLostReconnect": "{appName} cercherà di connettersi automaticamente, oppure clicca su ricarica qui sotto.",
|
||||
"ListWillRefreshEveryInterval": "Le liste verranno aggiornate ogni {refreshInterval}",
|
||||
"NotificationsNtfySettingsServerUrlHelpText": "Lascia vuoto per usare il server pubblico {url}",
|
||||
"NotificationsTwitterSettingsMentionHelpText": "Menziona questo utente nei tweet inviati",
|
||||
"NotificationsValidationUnableToSendTestMessageApiResponse": "Impossibile inviare messaggio di prova. Risposta dalle API: {error}",
|
||||
"RemoveFromDownloadClientHint": "Rimuovi il download e i file dal client di download",
|
||||
"DayOfWeekAt": "{day} alle {time}",
|
||||
"DeleteRootFolder": "Elimina Cartella Radice",
|
||||
"DeleteRootFolderMessageText": "Sei sicuro di volere eliminare la cartella radice '{path}'?",
|
||||
"ManageIndexers": "Gestisci Indicizzatori",
|
||||
"MissingNoItems": "Nessun elemento mancante",
|
||||
"NotificationsKodiSettingsCleanLibrary": "Pulisci Libreria",
|
||||
"NotificationsNtfySettingsUsernameHelpText": "Nome utente opzionale",
|
||||
"NotificationsSettingsUpdateLibrary": "Aggiorna Libreria",
|
||||
"NotificationsSlackSettingsChannel": "Canale",
|
||||
"NotificationsSlackSettingsIcon": "Icona",
|
||||
"NotificationsTelegramSettingsBotToken": "Token Bot",
|
||||
"NotificationsTwitterSettingsAccessToken": "Access Token",
|
||||
"NotificationsTwitterSettingsConnectToTwitter": "Connetti a Twitter / X",
|
||||
"NotificationsTwitterSettingsDirectMessage": "Messaggio Diretto",
|
||||
"NotificationsValidationInvalidUsernamePassword": "Nome Utente o password non validi",
|
||||
"NotificationsValidationUnableToConnect": "Impossibile connettersi: {exceptionMessage}",
|
||||
"NotificationsValidationUnableToConnectToService": "Impossibile connettersi a {serviceName}",
|
||||
"NotificationsValidationUnableToSendTestMessage": "Impossibile inviare messaggio di prova: {exceptionMessage}",
|
||||
"ThemeHelpText": "Cambia il Tema dell'interfaccia dell’applicazione, il Tema 'Auto' userà il suo Tema di Sistema per impostare la modalità Chiara o Scura. Ispirato da Theme.Park",
|
||||
"Torrents": "Torrents",
|
||||
"Upcoming": "In arrivo",
|
||||
"DownloadClientUTorrentTorrentStateError": "uTorrent sta segnalando un errore",
|
||||
"DownloadClientValidationSslConnectFailure": "Impossibile connettersi tramite SSL",
|
||||
"ErrorLoadingContent": "Si è verificato un errore caricando questo contenuto",
|
||||
"FilterDoesNotStartWith": "non inizia con",
|
||||
"Filters": "Filtri",
|
||||
"IgnoreDownloads": "Ignora Download",
|
||||
"IndexerSettingsApiPathHelpText": "Percorso API, solitamente {url}",
|
||||
"BlackholeFolderHelpText": "Cartella nella quale {appName} salverà i file di tipo {extension}",
|
||||
"UseSeasonFolder": "Usa Cartella Stagione",
|
||||
"Monday": "Lunedì",
|
||||
"DetailedProgressBarHelpText": "Mostra testo sulla barra di avanzamento",
|
||||
"DownloadClientValidationUnknownException": "Eccezione sconosciuta: {exception}",
|
||||
"OnlyTorrent": "Solo Torrent",
|
||||
"OpenBrowserOnStart": "Apri browser all'avvio",
|
||||
"OnlyUsenet": "Solo Usenet",
|
||||
"OpenSeries": "Apri Serie",
|
||||
"Organize": "Organizza",
|
||||
"Other": "Altri",
|
||||
"Yesterday": "Ieri",
|
||||
"Paused": "In Pausa",
|
||||
"Priority": "Priorità",
|
||||
"Metadata": "Metadati",
|
||||
"Quality": "Qualità",
|
||||
"MidseasonFinale": "Finale di Metà Stagione",
|
||||
"QualityProfile": "Profilo Qualità",
|
||||
"MinimumAge": "Età Minima",
|
||||
"ShowAdvanced": "Mostra Avanzate",
|
||||
"MonitorFirstSeason": "Prima Stagione",
|
||||
"MonitoredOnly": "Solo Monitorati",
|
||||
"MoreInfo": "Ulteriori Informazioni",
|
||||
"FormatRuntimeMinutes": "{minutes}m",
|
||||
"Options": "Opzioni",
|
||||
"PartialSeason": "Stagione Parziale",
|
||||
"Port": "Porta",
|
||||
"PreferTorrent": "Preferisci Torrent",
|
||||
"Reset": "Reimposta",
|
||||
"RssIsNotSupportedWithThisIndexer": "RSS non è supportato con questo indicizzatore",
|
||||
"PublishedDate": "Data Pubblicazione",
|
||||
"SeriesDetailsGoTo": "Vai a {title}",
|
||||
"Security": "Sicurezza",
|
||||
"Settings": "Impostazioni",
|
||||
"ShowDateAdded": "Mostra Data Aggiunta",
|
||||
"Reason": "Ragione",
|
||||
"RecyclingBin": "Cestino",
|
||||
"SpecialEpisode": "Episodio Speciale",
|
||||
"Space": "Spazio",
|
||||
"SslPort": "Porta SSL",
|
||||
"StartImport": "Inizia Importazione",
|
||||
"Test": "Prova",
|
||||
"Titles": "Titoli",
|
||||
"Result": "Risultato",
|
||||
"Unavailable": "Non disponibile",
|
||||
"SearchSelected": "Ricerca Selezionate",
|
||||
"SeasonCount": "Conteggio Stagioni",
|
||||
"Season": "Stagione",
|
||||
"SeriesType": "Tipo Serie",
|
||||
"ShowNetwork": "Mostra Rete",
|
||||
"MoveSeriesFoldersMoveFiles": "Sì, Sposta i File",
|
||||
"Negated": "Negato",
|
||||
"SizeOnDisk": "Dimensione sul disco",
|
||||
"Sort": "Ordina",
|
||||
"FilterSeriesPlaceholder": "Filtra serie",
|
||||
"FormatShortTimeSpanHours": "{hours} ora/e",
|
||||
"Monitoring": "Monitorando",
|
||||
"Month": "Mese",
|
||||
"MonitoredEpisodesHelpText": "Scarica gli episodi monitorati in questa serie",
|
||||
"NoChanges": "Nessun Cambiamento",
|
||||
"NotificationsNtfyValidationAuthorizationRequired": "Autorizzazione richiesta",
|
||||
"PreferredProtocol": "Protocollo Preferito",
|
||||
"QualitiesHelpText": "Qualità più alte nella lista sono quelle preferite. Qualità all'interno dello stesso gruppo sono equivalenti. Solo le qualità selezionate saranno ricercate",
|
||||
"Real": "Reale",
|
||||
"RefreshAndScanTooltip": "Aggiorna informazioni e scansiona disco",
|
||||
"SeriesIndexFooterMissingMonitored": "Episodi Mancanti (Serie monitorate)",
|
||||
"SingleEpisode": "Episodio Singolo",
|
||||
"Shutdown": "Spegnimento",
|
||||
"Wiki": "Wiki",
|
||||
"True": "Vero",
|
||||
"WhatsNew": "Cosa c'è di nuovo?",
|
||||
"SelectQuality": "Seleziona Qualità",
|
||||
"SelectLanguages": "Seleziona Lingue",
|
||||
"TableOptions": "Opzioni Tabella",
|
||||
"Tba": "TBA",
|
||||
"TablePageSizeMinimum": "La dimensione della pagina deve essere almeno {minimumValue}",
|
||||
"Theme": "Tema",
|
||||
"Updates": "Aggiornamenti",
|
||||
"VersionNumber": "Versione {version}",
|
||||
"EditSelectedIndexers": "Modifica Indicizzatori Selezionati",
|
||||
"Example": "Esempio",
|
||||
"FilterContains": "contiene",
|
||||
"FilterDoesNotContain": "non contiene",
|
||||
"FilterIsAfter": "è dopo",
|
||||
"FilterIsBefore": "è prima",
|
||||
"FilterIs": "è",
|
||||
"FilterLessThanOrEqual": "meno o uguale di",
|
||||
"FilterNotEqual": "non uguale",
|
||||
"DownloadClientFreeboxApiError": "Le API di Freebox hanno segnalato un errore: {errorDescription}",
|
||||
"DailyEpisodeFormat": "Formato Episodi Giornalieri",
|
||||
"FileManagement": "Gestione File",
|
||||
"MissingEpisodes": "Episodi Mancanti",
|
||||
"Mode": "Modalità",
|
||||
"MonitorAllEpisodesDescription": "Monitora tutti gli episodi esclusi gli speciali",
|
||||
"MonitorAllEpisodes": "Tutti gli Episodi",
|
||||
"MonitorExistingEpisodes": "Episodi Esistenti",
|
||||
"Preferred": "Preferito",
|
||||
"Scene": "Scene",
|
||||
"SizeLimit": "Limite Dimensione",
|
||||
"Twitter": "Twitter",
|
||||
"Type": "Tipo",
|
||||
"DownloadClientNzbgetSettingsAddPausedHelpText": "Questa opzione richiede almeno la versione 16.0 di NzbGet",
|
||||
"System": "Sistema",
|
||||
"CollectionsLoadError": "Impossibile caricare le collezioni",
|
||||
"ConnectSettingsSummary": "Notifiche, connessioni a media servers/players, e script personalizzati",
|
||||
"Folders": "Cartelle",
|
||||
"PreferredSize": "Dimensione Preferita",
|
||||
"Proxy": "Proxy",
|
||||
"DownloadClientQbittorrentSettingsUseSslHelpText": "Usa una connessione sicura. Vedi Opzioni -> Web UI -> 'Usa HTTPS invece di HTTP' in qBittorrent.",
|
||||
"DownloadClientQbittorrentTorrentStateError": "qBittorrent sta segnalando un errore",
|
||||
"TimeLeft": "Tempo Rimasto",
|
||||
"Unlimited": "Illimitato",
|
||||
"Title": "Titolo",
|
||||
"Original": "Originale",
|
||||
"Path": "Percorso",
|
||||
"MaximumSize": "Dimensione Massima",
|
||||
"ProgressBarProgress": "Barra Progressi al {progress}%",
|
||||
"RootFolderSelectFreeSpace": "{freeSpace} Libero",
|
||||
"RssSync": "Sincronizza RSS",
|
||||
"SaveSettings": "Salva Impostazioni",
|
||||
"Search": "Ricerca",
|
||||
"SearchForMissing": "Ricerca dei Mancanti",
|
||||
"SearchForMonitoredEpisodes": "Ricerca degli episodi monitorati",
|
||||
"SeasonFolder": "Cartella della Stagione",
|
||||
"SeasonNumber": "Numero Stagione",
|
||||
"SelectLanguageModalTitle": "{modalTitle} - Seleziona Lingua",
|
||||
"SeriesDetailsRuntime": "{runtime} Minuti",
|
||||
"SeriesIsUnmonitored": "Serie non monitorata",
|
||||
"SeriesProgressBarText": "{episodeFileCount} / {episodeCount} (Totale: {totalEpisodeCount}, Scaricando: {downloadingCount})",
|
||||
"ShowEpisodes": "Mostra Episodi",
|
||||
"ShowEpisodeInformationHelpText": "Mostra titolo e numero dell'episodio",
|
||||
"Source": "Fonte",
|
||||
"Unknown": "Sconosciuto",
|
||||
"UseSsl": "Usa SSL",
|
||||
"DownloadClientSettingsDestinationHelpText": "Specifica manualmente la destinazione dei download, lascia vuoti per usare la predefinita",
|
||||
"DownloadClientSettingsInitialState": "Stato Iniziale",
|
||||
"Folder": "Cartella",
|
||||
"TorrentBlackholeSaveMagnetFilesReadOnly": "Solo Lettura",
|
||||
"Ui": "Interfaccia",
|
||||
"UiLanguage": "Lingua Interfaccia",
|
||||
"UiSettingsLoadError": "Impossibile caricare le impostazioni interfaccia",
|
||||
"MustNotContain": "Non Deve Contenere",
|
||||
"Network": "Rete",
|
||||
"ReadTheWikiForMoreInformation": "Leggi la Wiki per più informazioni",
|
||||
"RecentChanges": "Cambiamenti Recenti",
|
||||
"Refresh": "Aggiorna",
|
||||
"DownloadClientSettingsUseSslHelpText": "Usa connessione sicura quando connetti a {clientName}",
|
||||
"SelectEpisodes": "Seleziona Episodio/i",
|
||||
"TestAll": "Prova Tutto",
|
||||
"SelectFolder": "Seleziona Cartella",
|
||||
"DownloadStationStatusExtracting": "Estrazione: {progress}&",
|
||||
"DownloadClientQbittorrentSettingsSequentialOrder": "Ordine Sequenziale",
|
||||
"DownloadClientQbittorrentValidationCategoryAddFailureDetail": "{appName} non è stato in grado di aggiungere l'etichetta a qBittorrent.",
|
||||
"DownloadClientQbittorrentSettingsSequentialOrderHelpText": "Scarica in ordine sequenziale (qBittorrent 4.1.0+)",
|
||||
"MegabytesPerMinute": "Megabyte Per Minuto",
|
||||
"MonitorFirstSeasonDescription": "Monitora tutti gli episodi delle prima stagione. Tutte le altre stagioni saranno ignorate",
|
||||
"MustContain": "Deve Contenere",
|
||||
"PreferUsenet": "Preferisci Usenet",
|
||||
"NoChange": "Nessun Cambio",
|
||||
"RestoreBackup": "Ripristina Backup",
|
||||
"SelectAll": "Seleziona Tutto",
|
||||
"SelectSeries": "Seleziona Serie",
|
||||
"SeriesTitle": "Titolo Serie",
|
||||
"ShowPath": "Mostra Percorso",
|
||||
"Table": "Tabella",
|
||||
"TheTvdb": "TheTVDB",
|
||||
"Total": "Totale",
|
||||
"TotalFileSize": "Totale Dimensione File",
|
||||
"DownloadClientQbittorrentTorrentStateMetadata": "qBittorrent sta scaricando i metadati",
|
||||
"DownloadClientQbittorrentTorrentStateUnknown": "Stato di download sconosciuto: {state}",
|
||||
"External": "Esterno",
|
||||
"Failed": "Fallito",
|
||||
"FilterLessThan": "meno di",
|
||||
"FilterDoesNotEndWith": "non termina con",
|
||||
"FilterEqual": "uguale",
|
||||
"FormatDateTimeRelative": "{relativeDay}, {formattedDate} {formattedTime}",
|
||||
"FormatShortTimeSpanMinutes": "{minutes} minuto/i",
|
||||
"InvalidUILanguage": "L'interfaccia è impostata in una lingua non valida, correggi e salva le tue impostazioni",
|
||||
"Max": "Massimo",
|
||||
"MinutesFortyFive": "45 Minuti: {fortyFive}",
|
||||
"NoDownloadClientsFound": "Nessun client di download trovato",
|
||||
"MassSearchCancelWarning": "Questo non può essere cancellato una volta avviato senza riavviare {appName} o disattivando tutti i tuoi indicizzatori.",
|
||||
"Profiles": "Profili",
|
||||
"Qualities": "Qualità",
|
||||
"QualityProfilesLoadError": "Impossibile caricare Profili Qualità",
|
||||
"QueueLoadError": "Impossibile caricare la Coda",
|
||||
"Queued": "In Coda",
|
||||
"SaveChanges": "Salva Cambiamenti",
|
||||
"Seasons": "Stagioni",
|
||||
"Series": "Serie",
|
||||
"MonitorRecentEpisodes": "Episodi Recenti",
|
||||
"MoreDetails": "Ulteriore dettagli",
|
||||
"MonitorSpecialEpisodes": "Monitora Speciali",
|
||||
"TimeFormat": "Formato Orario",
|
||||
"UsenetDisabled": "Usenet Disabilitato",
|
||||
"Version": "Versione",
|
||||
"WithFiles": "Con i File",
|
||||
"Username": "Nome Utente",
|
||||
"YesCancel": "Sì, Cancella",
|
||||
"Yes": "Sì",
|
||||
"DownloadClientRTorrentSettingsAddStopped": "Aggiungi Fermato",
|
||||
"Restart": "Riavvia",
|
||||
"Rss": "RSS",
|
||||
"ProxyBadRequestHealthCheckMessage": "Test del proxy fallito: Status Code: {statusCode}",
|
||||
"ProxyBypassFilterHelpText": "Usa ',' come separatore, e '*.' come wildcard per i sottodomini",
|
||||
"RestartRequiredToApplyChanges": "{appName} richiede un riavvio per applicare i cambiamenti, vuoi riavviare ora?",
|
||||
"SearchMonitored": "Ricerca Monitorate",
|
||||
"SeasonDetails": "Dettagli Stagione",
|
||||
"SelectDownloadClientModalTitle": "{modalTitle} - Seleziona Client di Download",
|
||||
"SelectDropdown": "Seleziona...",
|
||||
"SeriesIsMonitored": "Serie monitorata",
|
||||
"Special": "Speciale",
|
||||
"TablePageSizeMaximum": "La dimensione della pagina non deve superare {maximumValue}",
|
||||
"TablePageSize": "Dimensione Pagina",
|
||||
"UnknownDownloadState": "Stato download sconosciuto: {state}",
|
||||
"UsenetBlackholeNzbFolder": "Cartella Nzb",
|
||||
"From": "Da",
|
||||
"QualityProfiles": "Profili Qualità",
|
||||
"RecyclingBinCleanupHelpText": "Imposta a 0 per disattivare la pulizia automatica",
|
||||
"RefreshAndScan": "Aggiorna & Scansiona",
|
||||
"HourShorthand": "h",
|
||||
"InteractiveImportNoImportMode": "Una modalità di importazione deve essere selezionata",
|
||||
"MyComputer": "Mio Computer",
|
||||
"Posters": "Locandine",
|
||||
"SeasonNumberToken": "Stagione {seasonNumber}",
|
||||
"ShowTitle": "Mostra Titolo",
|
||||
"TorrentBlackholeTorrentFolder": "Cartella Torrent",
|
||||
"UiLanguageHelpText": "Lingua che {appName} userà per l'interfaccia",
|
||||
"DownloadIgnored": "Download Ignorato",
|
||||
"CustomFormatsSpecificationMaximumSizeHelpText": "La release deve essere minore o uguale a questa dimensione",
|
||||
"CustomFormatsSpecificationMinimumSizeHelpText": "La release deve essere maggiore di questa dimensione",
|
||||
"CustomFormatsSpecificationRegularExpression": "Espressione Regolare",
|
||||
"DefaultDelayProfileSeries": "Questo è il profilo di default. Viene Applicato a tutte le serie che non hanno in profilo esplicito.",
|
||||
"DownloadClientDelugeTorrentStateError": "Deluge sta segnalando un errore",
|
||||
"DownloadClientDelugeSettingsUrlBaseHelpText": "Aggiungi un prefisso all'url del json di deluge, vedi {url}",
|
||||
"FilterEpisodesPlaceholder": "Filtra episodi per titolo o numero",
|
||||
"FilterGreaterThanOrEqual": "più grande o uguale di",
|
||||
"FilterIsNot": "non è",
|
||||
"FilterGreaterThan": "più grande di",
|
||||
"Formats": "Formati",
|
||||
"FormatRuntimeHours": "{hours}h",
|
||||
"FirstDayOfWeek": "Primo Giorno della Settimana",
|
||||
"FreeSpace": "Spazio Libero",
|
||||
"FormatDateTime": "{formattedDate} {formattedTime}",
|
||||
"FormatTimeSpanDays": "{days}d {time}",
|
||||
"MetadataSettingsSeriesImages": "Immagini della Serie",
|
||||
"MetadataSettingsSeriesMetadata": "Metadati della Serie",
|
||||
"MetadataSettingsSeasonImages": "Immagini della Stagione",
|
||||
"NotificationsEmailSettingsBccAddress": "Indirizzo/i BCC",
|
||||
"OrganizeLoadError": "Errore caricando le anteprime",
|
||||
"OrganizeSelectedSeriesModalHeader": "Organizza Serie Selezionate",
|
||||
"Today": "Oggi",
|
||||
"Specials": "Speciali",
|
||||
"NotificationsLoadError": "Impossibile caricare Notifiche",
|
||||
"SeasonPassTruncated": "Solo le ultime 25 stagione sono mostrate, vai ai dettagli per vedere tutte le stagioni",
|
||||
"SeriesCannotBeFound": "Scusa, quella serie non può essere trovata.",
|
||||
"Style": "Stile",
|
||||
"NotificationsEmailSettingsCcAddress": "Indirizzo/i CC",
|
||||
"NotificationsMailgunSettingsUseEuEndpoint": "Usa Endpoint EU",
|
||||
"Pending": "In Attesa",
|
||||
"PendingChangesStayReview": "Rimani e rivedi i cambiamenti",
|
||||
"FormatAgeDays": "giorni",
|
||||
"FormatAgeHour": "ora",
|
||||
"FormatAgeHours": "ore",
|
||||
"FormatAgeMinute": "minuto",
|
||||
"FormatAgeMinutes": "minuti",
|
||||
"FormatAgeDay": "giorno",
|
||||
"MetadataPlexSettingsSeriesPlexMatchFileHelpText": "Crea un file .plexmatch nella cartella della serie",
|
||||
"MonitorMissingEpisodes": "Episodi Mancanti",
|
||||
"New": "Nuovo",
|
||||
"NoBackupsAreAvailable": "Nessun backup disponibile",
|
||||
"MetadataSettingsEpisodeImages": "Immagini dell'Episodio",
|
||||
"PosterSize": "Dimensioni Locandina",
|
||||
"Restore": "Ripristina",
|
||||
"RestartReloadNote": "Nota: {appName} si riavvierà automaticamente e ricaricherà l'interfaccia durante il processo di ripristino.",
|
||||
"SeasonPassEpisodesDownloaded": "{episodeFileCount}/{totalEpisodeCount} episodi scaricati",
|
||||
"SelectLanguage": "Seleziona Lingua",
|
||||
"SeriesIndexFooterDownloading": "Scaricando (Uno o più episodi)",
|
||||
"SeriesTypes": "Tipi Serie",
|
||||
"SetPermissions": "Imposta Permessi",
|
||||
"StartupDirectory": "Cartella di Avvio",
|
||||
"Tomorrow": "Domani",
|
||||
"UnknownEventTooltip": "Evento sconosciuto",
|
||||
"UseProxy": "Usa Proxy",
|
||||
"InteractiveImportNoQuality": "Una qualità deve essere scelta per ogni file selezionato",
|
||||
"PortNumber": "Numero Porta",
|
||||
"MonitorAllSeasons": "Tutte le Stagioni",
|
||||
"DownloadClientValidationUnableToConnect": "Impossibile connettersi a {clientName}",
|
||||
"DownloadClientValidationVerifySslDetail": "Per favore verifica la tua configurazione SSL su entrambi {clientName} e {appName}",
|
||||
"IndexerDownloadClientHealthCheckMessage": "Indicizzatori con client di download non validi: {indexerNames}.",
|
||||
"MonitorAllSeasonsDescription": "Monitora tutte le nuove stagioni automaticamente",
|
||||
"MonitorFutureEpisodes": "Episodi Futuri",
|
||||
"No": "No",
|
||||
"MonitorLastSeason": "Ultima Stagione",
|
||||
"NoEpisodesInThisSeason": "Nessun episodio in questa stagione",
|
||||
"OrganizeModalHeaderSeason": "Organizza & Rinomina - {season}",
|
||||
"Save": "Salva",
|
||||
"More": "Altro",
|
||||
"MonitorLastSeasonDescription": "Monitora tutti gli episodi della ultima stagione",
|
||||
"RestartSonarr": "Riavvia {appName}",
|
||||
"SeasonFinale": "Finale di Stagione",
|
||||
"RestartNow": "Riavvia ora",
|
||||
"TablePageSizeHelpText": "Numero di elementi da mostrare in ogni pagina",
|
||||
"UiSettings": "Impostazioni Interfaccia",
|
||||
"TvdbId": "ID TVDB",
|
||||
"Wanted": "Ricercato",
|
||||
"CustomFormatsLoadError": "Impossibile a caricare Formati Personalizzati",
|
||||
"FilterEndsWith": "termina con",
|
||||
"MetadataSource": "Fonte Metadati",
|
||||
"Monitored": "Monitorato",
|
||||
"MoveSeriesFoldersDontMoveFiles": "No, Sposterò i File da Solo",
|
||||
"RemoveQueueItemRemovalMethod": "Metodo di Rimozione",
|
||||
"SecretToken": "Secret Token",
|
||||
"UrlBase": "Base Url",
|
||||
"NotificationsEmailSettingsFromAddress": "Dall'Indirizzo",
|
||||
"NotificationsGotifySettingsServer": "Server Gotify",
|
||||
"NotificationsGotifySettingsPriorityHelpText": "Priorità della notifica",
|
||||
"Score": "Punteggio",
|
||||
"SelectSeasonModalTitle": "{modalTitle} - Seleziona Stagione",
|
||||
"UpdateAvailableHealthCheckMessage": "Nuovo aggiornamento disponibile",
|
||||
"Permissions": "Permessi",
|
||||
"Scheduled": "Pianificato",
|
||||
"SearchAll": "Ricerca tutto",
|
||||
"VisitTheWikiForMoreDetails": "Visita la wiki per ulteriori dettagli: ",
|
||||
"Week": "Settimana",
|
||||
"DownloadClientValidationSslConnectFailureDetail": "{appName} non è in grado di connettersi a {clientName} usando SSL. Questo problema potrebbe essere legato al computer. Prova a configurare entrambi {appName} e {clientName} senza usare SSL.",
|
||||
"LogFilesLocation": "File di Log localizzati in: {location}",
|
||||
"PendingChangesMessage": "Hai dei cambiamenti non salvati, sei sicuro di volere lasciare questa pagina?",
|
||||
"NotificationsEmailSettingsUseEncryption": "Usa Crittografia",
|
||||
"DailyEpisodeTypeDescription": "Episodi rilasciati giornalmente o meno frequentemente che usano anno-mese-giorno (2023-08-04)",
|
||||
"DownloadClientQbittorrentTorrentStateMissingFiles": "qBittorrent sta segnalando dei file mancanti",
|
||||
"NoEventsFound": "Nessun evento trovato",
|
||||
"SearchIsNotSupportedWithThisIndexer": "Ricerca non supportata con questo indicizzatore",
|
||||
"SelectReleaseType": "Seleziona Tipo Release",
|
||||
"Uppercase": "Maiuscolo",
|
||||
"False": "Falso",
|
||||
"Files": "File",
|
||||
"Filter": "Filtro",
|
||||
"OverrideGrabNoEpisode": "Almeno un episodio deve essere selezionato",
|
||||
"FilterStartsWith": "inizia con",
|
||||
"FinaleTooltip": "Serie o finale di stagione",
|
||||
"QualitySettings": "Impostazioni Qualità",
|
||||
"Queue": "Coda",
|
||||
"QueueIsEmpty": "La coda è vuota",
|
||||
"QuickSearch": "Ricerca Veloce",
|
||||
"RefreshSeries": "Aggiorna Serie",
|
||||
"Year": "Anno",
|
||||
"LanguagesLoadError": "Impossibile caricare le lingue",
|
||||
"MetadataLoadError": "Impossibile caricare i Metadati",
|
||||
"MetadataSettings": "Impostazioni Metadati",
|
||||
"MetadataSettingsEpisodeMetadata": "Metadati dell'Episodio",
|
||||
"Status": "Stato",
|
||||
"DeleteQualityProfileMessageText": "Sicuro di voler cancellare il profilo di qualità '{name}'?",
|
||||
"NotificationsGotifySettingsServerHelpText": "URL server Gotify, includendo http(s):// e porta se necessario",
|
||||
"Time": "Orario",
|
||||
"Password": "Password",
|
||||
"OrganizeNothingToRename": "Successo! Il mio lavoro è finito, nessun file da rinominare.",
|
||||
"PosterOptions": "Opzioni Locandina",
|
||||
"Progress": "Progressi",
|
||||
"Protocol": "Protocollo",
|
||||
"ProxyFailedToTestHealthCheckMessage": "Test del proxy fallito: {url}",
|
||||
"ProxyType": "Tipo Proxy",
|
||||
"QualitiesLoadError": "Impossibile caricare qualità",
|
||||
"RecyclingBinCleanup": "Pulizia Cestino",
|
||||
"SelectFolderModalTitle": "{modalTitle} - Seleziona Cartella",
|
||||
"ResetTitles": "Reimposta Titoli",
|
||||
"RestartLater": "Lo riavvierò dopo",
|
||||
"RssSyncInterval": "Intervallo Sincronizzazione RSS",
|
||||
"Runtime": "Tempo di esecuzione",
|
||||
"SearchByTvdbId": "Puoi anche ricercare usando l'ID TVDB di uno show. Es. tvdb:71663",
|
||||
"SearchForMonitoredEpisodesSeason": "Ricerca degli episodi monitorati in questa stagione",
|
||||
"SelectEpisodesModalTitle": "{modalTitle} - Seleziona Episodio/i",
|
||||
"SelectSeason": "Seleziona Stagione",
|
||||
"SeriesIndexFooterMissingUnmonitored": "Episodi Mancanti (Serie non monitorate)",
|
||||
"ShowEpisodeInformation": "Mostra Informazioni Episodio",
|
||||
"Size": "Dimensione",
|
||||
"YesterdayAt": "Ieri alle {time}",
|
||||
"UnableToImportAutomatically": "Impossibile Importare Automaticamente",
|
||||
"Small": "Piccolo",
|
||||
"Socks4": "Socks4",
|
||||
"Standard": "Standard",
|
||||
"Started": "Iniziato",
|
||||
"TorrentsDisabled": "Torrent Disattivati",
|
||||
"UnsavedChanges": "Cambiamenti Non Salvati",
|
||||
"UnselectAll": "Deseleziona Tutto",
|
||||
"UpdateAll": "Aggiorna Tutto",
|
||||
"UpdateSonarrDirectlyLoadError": "Impossibile aggiornare {appName} direttamente,",
|
||||
"UseSeasonFolderHelpText": "Ordina episodi dentro all cartella della stagione",
|
||||
"VideoCodec": "Codec Video",
|
||||
"DownloadClientValidationCategoryMissingDetail": "La categoria che ha inserito non esiste in {clientName}. Crealo prima su {clientName}.",
|
||||
"RestrictionsLoadError": "Impossibile caricare le Restrizioni",
|
||||
"SearchFailedError": "Ricerca fallita, per favore riprova nuovamente dopo.",
|
||||
"DownloadClientDelugeValidationLabelPluginFailureDetail": "{appName} non è stato in grado di aggiungere l'etichetta a {clientName}.",
|
||||
"DownloadClientFreeboxAuthenticationError": "Autenticazione alle API di Freebox fallita. Ragione: {errorDescription}",
|
||||
"DownloadClientQbittorrentValidationCategoryUnsupportedDetail": "Categorie non supportate fino alla versione 3.3.0 di qBittorrent. Per favore aggiorna o prova con una Categoria vuota.",
|
||||
"DownloadClientSettingsInitialStateHelpText": "Stato iniziale per i torrent aggiunti a {clientName}",
|
||||
"DeleteSelectedDownloadClientsMessageText": "Sei sicuro di voler eliminare i '{count}' client di download selezionato/i?",
|
||||
"DownloadClientSettingsAddPaused": "Aggiungi In Pausa",
|
||||
"DownloadClientValidationUnableToConnectDetail": "Per favore verifica nome host e porta.",
|
||||
"FormatShortTimeSpanSeconds": "{seconds} secondo/i",
|
||||
"IgnoreDownload": "Ignora Download",
|
||||
"Implementation": "Implementazione",
|
||||
"File": "File",
|
||||
"LabelIsRequired": "Etichetta richiesta",
|
||||
"IndexerSettingsSeedRatio": "Rapporto Seed",
|
||||
"ManageClients": "Gestisci Clients",
|
||||
"ManageDownloadClients": "Gestisci Clients di Download",
|
||||
"Message": "Messaggio",
|
||||
"Min": "Min",
|
||||
"MinutesThirty": "30 Minuti: {thirty}",
|
||||
"Missing": "Mancante",
|
||||
"MonitorNewItems": "Monitora Nuovi Elementi",
|
||||
"MonitorNewSeasons": "Monitora Nuove Stagioni",
|
||||
"MonitorNewSeasonsHelpText": "Quali nuove stagioni devono essere monitorati automaticamente",
|
||||
"MonitorNoEpisodes": "Nessuno",
|
||||
"MonitorNoEpisodesDescription": "Nessun episodio sarà monitorato",
|
||||
"NotificationsDiscordSettingsAvatar": "Avatar",
|
||||
"NotificationsMailgunSettingsUseEuEndpointHelpText": "Abilita per usare l'endpoint EU di MailGun",
|
||||
"MonitorNoNewSeasonsDescription": "Non monitorare nessuna nuova stagione automaticamente",
|
||||
"MonitorPilotEpisode": "Episodio Pilota",
|
||||
"MonitorPilotEpisodeDescription": "Monitora solo il primo episodio della prima stagione",
|
||||
"MonitorSeries": "Monitora Serie",
|
||||
"MonitoredStatus": "Monitorato/Stato",
|
||||
"MoveFiles": "Sposta File",
|
||||
"Name": "Nome",
|
||||
"Negate": "Nega",
|
||||
"SubtitleLanguages": "Lingua Sottotitoli",
|
||||
"Sunday": "Domenica",
|
||||
"TableColumns": "Colonne",
|
||||
"TodayAt": "Oggi alle {time}",
|
||||
"TomorrowAt": "Domani alle {time}",
|
||||
"TotalSpace": "Totale Spazio",
|
||||
"EnableRss": "Abilita RSS",
|
||||
"DownloadFailedEpisodeTooltip": "Download dell'episodio fallito",
|
||||
"Downloading": "Scaricando",
|
||||
"Edit": "Modifica",
|
||||
"EditCustomFormat": "Modifica Formato Personalizzato",
|
||||
"Error": "Errore",
|
||||
"EpisodeDownloaded": "Episodio Scaricato",
|
||||
"ImportListsSonarrSettingsFullUrl": "URL Completo",
|
||||
"ImportListsTraktSettingsLimit": "Limite",
|
||||
"DownloadClient": "Client di Download",
|
||||
"EditConditionImplementation": "Modifica Condizione - {implementationName}",
|
||||
"EpisodeFileMissingTooltip": "File dell'episodio mancante",
|
||||
"EpisodeInfo": "Info Episodio",
|
||||
"DockerUpdater": "Aggiorna il container di docker per ricevere l'aggiornamento",
|
||||
"DownloadFailed": "Download Fallito",
|
||||
"EpisodeFileRenamedTooltip": "Episodio del file rinominato",
|
||||
"EpisodeTitleRequired": "Titolo Episodio Richiesto",
|
||||
"Existing": "Esistente",
|
||||
"Episode": "Episodio",
|
||||
"ImportSeries": "Importa Serie",
|
||||
"Donations": "Donazioni",
|
||||
"EditDelayProfile": "Modifica Profilo di Ritardo",
|
||||
"EnableInteractiveSearchHelpTextWarning": "Ricerca non supportata con questo indicizzatore",
|
||||
"EnableProfile": "Abilita Profilo",
|
||||
"Exception": "Eccezione",
|
||||
"EditGroups": "Modifica Gruppi",
|
||||
"EditDownloadClientImplementation": "Modifica Client di Download - {implementationName}",
|
||||
"EditSelectedSeries": "Modifica Serie Selezionate",
|
||||
"DotNetVersion": ".NET",
|
||||
"DiskSpace": "Spazio sul Disco",
|
||||
"EpisodeFileRenamed": "File dell'Episodio Rinominato",
|
||||
"Events": "Eventi",
|
||||
"Episodes": "Episodi",
|
||||
"NotificationsDiscordSettingsAuthor": "Autore",
|
||||
"EpisodeTitle": "Titolo Episodio",
|
||||
"Imported": "Importato",
|
||||
"EpisodeImported": "Episodio Importato",
|
||||
"EditConnectionImplementation": "Modifica Connessione - {implementationName}",
|
||||
"Enable": "Abilita",
|
||||
"EnableAutomaticAdd": "Abilita Aggiunta Automatica",
|
||||
"EnableAutomaticAddSeriesHelpText": "Aggiungi serie da questa lista a {appName} quando la sincronizzazione è effettuata tramite UI o da {appName}",
|
||||
"EditIndexerImplementation": "Modifica Indicizzatore - {implementationName}",
|
||||
"ImportListsTraktSettingsAuthenticateWithTrakt": "Autentica con Trakt",
|
||||
"ImportListsTraktSettingsGenres": "Generi",
|
||||
"ImportListsTraktSettingsListName": "Nome Lista",
|
||||
"ImportListsTraktSettingsAdditionalParameters": "Parametri Addizionali",
|
||||
"ImportListsTraktSettingsListType": "Tipo Lista",
|
||||
"ImportListsTraktSettingsRating": "Valutazione",
|
||||
"Docker": "Docker",
|
||||
"IndexerHDBitsSettingsCategories": "Categorie",
|
||||
"EditSeries": "Modifica Serie",
|
||||
"Duration": "Durata",
|
||||
"EnableCompletedDownloadHandlingHelpText": "Importa automaticamente i download completati dal client di download",
|
||||
"SeriesIndexFooterEnded": "Terminata (Tutti gli episodi scaricati)",
|
||||
"EpisodeFileDeletedTooltip": "File dell'episodio eliminato",
|
||||
"Downloaded": "Scaricato",
|
||||
"EditQualityProfile": "Modifica Profilo Qualità",
|
||||
"EditSeriesModalHeader": "Modifica - {title}",
|
||||
"EpisodeRequested": "Episodio Richiesto",
|
||||
"DownloadIgnoredEpisodeTooltip": "Download dell'Episodio Ignorato",
|
||||
"EditRestriction": "Modifica Restrizione",
|
||||
"EnableSsl": "Abilita SSL",
|
||||
"EpisodeFileDeleted": "File dell'Episodio Eliminato",
|
||||
"Importing": "Importando"
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@
|
||||
"Calendar": "Calendário",
|
||||
"Connect": "Conectar",
|
||||
"CustomFormats": "Formatos personalizados",
|
||||
"CutoffUnmet": "Limite não alcançado",
|
||||
"CutoffUnmet": "Corte Não Alcançado",
|
||||
"DownloadClients": "Clientes de download",
|
||||
"Events": "Eventos",
|
||||
"General": "Geral",
|
||||
@@ -205,7 +205,7 @@
|
||||
"SeasonNumber": "Número da Temporada",
|
||||
"SeriesTitle": "Título da Série",
|
||||
"Special": "Especial",
|
||||
"TestParsing": "Testar Análise",
|
||||
"TestParsing": "Análise de teste",
|
||||
"About": "Sobre",
|
||||
"Actions": "Ações",
|
||||
"AppDataDirectory": "Diretório AppData",
|
||||
@@ -494,7 +494,7 @@
|
||||
"CustomFormatsSettings": "Configurações de Formatos Personalizados",
|
||||
"CustomFormatsSettingsSummary": "Configurações e Formatos Personalizados",
|
||||
"DailyEpisodeFormat": "Formato do episódio diário",
|
||||
"Cutoff": "Limite",
|
||||
"Cutoff": "Corte",
|
||||
"Dash": "Traço",
|
||||
"Dates": "Datas",
|
||||
"Debug": "Depuração",
|
||||
@@ -686,11 +686,9 @@
|
||||
"OnGrab": "Ao obter",
|
||||
"OnHealthIssue": "Ao Problema de Saúde",
|
||||
"OnHealthRestored": "Com a Saúde Restaurada",
|
||||
"OnImport": "Ao Importar",
|
||||
"OnRename": "Ao Renomear",
|
||||
"OnSeriesAdd": "Ao Adicionar Série",
|
||||
"OnSeriesDelete": "Ao Excluir Série",
|
||||
"OnUpgrade": "Ao Atualizar",
|
||||
"OneMinute": "1 Minuto",
|
||||
"OnlyForBulkSeasonReleases": "Apenas para lançamentos de temporada em massa",
|
||||
"OnlyTorrent": "Só Torrent",
|
||||
@@ -891,7 +889,7 @@
|
||||
"UnmonitorDeletedEpisodes": "Cancelar Monitoramento de Episódios Excluídos",
|
||||
"UnsavedChanges": "Alterações Não Salvas",
|
||||
"UpdateAutomaticallyHelpText": "Baixe e instale atualizações automaticamente. Você ainda poderá instalar a partir do Sistema: Atualizações",
|
||||
"UpdateMechanismHelpText": "Use o atualizador integrado do {appName} ou um script",
|
||||
"UpdateMechanismHelpText": "Usar o atualizador integrado do {appName} ou um script",
|
||||
"UpdateSonarrDirectlyLoadError": "Incapaz de atualizar o {appName} diretamente,",
|
||||
"UpdateUiNotWritableHealthCheckMessage": "Não é possível instalar a atualização porque a pasta de IU '{uiFolder}' não pode ser salva pelo usuário '{userName}'.",
|
||||
"UpgradeUntil": "Atualizar Até",
|
||||
@@ -1262,7 +1260,7 @@
|
||||
"OverrideGrabNoQuality": "A qualidade deve ser selecionada",
|
||||
"OverrideGrabNoSeries": "A série deve ser selecionada",
|
||||
"Parse": "Analisar",
|
||||
"ParseModalHelpTextDetails": "{appName} tentará analisar o título e mostrar detalhes sobre ele",
|
||||
"ParseModalHelpTextDetails": "O {appName} tentará analisar o título e mostrar detalhes sobre ele",
|
||||
"RecentChanges": "Mudanças Recentes",
|
||||
"ReleaseRejected": "Lançamento Rejeitado",
|
||||
"ReleaseSceneIndicatorAssumingScene": "Assumindo a Numeração da Scene.",
|
||||
@@ -2078,5 +2076,13 @@
|
||||
"TodayAt": "Hoje às {time}",
|
||||
"TomorrowAt": "Amanhã às {time}",
|
||||
"HasUnmonitoredSeason": "Tem Temporada Não Monitorada",
|
||||
"YesterdayAt": "Ontem às {time}"
|
||||
"YesterdayAt": "Ontem às {time}",
|
||||
"UnableToImportAutomatically": "Não foi possível importar automaticamente",
|
||||
"CustomColonReplacement": "Substituto de Dois Pontos Personalizado",
|
||||
"CustomColonReplacementFormatHint": "Caractere válido do sistema de arquivos, como dois pontos (letra)",
|
||||
"NotificationsPlexSettingsServerHelpText": "Selecione o servidor da conta plex.tv após a autenticação",
|
||||
"OnFileImport": "Ao Importar o Arquivo",
|
||||
"OnImportComplete": "Ao Completar Importação",
|
||||
"CustomColonReplacementFormatHelpText": "Caracteres a serem usados em substituição aos dois pontos",
|
||||
"NotificationsPlexSettingsServer": "Servidor"
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@
|
||||
"AlreadyInYourLibrary": "Уже в вашей библиотеке",
|
||||
"Always": "Всегда",
|
||||
"Conditions": "Условия",
|
||||
"AbsoluteEpisodeNumber": "Абсолютный номер эпизода",
|
||||
"AbsoluteEpisodeNumber": "Абсолютные номера эпизодов",
|
||||
"CustomFormatsSettings": "Настройки пользовательских форматов",
|
||||
"Daily": "Ежедневно",
|
||||
"AnalyticsEnabledHelpText": "Отправлять в {appName} анонимную информацию об использовании и ошибках. Анонимная статистика включает в себя информацию о браузере, какие страницы веб-интерфейса {appName} загружены, сообщения об ошибках, а также операционной системе. Мы используем эту информацию для выявления ошибок, а также для разработки нового функционала.",
|
||||
@@ -216,5 +216,552 @@
|
||||
"AddListExclusionError": "Не удалось добавить новое исключение из списка. Повторите попытку.",
|
||||
"AddImportListExclusionError": "Не удалось добавить новое исключение из списка импорта. Повторите попытку.",
|
||||
"AddListExclusion": "Добавить исключение из списка",
|
||||
"AddDelayProfileError": "Не удалось добавить новый профиль задержки. Повторите попытку."
|
||||
"AddDelayProfileError": "Не удалось добавить новый профиль задержки. Повторите попытку.",
|
||||
"Blocklist": "Черный список",
|
||||
"Connect": "Подключить",
|
||||
"Username": "Пользователь",
|
||||
"View": "Просмотр",
|
||||
"EpisodeFileMissingTooltip": "Файл эпизода отсутствует",
|
||||
"DownloadClientAriaSettingsDirectoryHelpText": "Опциональное местоположение для загрузок. Оставьте пустым, чтобы использовать местоположение Aria2 по умолчанию",
|
||||
"DownloadClientDownloadStationValidationNoDefaultDestination": "Нет пункта назначения по умолчанию",
|
||||
"DownloadClientFreeboxApiError": "API Freebox вернул ошибку: {errorDescription}",
|
||||
"DownloadClientFreeboxNotLoggedIn": "Не авторизован",
|
||||
"DownloadClientFreeboxSettingsAppIdHelpText": "Идентификатор приложения, указанный при создании доступа к Freebox API (т. е. 'app_id')",
|
||||
"Episode": "Эпизод",
|
||||
"DownloadClientNzbgetValidationKeepHistoryOverMax": "Для параметра NzbGet KeepHistory должно быть меньше 25000",
|
||||
"UpgradesAllowedHelpText": "Если отключено, то качества не будут обновляться",
|
||||
"UseSeasonFolderHelpText": "Сортировать эпизоды внутри папки сезона",
|
||||
"EpisodeTitleRequired": "Требуется название эпизода",
|
||||
"DownloadClientValidationTestTorrents": "Не удалось получить список торрентов: {exceptionMessage}",
|
||||
"Downloaded": "Скачано",
|
||||
"AddingTag": "Добавить тэг",
|
||||
"EnableAutomaticAdd": "Включить автоматическое добавление",
|
||||
"EpisodeTitle": "Название эпизода",
|
||||
"DeleteSelectedEpisodeFilesHelpText": "Вы уверены, что хотите удалить выбранные файлы эпизода?",
|
||||
"Calendar": "Календарь",
|
||||
"CloneProfile": "Клонировать профиль",
|
||||
"Ended": "Завершен",
|
||||
"Download": "Скачать",
|
||||
"DownloadClient": "Загрузочный клиент",
|
||||
"Donate": "Пожертвовать",
|
||||
"AnalyseVideoFiles": "Анализировать видео файлы",
|
||||
"Analytics": "Аналитика",
|
||||
"Anime": "Аниме",
|
||||
"Any": "Любой",
|
||||
"AppUpdated": "{appName} обновлен",
|
||||
"AppUpdatedVersion": "Приложение {appName} обновлено до версии `{version}`. Чтобы получить последние изменения, вам необходимо перезагрузить приложение {appName} ",
|
||||
"ApplyTagsHelpTextHowToApplyImportLists": "Как применить теги к выбранным спискам импорта",
|
||||
"CancelPendingTask": "Вы уверены, что хотите убрать данную задачу из очереди?",
|
||||
"CancelProcessing": "Отменить обработку",
|
||||
"CertificateValidationHelpText": "Измените строгую проверку сертификации HTTPS. Не меняйте, если вы не понимаете риски.",
|
||||
"Certification": "Возрастной рейтинг",
|
||||
"ChangeFileDate": "Изменить дату файла",
|
||||
"ClickToChangeQuality": "Нажмите чтобы изменить качество",
|
||||
"ClickToChangeSeason": "Нажмите, чтобы изменить сезон",
|
||||
"ClickToChangeSeries": "Нажмите, чтобы изменить сериал",
|
||||
"CloneIndexer": "Клонировать индексер",
|
||||
"BackupNow": "Создать резервную копию",
|
||||
"UpdaterLogFiles": "Фалы журналов обновления",
|
||||
"Updates": "Обновления",
|
||||
"UpgradeUntil": "Обновить до качества",
|
||||
"UrlBaseHelpText": "Для поддержки обратного прокси, по умолчанию пусто",
|
||||
"AbsoluteEpisodeNumbers": "Абсолютные номера эпизодов",
|
||||
"AddReleaseProfile": "Добавить профиль выпуска",
|
||||
"AddRemotePathMapping": "Добавить удаленный путь",
|
||||
"AirDate": "Дата выхода в эфир",
|
||||
"AnimeEpisodeFormat": "Формат аниме-эпизода",
|
||||
"AuthBasic": "Базовый (всплывающее окно браузера)",
|
||||
"AuthForm": "Формы (Страница авторизации)",
|
||||
"Authentication": "Аутентификация",
|
||||
"AuthenticationRequired": "Требуется авторизация",
|
||||
"BackupIntervalHelpText": "Периодичность автоматического резервного копирования",
|
||||
"BackupRetentionHelpText": "Автоматические резервные копии старше указанного периода будут автоматически удалены",
|
||||
"Backups": "Резервные копии",
|
||||
"BackupsLoadError": "Невозможно загрузить резервные копии",
|
||||
"BlocklistLoadError": "Не удалось загрузить черный список",
|
||||
"Branch": "Ветвь",
|
||||
"BranchUpdate": "Ветвь для обновления {appName}",
|
||||
"ClientPriority": "Приоритет клиента",
|
||||
"CopyUsingHardlinksHelpTextWarning": "Блокировка файлов иногда может мешать переименованию файлов во время раздачи. Можно временно приостановить раздачу и использовать функции {appName} для переименования.",
|
||||
"CreateEmptySeriesFolders": "Создать пустые папки для сериалов",
|
||||
"CreateGroup": "Создать группу",
|
||||
"CurrentlyInstalled": "Установлено",
|
||||
"CustomFormatScore": "Настраиваемый формат оценки",
|
||||
"DelayMinutes": "{delay} Минуты",
|
||||
"DailyEpisodeFormat": "Формат ежедневных эпизодов",
|
||||
"DelayProfilesLoadError": "Невозможно загрузить профили задержки",
|
||||
"DelayProfile": "Профиль задержки",
|
||||
"DeleteReleaseProfile": "Удалить профиль релиза",
|
||||
"DeleteSpecificationHelpText": "Вы уверены, что хотите удалить уведомление '{name}'?",
|
||||
"ChooseAnotherFolder": "Выбрать другой каталог",
|
||||
"DownloadClients": "Клиенты для скачивания",
|
||||
"DownloadPropersAndRepacks": "Проперы и репаки",
|
||||
"Edit": "Редактирование",
|
||||
"Duration": "Длительность",
|
||||
"EnableColorImpairedMode": "Включить режим для слабовидящих",
|
||||
"EnableProfileHelpText": "Установите флажок, чтобы включить профиль релиза",
|
||||
"EnableRss": "Включить RSS",
|
||||
"CollectionsLoadError": "Не удалось загрузить коллекции",
|
||||
"ColonReplacement": "Замена двоеточий",
|
||||
"UpgradesAllowed": "Обновления разрешены",
|
||||
"DoNotPrefer": "Не предпочитать",
|
||||
"Donations": "Пожертвования",
|
||||
"ConnectionSettingsUrlBaseHelpText": "Добавляет префикс к URL-адресу {connectionName}, например {url}",
|
||||
"CopyToClipboard": "Копировать в буфер обмена",
|
||||
"Custom": "Настраиваемый",
|
||||
"CustomFilter": "Настраиваемые фильтры",
|
||||
"DeleteEpisodeFromDisk": "Удалить эпизод с диска",
|
||||
"CustomFormatsSettingsSummary": "Пользовательские форматы и настройки",
|
||||
"DeleteSeriesFolder": "Удалить папку сериала",
|
||||
"DeleteSpecification": "Удалить уведомление",
|
||||
"Deleted": "Удалено",
|
||||
"DeletedReasonUpgrade": "Файл был удален чтобы импортировать обновление",
|
||||
"DetailedProgressBar": "Подробный индикатор выполнения",
|
||||
"Directory": "Каталог",
|
||||
"DownloadClientDelugeValidationLabelPluginFailure": "Не удалось настроить метку",
|
||||
"DownloadClientDownloadStationValidationNoDefaultDestinationDetail": "Вы должны войти в свою Diskstation как {username} и вручную настроить ее в настройках DownloadStation в разделе BT/HTTP/FTP/NZB -> Местоположение",
|
||||
"DownloadClientFloodSettingsPostImportTagsHelpText": "Добавить теги после импорта загрузки",
|
||||
"DownloadClientFreeboxSettingsApiUrlHelpText": "Определите базовый URL-адрес Freebox API с версией API, например '{url}', по умолчанию — '{defaultApiUrl}'",
|
||||
"DownloadClientQbittorrentSettingsFirstAndLastFirst": "Первый и последний Первый",
|
||||
"DownloadClientQbittorrentValidationCategoryAddFailure": "Не удалось настроить категорию",
|
||||
"DownloadClientQbittorrentValidationCategoryAddFailureDetail": "Пользователю {appName} не удалось добавить метку в qBittorrent",
|
||||
"DownloadClientQbittorrentValidationCategoryUnsupportedDetail": "Категории не поддерживаются до версии qBittorrent 3.3.0. Пожалуйста, обновите версию или повторите попытку, указав пустую категорию",
|
||||
"DownloadClientQbittorrentValidationQueueingNotEnabled": "Очередь не включена",
|
||||
"DownloadClientQbittorrentValidationRemovesAtRatioLimit": "qBittorrent настроен на удаление торрентов, когда они достигают предельного рейтинга (Ratio)",
|
||||
"DownloadClientSabnzbdValidationCheckBeforeDownload": "Отключите опцию «Проверить перед загрузкой» в Sabnbzd",
|
||||
"DownloadClientSabnzbdValidationEnableDisableMovieSortingDetail": "Вы должны отключить сортировку фильмов для категории, которую использует {appName}, чтобы избежать проблем с импортом. Отправляйтесь в Sabnzbd, чтобы исправить это.",
|
||||
"DownloadClientTransmissionSettingsDirectoryHelpText": "Опциональное место для загрузок. Оставьте пустым, чтобы использовать каталог Transmission по умолчанию",
|
||||
"DownloadClientTransmissionSettingsUrlBaseHelpText": "Добавляет префикс к URL-адресу RPC {clientName}, например {url}, по умолчанию — '{defaultUrl}'",
|
||||
"EditQualityProfile": "Редактировать профиль качества",
|
||||
"DownloadClientValidationAuthenticationFailure": "Ошибка аутентификации",
|
||||
"DownloadClientValidationCategoryMissing": "Категория не существует",
|
||||
"DownloadClientValidationUnableToConnect": "Невозможно подключиться к {clientName}",
|
||||
"DownloadClientValidationTestNzbs": "Не удалось получить список NZB: {exceptionMessage}",
|
||||
"DownloadFailedEpisodeTooltip": "Загрузка эпизода не удалась",
|
||||
"DownloadFailed": "Неудачное скачивание",
|
||||
"DownloadStationStatusExtracting": "Извлечение: {progress}%",
|
||||
"EditCustomFormat": "Редактировать пользовательский формат",
|
||||
"EditConnectionImplementation": "Добавить соединение - {implementationName}",
|
||||
"EditMetadata": "Редактировать метаданные {metadataType}",
|
||||
"EditRemotePathMapping": "Редактировать расположение подключенной папки",
|
||||
"EnableInteractiveSearchHelpTextWarning": "Поиск не поддерживается с этим индексатором",
|
||||
"EpisodeFileDeleted": "Файл эпизода удален",
|
||||
"EpisodeCount": "Количество эпизодов",
|
||||
"UpgradeUntilThisQualityIsMetOrExceeded": "Обновлять, пока это качество не будет достигнуто или превышено",
|
||||
"AuthenticationRequiredWarning": "Чтобы предотвратить удаленный доступ без авторизации, {appName} теперь требует, чтобы авторизация была включена. При желании вы можете отключить авторизацию с локальных адресов.",
|
||||
"AutoTagging": "Автоматическая маркировка",
|
||||
"AutoTaggingLoadError": "Не удается загрузить автоматическую маркировку",
|
||||
"AutoTaggingRequiredHelpText": "Это условие {implementationName} должно соответствовать правилу автоматической пометки. В противном случае достаточно одного совпадения {implementationName}",
|
||||
"Automatic": "Автоматически",
|
||||
"AutomaticAdd": "Автоматическое добавление",
|
||||
"AutomaticSearch": "Автоматический поиск",
|
||||
"Backup": "Резервное копирование",
|
||||
"BackupFolderHelpText": "Относительные пути будут в каталоге AppData {appName}",
|
||||
"Connection": "Подключение",
|
||||
"CountSeasons": "{count} Сезоны",
|
||||
"DownloadIgnored": "Загрузка игнорируется",
|
||||
"EditGroups": "Редактировать группы",
|
||||
"CalendarLoadError": "Не удалось загрузить календарь",
|
||||
"CertificateValidation": "Проверка сертификата",
|
||||
"DeleteQualityProfile": "Удалить качественный профиль",
|
||||
"DotNetVersion": ".NET",
|
||||
"DoneEditingGroups": "Группы редактирования сделаны",
|
||||
"CustomColonReplacementFormatHelpText": "Символы, которые будут использоваться вместо двоеточий",
|
||||
"CustomColonReplacementFormatHint": "Допустимый символ файловой системы, например двоеточие (буква)",
|
||||
"Debug": "Отладка",
|
||||
"DeleteEpisodeFileMessage": "Вы уверены, что хотите удалить '{path}'?",
|
||||
"DeleteEpisodesFiles": "Удалить файлы эпизодов ({episodeFileCount})",
|
||||
"DeleteImportListExclusion": "Удалить лист исключения для импорта",
|
||||
"DeleteIndexer": "Удалить индексер",
|
||||
"DeleteNotification": "Удалить уведомление",
|
||||
"DeleteSelectedEpisodeFiles": "Удалить выбранные эпизоды сериала",
|
||||
"DeleteSeriesFolderConfirmation": "Папка с сериалом '{path}' и все ее содержимое будут удалены.",
|
||||
"DeleteSeriesFolderCountConfirmation": "Вы уверены, что хотите удалить {count} выбранных эпизодов ?",
|
||||
"DeleteSeriesFolderCountWithFilesConfirmation": "Вы уверены, что хотите удалить {count} выбранных сериалов и все их содержимое?",
|
||||
"DeleteSeriesFolderEpisodeCount": "Файлы эпизодов: {episodeFileCount}, общим размером {size}",
|
||||
"DeleteSeriesFoldersHelpText": "Удалить папки сериалов и все их содержимое",
|
||||
"DeletedSeriesDescription": "Сериал был удален из TheTVDB",
|
||||
"DestinationRelativePath": "Относительный путь назначения",
|
||||
"DetailedProgressBarHelpText": "Показать текст на индикаторе выполнения",
|
||||
"Details": "Подробности",
|
||||
"DiskSpace": "Дисковое пространство",
|
||||
"DoNotBlocklist": "Не вносить в черный список",
|
||||
"DockerUpdater": "Обновить контейнер, чтобы получить обновление",
|
||||
"DownloadClientDelugeTorrentStateError": "Deluge сообщает об ошибке",
|
||||
"DownloadClientDelugeValidationLabelPluginInactive": "Плагин меток не активирован",
|
||||
"DownloadClientDownloadStationProviderMessage": "Приложение {appName} не может подключиться к Download Station, если в вашей учетной записи DSM включена двухфакторная аутентификация",
|
||||
"DownloadClientDownloadStationSettingsDirectoryHelpText": "Опциональная общая папка для размещения загрузок. Оставьте пустым, чтобы использовать каталог Download Station по умолчанию",
|
||||
"DownloadClientDownloadStationValidationApiVersion": "Версия Download Station API не поддерживается. Она должна быть не ниже {requiredVersion}. Поддерживается от {minVersion} до {maxVersion}",
|
||||
"DownloadClientDownloadStationValidationFolderMissing": "Папка не существует",
|
||||
"DownloadClientDownloadStationValidationFolderMissingDetail": "Папка '{downloadDir}' не существует, ее необходимо создать вручную внутри общей папки '{sharedFolder}'.",
|
||||
"DownloadClientDownloadStationValidationSharedFolderMissing": "Общая папка не существует",
|
||||
"DownloadClientFloodSettingsAdditionalTags": "Дополнительные теги",
|
||||
"DownloadClientFloodSettingsAdditionalTagsHelpText": "Добавляет свойства мультимедиа в виде тегов. Подсказки являются примерами",
|
||||
"DownloadClientFloodSettingsStartOnAdd": "Начать добавление",
|
||||
"DownloadClientFloodSettingsUrlBaseHelpText": "Добавляет префикс к Flood API, например {url}",
|
||||
"DownloadClientFreeboxAuthenticationError": "Не удалось выполнить аутентификацию в API Freebox. Причина: {errorDescription}",
|
||||
"DownloadClientFreeboxSettingsApiUrl": "URL-адрес API",
|
||||
"DownloadClientFreeboxSettingsAppTokenHelpText": "Токен приложения, полученный при создании доступа к API Freebox (т. е. 'app_token')",
|
||||
"DownloadClientFreeboxSettingsPortHelpText": "Порт, используемый для доступа к интерфейсу Freebox, по умолчанию — '{port}'",
|
||||
"DownloadClientFreeboxUnableToReachFreebox": "Невозможно получить доступ к API Freebox. Проверьте настройки «Хост», «Порт» или «Использовать SSL». (Ошибка: {exceptionMessage})",
|
||||
"DownloadClientNzbVortexMultipleFilesMessage": "Загрузка содержит несколько файлов и находится не в папке задания: {outputPath}",
|
||||
"DownloadClientNzbgetSettingsAddPausedHelpText": "Для этой опции требуется как минимум NzbGet версии 16.0",
|
||||
"DownloadClientNzbgetValidationKeepHistoryOverMaxDetail": "Для параметра NzbGet KeepHistory установлено слишком высокое значение",
|
||||
"DownloadClientNzbgetValidationKeepHistoryZero": "Параметр NzbGet KeepHistory должен быть больше 0",
|
||||
"DownloadClientOptionsLoadError": "Не удалось загрузить параметры клиента загрузки",
|
||||
"DownloadClientPneumaticSettingsNzbFolderHelpText": "Эта папка должна быть доступна из XBMC",
|
||||
"DownloadClientPneumaticSettingsStrmFolderHelpText": "Файлы .strm в этой папке будут импортированы дроном",
|
||||
"DownloadClientQbittorrentSettingsContentLayoutHelpText": "Использовать ли настроенный макет контента qBittorrent, исходный макет из торрента или всегда создавать подпапку (qBittorrent 4.3.2+)",
|
||||
"DownloadClientQbittorrentSettingsSequentialOrder": "Последовательный порядок",
|
||||
"DownloadClientQbittorrentSettingsUseSslHelpText": "Используйте безопасное соединение. См. «Параметры» -> «Веб-интерфейс» -> «Использовать HTTPS вместо HTTP» в qBittorrent",
|
||||
"DownloadClientQbittorrentTorrentStateDhtDisabled": "qBittorrent не может разрешить магнитную ссылку с отключенным DHT",
|
||||
"DownloadClientQbittorrentTorrentStateMetadata": "qBittorrent загружает метаданные",
|
||||
"DownloadClientQbittorrentTorrentStatePathError": "Невозможно импортировать. Путь соответствует базовому каталогу загрузки клиента, возможно, для этого торрента отключен параметр «Сохранить папку верхнего уровня» или для параметра «Макет содержимого торрента» НЕ установлено значение «Исходный» или «Создать подпапку»?",
|
||||
"DownloadClientQbittorrentTorrentStateUnknown": "Неизвестное состояние загрузки: {state}",
|
||||
"DownloadClientQbittorrentValidationCategoryRecommended": "Категория рекомендуется",
|
||||
"DownloadClientQbittorrentValidationRemovesAtRatioLimitDetail": "{appName} не сможет выполнить обработку завершенной загрузки, как настроено. Вы можете исправить это в qBittorrent («Инструменты -> Параметры...» в меню), изменив «Параметры -> BitTorrent -> Ограничение доли ресурсов» с «Удалить их» на «Приостановить их»",
|
||||
"DownloadClientSabnzbdValidationDevelopVersion": "Sabnzbd разрабатывает версию, предполагающую версию 3.0.0 или выше.",
|
||||
"DownloadClientSabnzbdValidationDevelopVersionDetail": "{appName} может не поддерживать новые функции, добавленные в SABnzbd при запуске разрабатываемых версий.",
|
||||
"DownloadClientSabnzbdValidationEnableJobFolders": "Включить папки заданий",
|
||||
"DownloadClientSeriesTagHelpText": "Используйте этот загрузочный клиент только для сериалов, имеющих хотя бы один соответствующий тег. Оставьте поле пустым, чтобы использовать его для всех серий.",
|
||||
"DownloadClientSettings": "Настройки клиента скачиваний",
|
||||
"DownloadClientValidationApiKeyIncorrect": "Ключ API неверен",
|
||||
"DownloadClientValidationCategoryMissingDetail": "Введенная вами категория не существует в {clientName}. Сначала создайте ее в {clientName}.",
|
||||
"DownloadClientValidationErrorVersion": "Версия {clientName} должна быть не ниже {requiredVersion}. Сообщенная версия: {reportedVersion}",
|
||||
"DownloadClientValidationUnableToConnectDetail": "Пожалуйста, проверьте имя хоста и порт.",
|
||||
"DownloadClientVuzeValidationErrorVersion": "Версия протокола не поддерживается, используйте Vuze 5.0.0.0 или выше с плагином Vuze Web Remote.",
|
||||
"DownloadIgnoredEpisodeTooltip": "Загрузка эпизода проигнорирована",
|
||||
"DownloadPropersAndRepacksHelpText": "Следует ли автоматически обновляться до Propers / Repacks",
|
||||
"EditDelayProfile": "Редактировать профиль задержки",
|
||||
"EditImportListExclusion": "Редактировать лист исключения для импорта",
|
||||
"EditListExclusion": "Изменить список исключений",
|
||||
"EnableAutomaticAddSeriesHelpText": "Добавляйте сериалы из этого списка в {appName}, когда синхронизация выполняется через пользовательский интерфейс или с помощью {appName}",
|
||||
"EnableAutomaticSearchHelpText": "Будет использовано для автоматических поисков через интерфейс или {appName}",
|
||||
"EndedOnly": "Только завершенные",
|
||||
"EpisodeFileDeletedTooltip": "Файл эпизода удален",
|
||||
"EpisodeHistoryLoadError": "Не удалось загрузить историю эпизодов",
|
||||
"EpisodeImported": "Эпизод импортирован",
|
||||
"EpisodeImportedTooltip": "Эпизод успешно загружен и получен из загрузочного клиента",
|
||||
"EpisodeIsDownloading": "Эпизод загружается",
|
||||
"EpisodeIsNotMonitored": "Эпизод не отслеживается",
|
||||
"EpisodeMissingAbsoluteNumber": "Эпизод не имеет абсолютного номера эпизода",
|
||||
"EpisodeNaming": "Именование эпизодов",
|
||||
"EpisodeNumbers": "Номер(а) эпизодов",
|
||||
"EpisodeSearchResultsLoadError": "Невозможно загрузить результаты для этого поискового запроса. Повторите попытку позже",
|
||||
"Episodes": "Эпизоды",
|
||||
"DownloadClientQbittorrentSettingsSequentialOrderHelpText": "Скачать в последовательном порядке (qBittorrent 4.1.0+)",
|
||||
"DownloadClientQbittorrentSettingsContentLayout": "Макет контента",
|
||||
"DownloadClientQbittorrentValidationCategoryUnsupported": "Категория не поддерживается",
|
||||
"DownloadClientValidationGroupMissingDetail": "Введенная вами группа не существует в {clientName}. Сначала создайте ее в {clientName}.",
|
||||
"DownloadClientValidationGroupMissing": "Группа не существует",
|
||||
"DownloadClientValidationSslConnectFailure": "Невозможно подключиться через SSL",
|
||||
"EnableAutomaticSearchHelpTextWarning": "Будет использовано при автоматических поисках",
|
||||
"EnableCompletedDownloadHandlingHelpText": "Автоматически импортировать завершенные скачивания",
|
||||
"EnableMetadataHelpText": "Включить создание файла метаданных для этого типа метаданных",
|
||||
"EnableProfile": "Включить профиль",
|
||||
"EndedSeriesDescription": "Никаких дополнительных эпизодов и сезонов не ожидается",
|
||||
"EpisodeAirDate": "Дата выхода эпизода",
|
||||
"EpisodeGrabbedTooltip": "Эпизод получен из {indexer} и отправлен в {downloadClient}",
|
||||
"CheckDownloadClientForDetails": "проверьте клиент загрузки для более подробной информации",
|
||||
"AudioInfo": "Информация о аудио",
|
||||
"ChmodFolderHelpText": "Восьмеричный, применяется при импорте / переименовании к медиа-папкам и файлам (без битов выполнения)",
|
||||
"Close": "Закрыть",
|
||||
"AuthenticationRequiredHelpText": "Отредактируйте, для каких запросов требуется аутентификация. Не меняйте, пока не поймете все риски.",
|
||||
"Day": "День",
|
||||
"UpdateStartupNotWritableHealthCheckMessage": "Невозможно установить обновление так как загрузочная папка '{startupFolder}' недоступна для записи для пользователя '{userName}'.",
|
||||
"UpdateUiNotWritableHealthCheckMessage": "Невозможно установить обновление так как UI папка '{uiFolder}' недоступна для записи для пользователя '{userName}'.",
|
||||
"UpdateStartupTranslocationHealthCheckMessage": "Не удается установить обновление, поскольку папка автозагрузки \"{startupFolder}\" находится в папке перемещения приложений.",
|
||||
"Uptime": "Время работы",
|
||||
"Uppercase": "Верхний регистр",
|
||||
"UpgradeUntilCustomFormatScoreEpisodeHelpText": "{appName} перестанет скачивать фильмы после достижения указанного количества очков",
|
||||
"UpgradeUntilCustomFormatScore": "Обновлять до пользовательской оценки",
|
||||
"UsenetDelayHelpText": "Задержка в минутах перед получением релиза из Usenet",
|
||||
"UsenetDelay": "Usenet задержки",
|
||||
"UseHardlinksInsteadOfCopy": "Используйте жесткие ссылки вместо копирования",
|
||||
"UrlBase": "Базовый URL",
|
||||
"VideoCodec": "Видео кодеки",
|
||||
"VersionNumber": "Версия {version}",
|
||||
"Version": "Версия",
|
||||
"UsenetDisabled": "Usenet отключён",
|
||||
"Wanted": "Разыскиваемый",
|
||||
"WaitingToProcess": "Ожидает обработки",
|
||||
"WaitingToImport": "Ожидание импорта",
|
||||
"VisitTheWikiForMoreDetails": "Перейти в wiki: ",
|
||||
"Continuing": "В стадии показа (или между сезонами)",
|
||||
"BlackholeFolderHelpText": "Папка, в которой {appName} будет хранить файл {extension}",
|
||||
"BlackholeWatchFolder": "Смотреть папку",
|
||||
"Category": "Категория",
|
||||
"ChangeFileDateHelpText": "Заменить дату файла при импорте/сканировании",
|
||||
"ChmodFolder": "chmod Папка",
|
||||
"ChownGroup": "chown группа",
|
||||
"ChownGroupHelpText": "Имя группы или id. Используйте id для отдалённой файловой системы.",
|
||||
"Condition": "Условие",
|
||||
"CountSelectedFile": "{selectedCount} выбранный файл",
|
||||
"EpisodeFileRenamed": "Файл эпизода переименован",
|
||||
"DeleteBackup": "Удалить резервную копию",
|
||||
"Cutoff": "Прекращение",
|
||||
"DeleteSelectedSeries": "Удалить выбранный сериал",
|
||||
"DeleteSeriesModalHeader": "Удалить - {title}",
|
||||
"DeleteTag": "Удалить тэг",
|
||||
"CutoffUnmet": "Порог невыполнен",
|
||||
"Dates": "Даты",
|
||||
"DefaultCase": "Случай по умолчанию",
|
||||
"DelayProfiles": "Профиль задержки",
|
||||
"DeleteEpisodeFile": "Удалить файл эпизода",
|
||||
"DownloadClientFloodSettingsPostImportTags": "Пост-импортные теги",
|
||||
"DownloadClientSettingsRecentPriority": "Недавний приоритет",
|
||||
"DeleteCustomFormat": "Удалить пользовательский формат",
|
||||
"DeleteDelayProfile": "Удалить профиль задержки",
|
||||
"DownloadClientSettingsInitialState": "Начальное состояние",
|
||||
"DownloadClientSettingsOlderPriority": "Более старый приоритет",
|
||||
"DownloadClientValidationVerifySsl": "Проверьте настройки SSL",
|
||||
"DownloadClientValidationUnknownException": "Неизвестное исключение: {exception}",
|
||||
"Agenda": "План",
|
||||
"Apply": "Применить",
|
||||
"ApplyTags": "Применить тэги",
|
||||
"DownloadClientSettingsUrlBaseHelpText": "Добавляет префикс к URL-адресу {clientName}, например {url}",
|
||||
"UpgradeUntilEpisodeHelpText": "{appName} перестанет скачивать фильмы после достижения указанного качества",
|
||||
"UseProxy": "Использовать прокси",
|
||||
"AddNewSeriesHelpText": "Добавить новый сериал очень просто! Начни печатать название сериала, который хочешь добавить",
|
||||
"AddNewSeriesSearchForCutoffUnmetEpisodes": "Начать поиск отклонённых эпизодов",
|
||||
"EditSelectedSeries": "Редактировать выбранный сериал",
|
||||
"CompletedDownloadHandling": "Обработка завершенных скачиваний",
|
||||
"Component": "Компонент",
|
||||
"CustomFilters": "Настраиваемые фильтры",
|
||||
"ApplicationURL": "URL-адрес приложения",
|
||||
"ApplicationUrlHelpText": "Внешний URL-адрес этого приложения, включая http(s)://, порт и базовый URL-адрес",
|
||||
"Disabled": "Выключено",
|
||||
"DownloadClientSabnzbdValidationEnableDisableTvSortingDetail": "Вы должны отключить сортировку сериалов для категории, которую использует {appName}, чтобы избежать проблем с импортом. Отправляйтесь в Sabnzbd, чтобы исправить это.",
|
||||
"UseSeasonFolder": "Использовать папку сезона",
|
||||
"AutoTaggingSpecificationTag": "Тэг",
|
||||
"BlocklistReleaseHelpText": "Блокирует повторную загрузку этого релиза пользователем {appName} через RSS или автоматический поиск",
|
||||
"BuiltIn": "Встроено",
|
||||
"BypassProxyForLocalAddresses": "Обход прокси для локальных адресов",
|
||||
"ChangeCategory": "Изменить категорию",
|
||||
"Clear": "Очистить",
|
||||
"ClearBlocklistMessageText": "Вы уверены, что хотите удалить выбранные элементы из черного списка?",
|
||||
"ClickToChangeEpisode": "Нажмите, чтобы изменить эпизод",
|
||||
"ClickToChangeLanguage": "Нажмите чтобы сменить язык",
|
||||
"Clone": "Клонировать",
|
||||
"CollapseMultipleEpisodes": "Свернуть несколько эпизодов",
|
||||
"DeleteSeriesFolderHelpText": "Удалить папку с сериалом и её содержимое",
|
||||
"DestinationPath": "Путь назначения",
|
||||
"DeleteSeriesFolders": "Удалить папки сериалов",
|
||||
"DownloadClientQbittorrentTorrentStateError": "qBittorrent сообщает об ошибке",
|
||||
"DownloadClientQbittorrentTorrentStateStalled": "Загрузка останавливается без подключения",
|
||||
"DownloadClientFreeboxSettingsAppToken": "Токен приложения",
|
||||
"DownloadClientValidationApiKeyRequired": "Требуется ключ API",
|
||||
"DownloadClientSettingsOlderPriorityEpisodeHelpText": "Приоритет при выборе эпизодов, вышедших в эфир более 14 дней назад",
|
||||
"DownloadClientsSettingsSummary": "Программы для скачивания, обработка скаченного и отдалённые ссылки",
|
||||
"Duplicate": "Дублировать",
|
||||
"EditSeriesModalHeader": "Изменить – {title}",
|
||||
"EditRestriction": "Редактировать ограничения",
|
||||
"EpisodeFileRenamedTooltip": "Файл эпизода переименован",
|
||||
"EnableColorImpairedModeHelpText": "Измененный стиль, позволяющий пользователям с нарушением цвета лучше различать информацию с цветовой кодировкой",
|
||||
"EpisodeTitleRequiredHelpText": "Запретить импорт на срок до 48 часов, если название эпизода указано в формате имени, а название эпизода будет объявлено позднее",
|
||||
"AutoTaggingSpecificationGenre": "Жанры",
|
||||
"AutoTaggingSpecificationMaximumYear": "Максимальный год",
|
||||
"AutoTaggingSpecificationMinimumYear": "Минимальный год",
|
||||
"CalendarLegendEpisodeUnmonitoredTooltip": "Эпизод не отслеживается",
|
||||
"CalendarLegendSeriesFinaleTooltip": "Финал сериала или сезона",
|
||||
"CalendarLegendSeriesPremiereTooltip": "Премьера сериала или сезона",
|
||||
"ChmodFolderHelpTextWarning": "Это работает только если пользователь {appName} является владельцем файла. Проверьте, что программа для скачивания установила правильные разрешения.",
|
||||
"ChooseImportMode": "Выберите режим импорта",
|
||||
"ClearBlocklist": "Очистить черный список",
|
||||
"ClickToChangeReleaseGroup": "Нажмите, чтобы изменить релиз-группу",
|
||||
"CustomFormatJson": "Настраиваемый формат JSON",
|
||||
"CustomFormats": "Настраиваемое форматирование",
|
||||
"CustomFormatUnknownConditionOption": "Неизвестный параметр «{key}» для условия '{implementation}'",
|
||||
"CustomFormatsSpecificationMinimumSizeHelpText": "Релиз должен быть больше этого размера",
|
||||
"CustomFormatsSpecificationMaximumSizeHelpText": "Релиз должен быть меньше или равен этому размеру",
|
||||
"CustomFormatsSpecificationRegularExpressionHelpText": "RegEx пользовательского формата не чувствителен к регистру",
|
||||
"Date": "Дата",
|
||||
"DelayProfileProtocol": "Протокол: {preferredProtocol}",
|
||||
"DownloadClientQbittorrentSettingsFirstAndLastFirstHelpText": "Сначала скачайте первую и последнюю части (qBittorrent 4.1.0+)",
|
||||
"DownloadClientValidationVerifySslDetail": "Проверьте конфигурацию SSL на {clientName} и {appName}",
|
||||
"EnableSsl": "Включить SSL",
|
||||
"Enable": "Включить",
|
||||
"AnimeEpisodeTypeFormat": "Абсолютный номер эпизода ({format})",
|
||||
"CalendarLegendEpisodeDownloadedTooltip": "Сериал скачан и отсортирован",
|
||||
"CalendarLegendEpisodeDownloadingTooltip": "Эпизод в настоящее время загружается",
|
||||
"AutoRedownloadFailed": "Неудачное скачивание",
|
||||
"AutoRedownloadFailedFromInteractiveSearch": "Не удалось выполнить повторную загрузку из интерактивного поиска",
|
||||
"AutoRedownloadFailedHelpText": "Автоматически искать и пытаться скачать разные релизы",
|
||||
"BeforeUpdate": "До обновления",
|
||||
"CalendarFeed": "Лента календаря {appName}",
|
||||
"AddRootFolderError": "Невозможно загрузить корневые папки",
|
||||
"BlocklistReleases": "Релиз из черного списка",
|
||||
"BypassDelayIfAboveCustomFormatScore": "Пропустить, если значение больше пользовательского формата",
|
||||
"CalendarLegendEpisodeMissingTooltip": "Эпизод вышел в эфир и отсутствует на диске",
|
||||
"CalendarLegendEpisodeOnAirTooltip": "Эпизод сейчас в эфире",
|
||||
"CalendarLegendEpisodeUnairedTooltip": "Эпизод еще не вышел в эфир",
|
||||
"Cancel": "Отменить",
|
||||
"Destination": "Место назначения",
|
||||
"DownloadClientRTorrentSettingsUrlPathHelpText": "Путь к конечной точке XMLRPC см. в {url}. Обычно это RPC2 или [путь к ruTorrent]{url2} при использовании ruTorrent",
|
||||
"DownloadClientSabnzbdValidationEnableDisableMovieSorting": "Отключить сортировку фильмов",
|
||||
"DownloadClientSabnzbdValidationEnableDisableDateSorting": "Отключить сортировку дат",
|
||||
"DownloadClientUTorrentTorrentStateError": "uTorrent сообщает об ошибке",
|
||||
"DownloadClientPneumaticSettingsNzbFolder": "Nzb папка",
|
||||
"DownloadClientPneumaticSettingsStrmFolder": "Strm папка",
|
||||
"DownloadClientFreeboxSettingsHostHelpText": "Имя хоста или IP-адрес хоста Freebox, по умолчанию — '{url}' (будет работать только в той же сети)",
|
||||
"DownloadClientSettingsDestinationHelpText": "Вручную указывает место назначения загрузки. Оставьте поле пустым, чтобы использовать значение по умолчанию",
|
||||
"Downloading": "Скачивается",
|
||||
"DownloadClientSettingsInitialStateHelpText": "Исходное состояние торрентов, добавленных в {clientName}",
|
||||
"Warn": "Предупреждение",
|
||||
"CustomFormatsSettingsTriggerInfo": "Пользовательский формат будет применен к релизу или файлу, если он соответствует хотя бы одному из каждого из выбранных типов условий",
|
||||
"DownloadClientQbittorrentValidationCategoryRecommendedDetail": "{appName} не будет пытаться импортировать завершенные загрузки без категории",
|
||||
"DownloadClientRTorrentSettingsUrlPath": "URL-путь",
|
||||
"DownloadClientSettingsUseSslHelpText": "Использовать безопасное соединение при подключении к {clientName}",
|
||||
"DownloadClientSettingsCategorySubFolderHelpText": "Добавление категории, специфичной для {appName}, позволяет избежать конфликтов с несвязанными загрузками, не относящимися к {appName}. Использование категории не является обязательным, но настоятельно рекомендуется. Создает подкаталог [category] в выходном каталоге.",
|
||||
"DownloadClientSabnzbdValidationUnknownVersion": "Неизвестная версия: {rawVersion}",
|
||||
"DownloadClientSettingsAddPaused": "Добавить приостановленное",
|
||||
"DownloadClientSabnzbdValidationEnableDisableTvSorting": "Отключить сортировку сериалов",
|
||||
"DownloadClientSabnzbdValidationEnableJobFoldersDetail": "{appName} предпочитает, чтобы каждая загрузка имела отдельную папку. Если к папке/пути добавлен *, Sabnzbd не будет создавать эти папки заданий. Отправляйтесь в Sabnzbd, чтобы исправить это.",
|
||||
"ConnectionLost": "Соединение прервано",
|
||||
"CleanLibraryLevel": "Очистить уровень библиотеки",
|
||||
"ColonReplacementFormatHelpText": "Изменить как {appName} обрабатывает замену двоеточий",
|
||||
"ConnectionLostReconnect": "{appName} попытается соединиться автоматически или нажмите кнопку внизу.",
|
||||
"ConnectSettingsSummary": "Уведомления, подключения к серверам/проигрывателям и настраиваемые скрипты",
|
||||
"ContinuingOnly": "Только в стадии показа",
|
||||
"Connections": "Соединения",
|
||||
"DailyEpisodeTypeDescription": "Эпизоды, выходящие ежедневно или реже, в которых используются год-месяц-день (2023-08-04)",
|
||||
"DailyEpisodeTypeFormat": "Дата ({format})",
|
||||
"Database": "База данных",
|
||||
"DefaultDelayProfileSeries": "Это профиль по умолчанию. Он относится ко всем сериалам, у которых нет явного профиля",
|
||||
"DefaultNameCopiedProfile": "{name} - Копировать",
|
||||
"DeleteEmptyFolders": "Удалить пустые папки",
|
||||
"CutoffUnmetLoadError": "Ошибка при загрузке элементов не выполнивших порог",
|
||||
"DoNotBlocklistHint": "Удалить без внесения в черный список",
|
||||
"DoNotUpgradeAutomatically": "Не обновлять автоматически",
|
||||
"DownloadClientDelugeSettingsUrlBaseHelpText": "Добавляет префикс к URL-адресу json deluge, см. {url}",
|
||||
"EpisodeInfo": "Информация об эпизоде",
|
||||
"DownloadClientDelugeSettingsDirectory": "Каталог загрузки",
|
||||
"DownloadClientDelugeSettingsDirectoryHelpText": "Опциональное место для загрузок. Оставьте пустым, чтобы использовать каталог Deluge по умолчанию",
|
||||
"DownloadClientDownloadStationValidationSharedFolderMissingDetail": "На Diskstation нет общей папки с именем '{sharedFolder}', вы уверены, что указали ее правильно?",
|
||||
"DownloadClientDelugeSettingsDirectoryCompleted": "Переместить каталог по завершении",
|
||||
"DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Опциональное место для перемещения завершенных загрузок. Оставьте пустым, чтобы использовать местоположение Deluge по умолчанию",
|
||||
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Подтвердите новый пароль",
|
||||
"BypassDelayIfHighestQuality": "Игнорировать при максимальном качестве",
|
||||
"BypassDelayIfHighestQualityHelpText": "Игнорирование задержки, когда выпуск имеет максимальное качество в выбранном профиле качества с предпочитаемым протоколом",
|
||||
"UseSsl": "Использовать SSL",
|
||||
"DownloadClientQbittorrentTorrentStateMissingFiles": "qBittorrent сообщает об отсутствующих файлах",
|
||||
"DownloadClientRTorrentSettingsDirectoryHelpText": "Опциональное место для загрузок. Оставьте пустым, чтобы использовать каталог rTorrent по умолчанию",
|
||||
"BlocklistAndSearch": "Черный список и поиск",
|
||||
"EnableSslHelpText": "Требуется перезапуск от администратора",
|
||||
"EpisodeDownloaded": "Эпизод скачан",
|
||||
"DeleteCustomFormatMessageText": "Вы уверены, что хотите удалить пользовательский формат '{name}'?",
|
||||
"AddNewSeries": "Добавить новый эпизод",
|
||||
"AddNewSeriesSearchForMissingEpisodes": "Начать поиск отсутствующих эпизодов",
|
||||
"AddRemotePathMappingError": "Не удалось добавить новый удаленный путь, попробуйте еще раз.",
|
||||
"AddSeriesWithTitle": "Добавить {title}",
|
||||
"AllSeriesInRootFolderHaveBeenImported": "Все сериалы из {path} были импортированы",
|
||||
"AllTitles": "Все заголовки",
|
||||
"AnimeEpisodeTypeDescription": "Эпизоды выпущены с использованием абсолютного номера эпизода",
|
||||
"ApiKey": "API ключ",
|
||||
"AptUpdater": "Используйте apt для установки обновления",
|
||||
"AuthenticationMethodHelpText": "Необходим логин и пароль для доступа в {appName}",
|
||||
"AutoAdd": "Автоматическое добавление",
|
||||
"AutoTaggingSpecificationOriginalLanguage": "Язык",
|
||||
"AutoTaggingSpecificationQualityProfile": "Профиль качества",
|
||||
"AutoTaggingSpecificationRootFolder": "Корневой каталог",
|
||||
"AutoTaggingSpecificationSeriesType": "Тип эпизода",
|
||||
"AutoTaggingSpecificationStatus": "Статус",
|
||||
"BlocklistAndSearchHint": "Начать поиск для замены после внесения в черный список",
|
||||
"BlocklistMultipleOnlyHint": "Черный список без поиска замен",
|
||||
"BlocklistOnly": "Только черный список",
|
||||
"BlocklistOnlyHint": "Черный список без поиска замен",
|
||||
"BrowserReloadRequired": "Требуется перезагрузка браузера",
|
||||
"DatabaseMigration": "Перенос БД",
|
||||
"CountSelectedFiles": "{selectedCount} выбранные файлы",
|
||||
"CountSeriesSelected": "{count} сериалов выбрано",
|
||||
"CustomFormat": "Настраиваемый формат",
|
||||
"EpisodeFilesLoadError": "Невозможно загрузить файлы эпизодов",
|
||||
"EpisodeHasNotAired": "Эпизод не вышел в эфир",
|
||||
"EpisodeMissingFromDisk": "Эпизод отсутствует на диске",
|
||||
"EpisodeProgress": "Прогресс эпизода",
|
||||
"EditReleaseProfile": "Редактировать профиль релиза",
|
||||
"BlocklistFilterHasNoItems": "Выбранный фильтр черного списка не содержит элементов",
|
||||
"ClickToChangeReleaseType": "Нажмите, чтобы изменить тип релиза",
|
||||
"ClickToChangeIndexerFlags": "Нажмите, чтобы изменить флаги индексатора",
|
||||
"CloneCustomFormat": "Клонировать пользовательский формат",
|
||||
"CollapseAll": "Свернуть Все",
|
||||
"CollapseMultipleEpisodesHelpText": "Свернуть несколько серий, выходящих в эфир в один и тот же день",
|
||||
"ConnectSettings": "Настройки соединения",
|
||||
"ContinuingSeriesDescription": "Ожидается больше эпизодов/еще один сезон",
|
||||
"CopyUsingHardlinksSeriesHelpText": "Прямые ссылки позволяют {appName} импортировать исходные торренты в папку с сериалами, не занимая дополнительного места на диске и не копируя все содержимое файла. Прямые ссылки будут работать только в том случае, если источник и пункт назначения находятся на одном томе",
|
||||
"DefaultNotFoundMessage": "Вы, должно быть, заблудились, здесь не на что смотреть.",
|
||||
"CustomFormatsSpecificationFlag": "Флаг",
|
||||
"DefaultNameCopiedSpecification": "{name} - Копировать",
|
||||
"Delete": "Удалить",
|
||||
"DownloadClientFreeboxSettingsAppId": "Идентификатор приложения",
|
||||
"AddNewSeriesRootFolderHelpText": "Подпапка '{folder}' будет создана автоматически",
|
||||
"AnalyseVideoFilesHelpText": "Извлекать из файлов видео разрешение, длительность и информацию о кодеках. Для этого потребуется, чтобы {appName} прочитал часть файла, что может вызвать высокую активность диска и сети во время сканирования.",
|
||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "Как применить теги к выбранным клиентам загрузки",
|
||||
"ChangeCategoryHint": "Перенести загружаемое в «Категорию после импорта» из клиента загрузки",
|
||||
"ChownGroupHelpTextWarning": "Это работает только если пользователь {appName} является владельцем файла. Проверьте, что программа для скачивания использует туже самую группу, что и {appName}.",
|
||||
"ConditionUsingRegularExpressions": "Это условие соответствует использованию регулярных выражений. Обратите внимание, что символы `\\^$.|?*+()[{` имеют особое значение и требуют экранирования с помощью `\\`",
|
||||
"CreateEmptySeriesFoldersHelpText": "Создать папки для не найденных сериалов при сканировании",
|
||||
"DownloadClientValidationAuthenticationFailureDetail": "Пожалуйста, подтвердите свое имя пользователя и пароль. Также проверьте, не заблокирован ли хост, на котором работает {appName}, доступ к {clientName} ограничениями белого списка в конфигурации {clientName}.",
|
||||
"AirsTomorrowOn": "Завтра в {time} на {networkLabel}",
|
||||
"AlternateTitles": "Альтернативное название",
|
||||
"ApplyTagsHelpTextHowToApplySeries": "Как применить теги к выбранным сериалам",
|
||||
"ChangeCategoryMultipleHint": "Перенести загружаемое в «Категорию после импорта» из клиента загрузки",
|
||||
"DownloadClientFloodSettingsRemovalInfo": "{appName} будет автоматически удалять торренты на основе текущих критериев раздачи в Настройки -> Индексаторы",
|
||||
"DownloadClientValidationSslConnectFailureDetail": "{appName} не может подключиться к {clientName} с помощью SSL. Эта проблема может быть связана с компьютером. Попробуйте настроить {appName} и {clientName} так, чтобы они не использовали SSL.",
|
||||
"AnEpisodeIsDownloading": "Эпизод загружается",
|
||||
"ConnectionLostToBackend": "{appName} потерял связь с сервером и его необходимо перезагрузить, чтобы восстановить работоспособность.",
|
||||
"CustomFormatHelpText": "{appName} оценивает каждый релиз используя сумму баллов по пользовательским форматам. Если новый релиз улучшит оценку, того же или лучшего качества, то Sonarr запишет его.",
|
||||
"AuthenticationMethodHelpTextWarning": "Пожалуйста, выберите действительный метод аутентификации",
|
||||
"CustomFormatUnknownCondition": "Неизвестное условие пользовательского формата '{implementation}'",
|
||||
"DownloadClientFloodSettingsTagsHelpText": "Начальные теги загрузки. Чтобы быть распознанным, загрузка должна иметь все начальные теги. Это позволяет избежать конфликтов с несвязанными загрузками",
|
||||
"DownloadPropersAndRepacksHelpTextCustomFormat": "Используйте 'Не предпочитать' для сортировки по рейтингу пользовательского формата по сравнению с Propers / Repacks",
|
||||
"DownloadPropersAndRepacksHelpTextWarning": "Используйте настраиваемое форматирование для автоматических обновлений до Проперов/Репаков",
|
||||
"AutoRedownloadFailedFromInteractiveSearchHelpText": "Автоматический поиск и попытка загрузки другого релиза, если неудачный релиз был получен из интерактивного поиска",
|
||||
"EnableHelpText": "Включить создание файла метаданных для этого типа метаданных",
|
||||
"AutoTaggingNegateHelpText": "Если отмечено, то настроенный формат не будет применён при условии {implementationName} .",
|
||||
"DownloadClientFreeboxUnableToReachFreeboxApi": "Невозможно получить доступ к API Freebox. Проверьте настройку «URL-адрес API» для базового URL-адреса и версии",
|
||||
"EnableInteractiveSearchHelpText": "Будет использовано при интерактивном поиске",
|
||||
"DelayProfileSeriesTagsHelpText": "Применимо к сериаламс хотя бы одним подходящим тегом",
|
||||
"EnableMediaInfoHelpText": "Извлекать из файлов видео разрешение, длительность и информацию о кодеках. Для этого требуется, чтобы {appName} прочитал часть файла, что может вызвать высокую активность диска или сети во время сканирования",
|
||||
"AutomaticUpdatesDisabledDocker": "Автоматические обновления напрямую не поддерживаются при использовании механизма обновления Docker. Вам нужно будет обновить образ контейнера за пределами {appName} или использовать скрипт",
|
||||
"DelayingDownloadUntil": "Приостановить скачивание до {date} в {time}",
|
||||
"DownloadClientNzbgetValidationKeepHistoryZeroDetail": "Для параметра NzbGet KeepHistory установлено значение 0. Это не позволяет {appName} видеть завершенные загрузки.",
|
||||
"EnableRssHelpText": "Будет использоваться, когда {appName} будет периодически искать выпуски через RSS Sync",
|
||||
"BindAddress": "Привязать адрес",
|
||||
"DeleteEmptySeriesFoldersHelpText": "Удалять пустые папки во время сканирования диска, а так же после удаления файлов сериала",
|
||||
"EpisodeRequested": "Запрошен эпизод",
|
||||
"BindAddressHelpText": "Действительный IP-адрес, локальный адрес или '*' для всех интерфейсов",
|
||||
"DeleteEpisodesFilesHelpText": "Удалить файлы и папку сериала",
|
||||
"DownloadClientPriorityHelpText": "Приоритет клиента загрузки от 1 (самый высокий) до 50 (самый низкий). По умолчанию: 1. Для клиентов с одинаковым приоритетом используется циклический перебор",
|
||||
"EpisodeTitleFootNote": "При необходимости можно управлять усечением до максимального количества байтов, включая многоточие (`...`). Поддерживается усечение как с конца (например, `{Episode Title:30}`), так и с начала (например, `{Episode Title:-30}`). При необходимости названия эпизодов будут автоматически обрезаны в соответствии с ограничениями файловой системы.",
|
||||
"BlackholeWatchFolderHelpText": "Папка, из которой {appName} должно импортировать завершенные загрузки",
|
||||
"DeleteReleaseProfileMessageText": "Вы действительно хотите удалить профиль релиза '{name}'?",
|
||||
"BlocklistAndSearchMultipleHint": "Начать поиск для замены после внесения в черный список",
|
||||
"DeletedReasonEpisodeMissingFromDisk": "{appName} не смог найти файл на диске, поэтому файл был откреплён от эпизода в базе данных",
|
||||
"DownloadClientQbittorrentSettingsInitialStateHelpText": "Исходное состояние торрентов, добавленных в qBittorrent. Обратите внимание, что принудительные торренты не подчиняются ограничениям на раздачу",
|
||||
"BlocklistRelease": "Релиз из черного списка",
|
||||
"DeletedReasonManual": "Файл был удален с помощью {appName} вручную или с помощью другого инструмента через API",
|
||||
"BranchUpdateMechanism": "Ветвь, используемая внешним механизмом обновления",
|
||||
"DownloadClientQbittorrentValidationQueueingNotEnabledDetail": "Очередь торрентов не включена в настройках qBittorrent. Включите его в qBittorrent или выберите «Последний» в качестве приоритета",
|
||||
"DownloadClientDelugeValidationLabelPluginFailureDetail": "Пользователю {appName} не удалось добавить метку к клиенту {clientName}",
|
||||
"DownloadClientDelugeValidationLabelPluginInactiveDetail": "Чтобы использовать категории, у вас должен быть включен плагин меток в {clientName}",
|
||||
"DownloadClientRTorrentProviderMessage": "rTorrent не будет приостанавливать торренты, если они соответствуют критериям раздачи. {appName} будет обрабатывать автоматическое удаление торрентов на основе текущих критериев раздачи в Настройки->Индексаторы, только если включена опция «Удаление завершенных». После импорта он также установит {importedView} в качестве представления rTorrent, которое можно использовать в сценариях rTorrent для настройки поведения",
|
||||
"DownloadClientRTorrentSettingsAddStoppedHelpText": "Включение добавит торренты и магниты в rTorrent в остановленном состоянии. Это может привести к поломке магнет файлов",
|
||||
"DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "Клиент загрузки {downloadClientName} настроен на удаление завершенных загрузок. Это может привести к удалению загрузок из вашего клиента до того, как {appName} сможет их импортировать",
|
||||
"DownloadClientSabnzbdValidationCheckBeforeDownloadDetail": "Использование функции «Проверка перед загрузкой» влияет на возможность приложения {appName} отслеживать новые загрузки. Также Sabnzbd рекомендует вместо этого «Отменять задания, которые невозможно завершить», поскольку это более эффективно",
|
||||
"DownloadClientSabnzbdValidationEnableDisableDateSortingDetail": "Вы должны отключить сортировку по дате для категории, которую использует {appName}, чтобы избежать проблем с импортом. Отправляйтесь в Sabnzbd, чтобы исправить это.",
|
||||
"DownloadClientSettingsCategoryHelpText": "Добавление категории, специфичной для {appName}, позволяет избежать конфликтов с несвязанными загрузками, не относящимися к {appName}. Использование категории не является обязательным, но настоятельно рекомендуется.",
|
||||
"DownloadClientSettingsPostImportCategoryHelpText": "Категория для приложения {appName}, которую необходимо установить после импорта загрузки. {appName} не удалит торренты в этой категории, даже если раздача завершена. Оставьте пустым, чтобы сохранить ту же категорию.",
|
||||
"DownloadClientSettingsRecentPriorityEpisodeHelpText": "Приоритет при выборе эпизодов, вышедших в эфир за последние 14 дней",
|
||||
"DayOfWeekAt": "{day} в {time}",
|
||||
"DeleteDownloadClient": "Удалить программу для скачивания",
|
||||
"MetadataSourceSettings": "Настройки источника метаданных",
|
||||
"MetadataSettingsSeriesMetadataUrl": "URL-адрес метаданных сериала",
|
||||
"MetadataSettingsSeriesMetadataEpisodeGuide": "Руководство по эпизодам метаданных сериала",
|
||||
"MetadataSource": "Источник метаданных",
|
||||
"MetadataSourceSettingsSeriesSummary": "Информация о том, откуда {appName} получает информацию о сериалах и эпизодах",
|
||||
"MetadataXmbcSettingsEpisodeMetadataImageThumbsHelpText": "Включите теги миниатюр изображений в <имя файла>.nfo (требуются метаданные эпизода)",
|
||||
"MetadataXmbcSettingsSeriesMetadataEpisodeGuideHelpText": "Включить элемент руководства по эпизодам в формате JSON в tvshow.nfo (требуются «метаданные сериала»)",
|
||||
"MetadataSettingsSeriesSummary": "Создавать файлы метаданных при импорте эпизодов или обновлении сериалов",
|
||||
"MetadataXmbcSettingsSeriesMetadataHelpText": "tvshow.nfo с полными метаданными сериала"
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
"AppUpdated": "{appName} Güncellendi",
|
||||
"ApplicationURL": "Uygulama URL'si",
|
||||
"ApplyTagsHelpTextAdd": "Ekle: Etiketleri mevcut etiket listesine ekleyin",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "Seçilen indeksleyicilere etiketler nasıl uygulanır?",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "Seçilen indeksleyicilere etiketler nasıl uygulanır",
|
||||
"ApplyTagsHelpTextRemove": "Kaldır: Girilen etiketleri kaldırın",
|
||||
"AuthenticationRequiredPasswordHelpTextWarning": "Yeni şifre girin",
|
||||
"AuthenticationRequiredUsernameHelpTextWarning": "Yeni kullanıcı adınızı girin",
|
||||
@@ -88,8 +88,8 @@
|
||||
"CustomFormatUnknownConditionOption": "'{implementation}' koşulu için bilinmeyen seçenek '{key}'",
|
||||
"AutoTagging": "Otomatik Etiketleme",
|
||||
"AutoTaggingNegateHelpText": "İşaretlenirse, {implementationName} koşulu eşleştiğinde otomatik etiketleme kuralı uygulanmayacaktır.",
|
||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "Seçilen indirme istemcilerine etiketler nasıl uygulanır?",
|
||||
"ApplyTagsHelpTextHowToApplyImportLists": "Seçilen içe aktarma listelerine etiketler nasıl uygulanır?",
|
||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "Seçilen indirme istemcilerine etiketler nasıl uygulanır",
|
||||
"ApplyTagsHelpTextHowToApplyImportLists": "Seçilen içe aktarma listelerine etiketler nasıl uygulanır",
|
||||
"AuthenticationRequiredHelpText": "İstekler için Kimlik doğrulamanın gereklilik ayarını değiştirin. Riskleri anlamadığınız sürece değiştirmeyin.",
|
||||
"AutoTaggingLoadError": "Otomatik etiketleme yüklenemiyor",
|
||||
"BypassDelayIfAboveCustomFormatScore": "Özel Format Koşullarının Üstündeyse Baypas Et",
|
||||
@@ -837,5 +837,15 @@
|
||||
"UpdateAutomaticallyHelpText": "Güncelleştirmeleri otomatik olarak indirip yükleyin. Sistem: Güncellemeler'den yükleme yapmaya devam edebileceksiniz",
|
||||
"Wanted": "Arananlar",
|
||||
"Cutoff": "Kesinti",
|
||||
"Required": "Gerekli"
|
||||
"Required": "Gerekli",
|
||||
"AirsTbaOn": "Daha sonra duyurulacak {networkLabel}'de",
|
||||
"AllFiles": "Tüm dosyalar",
|
||||
"AllSeriesAreHiddenByTheAppliedFilter": "Tüm sonuçlar, uygulanan filtre tarafından gizlenir",
|
||||
"Always": "Her zaman",
|
||||
"AirsDateAtTimeOn": "{date} saat {time} {networkLabel}'de",
|
||||
"AllResultsAreHiddenByTheAppliedFilter": "Tüm sonuçlar, uygulanan filtre tarafından gizlenir",
|
||||
"AllSeriesInRootFolderHaveBeenImported": "{path} içerisindeki tüm diziler içeri aktarıldı",
|
||||
"AlternateTitles": "Alternatif Başlıklar",
|
||||
"AnEpisodeIsDownloading": "Bir bölüm indiriliyor",
|
||||
"UnableToImportAutomatically": "Otomatikman İçe Aktarılamıyor"
|
||||
}
|
||||
|
||||
@@ -345,5 +345,27 @@
|
||||
"DownloadClientDelugeValidationLabelPluginInactive": "Плагін міток не активовано",
|
||||
"DownloadClientAriaSettingsDirectoryHelpText": "Додаткове розташування для розміщення завантажень. Залиште поле порожнім, щоб використовувати стандартне розташування Aria2",
|
||||
"CustomFormatHelpText": "{appName} оцінює кожен випуск, використовуючи суму балів для відповідності користувацьких форматів. Якщо новий випуск покращить оцінку, з такою ж або кращою якістю, {appName} схопить його.",
|
||||
"DownloadClientDownloadStationSettingsDirectoryHelpText": "Додаткова спільна папка для розміщення завантажень. Залиште поле порожнім, щоб використовувати стандартне розташування Download Station"
|
||||
"DownloadClientDownloadStationSettingsDirectoryHelpText": "Додаткова спільна папка для розміщення завантажень. Залиште поле порожнім, щоб використовувати стандартне розташування Download Station",
|
||||
"ClickToChangeIndexerFlags": "Натисніть, щоб змінити прапорці індексатора",
|
||||
"AutoTaggingLoadError": "Не вдалося завантажити автоматичне маркування",
|
||||
"CountDownloadClientsSelected": "Вибрано {count} клієнтів завантажувача",
|
||||
"CountImportListsSelected": "Вибрано {count} списків імпорту",
|
||||
"AutoTagging": "Автоматичне маркування",
|
||||
"CloneCondition": "Клонування умови",
|
||||
"AutomaticAdd": "Автоматичне додавання",
|
||||
"BypassDelayIfAboveCustomFormatScoreMinimumScoreHelpText": "Мінімальна оцінка користувацького формату, необхідна для обходу затримки для обраного протоколу",
|
||||
"ChownGroup": "chown Група",
|
||||
"BlocklistMultipleOnlyHint": "Додати до чорного списку без пошуку замін",
|
||||
"BlocklistOnly": "Тільки чорний список",
|
||||
"BlocklistOnlyHint": "Додати до чорного списку без пошуку заміни",
|
||||
"ChangeCategoryHint": "Змінює завантаження на «Категорію після імпорту» з клієнта завантажувача",
|
||||
"ChangeCategoryMultipleHint": "Змінює завантаження на «Категорію після імпорту» з клієнта завантажувача",
|
||||
"BlocklistAndSearchHint": "Розпочати пошук заміни після додавання до чорного списку",
|
||||
"BlocklistAndSearchMultipleHint": "Розпочати пошук замін після додавання до чорного списку",
|
||||
"AutoRedownloadFailedFromInteractiveSearchHelpText": "Автоматично шукати та намагатися завантажити інший реліз, якщо обраний реліз не вдалось завантажити з інтерактивного пошуку.",
|
||||
"BlackholeWatchFolder": "Папка для спостереження",
|
||||
"BypassDelayIfAboveCustomFormatScore": "Пропустити, якщо перевищено оцінку користувацького формату",
|
||||
"Clone": "Клонування",
|
||||
"BlocklistFilterHasNoItems": "Вибраний фільтр чорного списку не містить елементів",
|
||||
"BypassDelayIfAboveCustomFormatScoreHelpText": "Увімкнути обхід, якщо реліз має оцінку вищу за встановлений мінімальний бал користувацького формату"
|
||||
}
|
||||
|
||||
@@ -1309,7 +1309,6 @@
|
||||
"OnApplicationUpdate": "程序更新时",
|
||||
"OnHealthIssue": "健康度异常",
|
||||
"OnHealthRestored": "健康度恢复",
|
||||
"OnImport": "导入中",
|
||||
"Password": "密码",
|
||||
"PendingDownloadClientUnavailable": "挂起 - 下载客户端不可用",
|
||||
"QueueLoadError": "加载队列失败",
|
||||
@@ -1373,7 +1372,6 @@
|
||||
"LanguagesLoadError": "无法加载语言",
|
||||
"MonitorMissingEpisodes": "缺失剧集",
|
||||
"OneMinute": "1分钟",
|
||||
"OnUpgrade": "升级中",
|
||||
"RemotePathMappings": "远程路径映射",
|
||||
"RemotePathMappingsLoadError": "无法加载远程路径映射",
|
||||
"RemoveQueueItemConfirmation": "您确定要从队列中移除“{sourceTitle}”吗?",
|
||||
|
||||
@@ -171,7 +171,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||
}
|
||||
|
||||
episodeFile = _mediaFileService.Add(episodeFile);
|
||||
importResults.Add(new ImportResult(importDecision));
|
||||
importResults.Add(new ImportResult(importDecision, episodeFile));
|
||||
|
||||
if (newDownload)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||
public class ImportResult
|
||||
{
|
||||
public ImportDecision ImportDecision { get; private set; }
|
||||
public EpisodeFile EpisodeFile { get; private set; }
|
||||
public List<string> Errors { get; private set; }
|
||||
|
||||
public ImportResultType Result
|
||||
@@ -34,5 +35,14 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||
ImportDecision = importDecision;
|
||||
Errors = errors.ToList();
|
||||
}
|
||||
|
||||
public ImportResult(ImportDecision importDecision, EpisodeFile episodeFile)
|
||||
{
|
||||
Ensure.That(importDecision, () => importDecision).IsNotNull();
|
||||
|
||||
ImportDecision = importDecision;
|
||||
EpisodeFile = episodeFile;
|
||||
Errors = new List<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,14 +395,6 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
||||
item.Name = Path.GetFileNameWithoutExtension(decision.LocalEpisode.Path);
|
||||
item.DownloadId = downloadId;
|
||||
|
||||
if (decision.LocalEpisode.Series != null)
|
||||
{
|
||||
item.Series = decision.LocalEpisode.Series;
|
||||
|
||||
item.CustomFormats = _formatCalculator.ParseCustomFormat(decision.LocalEpisode);
|
||||
item.CustomFormatScore = item.Series.QualityProfile?.Value.CalculateCustomFormatScore(item.CustomFormats) ?? 0;
|
||||
}
|
||||
|
||||
if (decision.LocalEpisode.Episodes.Any() && decision.LocalEpisode.Episodes.Select(c => c.SeasonNumber).Distinct().Count() == 1)
|
||||
{
|
||||
var seasons = decision.LocalEpisode.Episodes.Select(c => c.SeasonNumber).Distinct().ToList();
|
||||
@@ -430,6 +422,14 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
||||
item.IndexerFlags = (int)decision.LocalEpisode.IndexerFlags;
|
||||
item.ReleaseType = decision.LocalEpisode.ReleaseType;
|
||||
|
||||
if (decision.LocalEpisode.Series != null)
|
||||
{
|
||||
item.Series = decision.LocalEpisode.Series;
|
||||
|
||||
item.CustomFormats = _formatCalculator.ParseCustomFormat(decision.LocalEpisode);
|
||||
item.CustomFormatScore = item.Series.QualityProfile?.Value.CalculateCustomFormatScore(item.CustomFormats) ?? 0;
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -506,8 +506,6 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
||||
|
||||
// Augment episode file so imported files have all additional information an automatic import would
|
||||
localEpisode = _aggregationService.Augment(localEpisode, trackedDownload?.DownloadItem);
|
||||
localEpisode.CustomFormats = _formatCalculator.ParseCustomFormat(localEpisode);
|
||||
localEpisode.CustomFormatScore = localEpisode.Series.QualityProfile?.Value.CalculateCustomFormatScore(localEpisode.CustomFormats) ?? 0;
|
||||
|
||||
// Apply the user-chosen values.
|
||||
localEpisode.Series = series;
|
||||
@@ -518,6 +516,9 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
||||
localEpisode.IndexerFlags = (IndexerFlags)file.IndexerFlags;
|
||||
localEpisode.ReleaseType = file.ReleaseType;
|
||||
|
||||
localEpisode.CustomFormats = _formatCalculator.ParseCustomFormat(localEpisode);
|
||||
localEpisode.CustomFormatScore = localEpisode.Series.QualityProfile?.Value.CalculateCustomFormatScore(localEpisode.CustomFormats) ?? 0;
|
||||
|
||||
// TODO: Cleanup non-tracked downloads
|
||||
|
||||
var importDecision = new ImportDecision(localEpisode);
|
||||
@@ -545,6 +546,24 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
||||
_logger.ProgressTrace("Manually imported {0} files", imported.Count);
|
||||
}
|
||||
|
||||
var untrackedImports = imported.Where(i => importedTrackedDownload.FirstOrDefault(t => t.ImportResult != i) == null).ToList();
|
||||
|
||||
if (untrackedImports.Any())
|
||||
{
|
||||
foreach (var groupedUntrackedImport in untrackedImports.GroupBy(i => new { i.EpisodeFile.SeriesId, i.EpisodeFile.SeasonNumber }))
|
||||
{
|
||||
var localEpisodes = groupedUntrackedImport.Select(u => u.ImportDecision.LocalEpisode).ToList();
|
||||
var episodeFiles = groupedUntrackedImport.Select(u => u.EpisodeFile).ToList();
|
||||
var localEpisode = localEpisodes.First();
|
||||
var series = localEpisode.Series;
|
||||
var sourcePath = localEpisodes.Select(l => l.Path).ToList().GetLongestCommonPath();
|
||||
var episodes = localEpisodes.SelectMany(l => l.Episodes).ToList();
|
||||
var parsedEpisodeInfo = localEpisode.FolderEpisodeInfo ?? localEpisode.FileEpisodeInfo;
|
||||
|
||||
_eventAggregator.PublishEvent(new UntrackedDownloadCompletedEvent(series, episodes, episodeFiles, parsedEpisodeInfo, sourcePath));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var groupedTrackedDownload in importedTrackedDownload.GroupBy(i => i.TrackedDownload.DownloadItem.DownloadId).ToList())
|
||||
{
|
||||
var trackedDownload = groupedTrackedDownload.First().TrackedDownload;
|
||||
@@ -561,15 +580,20 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
||||
}
|
||||
}
|
||||
|
||||
var allEpisodesImported = groupedTrackedDownload.Select(c => c.ImportResult)
|
||||
.Where(c => c.Result == ImportResultType.Imported)
|
||||
var importedResults = groupedTrackedDownload.Select(c => c.ImportResult)
|
||||
.Where(c => c.Result == ImportResultType.Imported)
|
||||
.ToList();
|
||||
|
||||
var allEpisodesImported = importedResults
|
||||
.SelectMany(c => c.ImportDecision.LocalEpisode.Episodes).Count() >=
|
||||
Math.Max(1, trackedDownload.RemoteEpisode?.Episodes?.Count ?? 1);
|
||||
|
||||
if (allEpisodesImported)
|
||||
{
|
||||
var episodeFiles = importedResults.Select(i => i.EpisodeFile).ToList();
|
||||
|
||||
trackedDownload.State = TrackedDownloadState.Imported;
|
||||
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload, importedSeries.Id));
|
||||
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload, importedSeries.Id, episodeFiles, importedResults.First().ImportDecision.LocalEpisode.Release));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,11 @@ namespace NzbDrone.Core.Notifications.Apprise
|
||||
_proxy.SendNotification(EPISODE_DOWNLOADED_TITLE, message.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
_proxy.SendNotification(IMPORT_COMPLETE_TITLE, message.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage)
|
||||
{
|
||||
_proxy.SendNotification(EPISODE_DELETED_TITLE, deleteMessage.Message, Settings);
|
||||
|
||||
@@ -96,6 +96,7 @@ namespace NzbDrone.Core.Notifications.CustomScript
|
||||
environmentVariables.Add("Sonarr_Download_Id", message.DownloadId ?? string.Empty);
|
||||
environmentVariables.Add("Sonarr_Release_CustomFormat", string.Join("|", remoteEpisode.CustomFormats));
|
||||
environmentVariables.Add("Sonarr_Release_CustomFormatScore", remoteEpisode.CustomFormatScore.ToString());
|
||||
environmentVariables.Add("Sonarr_Release_ReleaseType", remoteEpisode.ParsedEpisodeInfo.ReleaseType.ToString());
|
||||
|
||||
ExecuteScript(environmentVariables);
|
||||
}
|
||||
@@ -158,6 +159,7 @@ namespace NzbDrone.Core.Notifications.CustomScript
|
||||
environmentVariables.Add("Sonarr_Release_Indexer", message.Release?.Indexer);
|
||||
environmentVariables.Add("Sonarr_Release_Size", message.Release?.Size.ToString());
|
||||
environmentVariables.Add("Sonarr_Release_Title", message.Release?.Title);
|
||||
environmentVariables.Add("Sonarr_Release_ReleaseType", message.Release?.ReleaseType.ToString() ?? string.Empty);
|
||||
|
||||
if (message.OldFiles.Any())
|
||||
{
|
||||
@@ -170,6 +172,65 @@ namespace NzbDrone.Core.Notifications.CustomScript
|
||||
ExecuteScript(environmentVariables);
|
||||
}
|
||||
|
||||
public override void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
var series = message.Series;
|
||||
var episodes = message.Episodes;
|
||||
var episodeFiles = message.EpisodeFiles;
|
||||
var sourcePath = message.SourcePath;
|
||||
var environmentVariables = new StringDictionary();
|
||||
|
||||
environmentVariables.Add("Sonarr_EventType", "Download");
|
||||
environmentVariables.Add("Sonarr_InstanceName", _configFileProvider.InstanceName);
|
||||
environmentVariables.Add("Sonarr_ApplicationUrl", _configService.ApplicationUrl);
|
||||
environmentVariables.Add("Sonarr_Series_Id", series.Id.ToString());
|
||||
environmentVariables.Add("Sonarr_Series_Title", series.Title);
|
||||
environmentVariables.Add("Sonarr_Series_TitleSlug", series.TitleSlug);
|
||||
environmentVariables.Add("Sonarr_Series_Path", series.Path);
|
||||
environmentVariables.Add("Sonarr_Series_TvdbId", series.TvdbId.ToString());
|
||||
environmentVariables.Add("Sonarr_Series_TvMazeId", series.TvMazeId.ToString());
|
||||
environmentVariables.Add("Sonarr_Series_TmdbId", series.TmdbId.ToString());
|
||||
environmentVariables.Add("Sonarr_Series_ImdbId", series.ImdbId ?? string.Empty);
|
||||
environmentVariables.Add("Sonarr_Series_Type", series.SeriesType.ToString());
|
||||
environmentVariables.Add("Sonarr_Series_Year", series.Year.ToString());
|
||||
environmentVariables.Add("Sonarr_Series_OriginalLanguage", IsoLanguages.Get(series.OriginalLanguage).ThreeLetterCode);
|
||||
environmentVariables.Add("Sonarr_Series_Genres", string.Join("|", series.Genres));
|
||||
environmentVariables.Add("Sonarr_Series_Tags", string.Join("|", GetTagLabels(series)));
|
||||
environmentVariables.Add("Sonarr_EpisodeFile_Ids", string.Join("|", episodeFiles.Select(f => f.Id)));
|
||||
environmentVariables.Add("Sonarr_EpisodeFile_Count", message.EpisodeFiles.Count.ToString());
|
||||
environmentVariables.Add("Sonarr_EpisodeFile_RelativePaths", string.Join("|", episodeFiles.Select(f => f.RelativePath)));
|
||||
environmentVariables.Add("Sonarr_EpisodeFile_Paths", string.Join("|", episodeFiles.Select(f => Path.Combine(series.Path, f.RelativePath))));
|
||||
environmentVariables.Add("Sonarr_EpisodeFile_EpisodeIds", string.Join(",", episodes.Select(e => e.Id)));
|
||||
environmentVariables.Add("Sonarr_EpisodeFile_SeasonNumber", episodes.First().SeasonNumber.ToString());
|
||||
environmentVariables.Add("Sonarr_EpisodeFile_EpisodeNumbers", string.Join(",", episodes.Select(e => e.EpisodeNumber)));
|
||||
environmentVariables.Add("Sonarr_EpisodeFile_EpisodeAirDates", string.Join(",", episodes.Select(e => e.AirDate)));
|
||||
environmentVariables.Add("Sonarr_EpisodeFile_EpisodeAirDatesUtc", string.Join(",", episodes.Select(e => e.AirDateUtc)));
|
||||
environmentVariables.Add("Sonarr_EpisodeFile_EpisodeTitles", string.Join("|", episodes.Select(e => e.Title)));
|
||||
environmentVariables.Add("Sonarr_EpisodeFile_EpisodeOverviews", string.Join("|", episodes.Select(e => e.Overview)));
|
||||
environmentVariables.Add("Sonarr_EpisodeFile_Qualities", string.Join("|", episodeFiles.Select(f => f.Quality.Quality.Name)));
|
||||
environmentVariables.Add("Sonarr_EpisodeFile_QualityVersions", string.Join("|", episodeFiles.Select(f => f.Quality.Revision.Version)));
|
||||
environmentVariables.Add("Sonarr_EpisodeFile_ReleaseGroups", string.Join("|", episodeFiles.Select(f => f.ReleaseGroup)));
|
||||
environmentVariables.Add("Sonarr_EpisodeFile_SceneNames", string.Join("|", episodeFiles.Select(f => f.SceneName)));
|
||||
environmentVariables.Add("Sonarr_Download_Client", message.DownloadClientInfo?.Name ?? string.Empty);
|
||||
environmentVariables.Add("Sonarr_Download_Client_Type", message.DownloadClientInfo?.Type ?? string.Empty);
|
||||
environmentVariables.Add("Sonarr_Download_Id", message.DownloadId ?? string.Empty);
|
||||
environmentVariables.Add("Sonarr_Release_Group", message.ReleaseGroup ?? string.Empty);
|
||||
environmentVariables.Add("Sonarr_Release_Quality", message.ReleaseQuality.Quality.Name);
|
||||
environmentVariables.Add("Sonarr_Release_QualityVersion", message.ReleaseQuality.Revision.Version.ToString());
|
||||
environmentVariables.Add("Sonarr_Release_Indexer", message.Release?.Indexer ?? string.Empty);
|
||||
environmentVariables.Add("Sonarr_Release_Size", message.Release?.Size.ToString() ?? string.Empty);
|
||||
environmentVariables.Add("Sonarr_Release_Title", message.Release?.Title ?? string.Empty);
|
||||
|
||||
// Prefer the release type from the release, otherwise use the first imported file (useful for untracked manual imports)
|
||||
environmentVariables.Add("Sonarr_Release_ReleaseType", message.Release == null ? message.EpisodeFiles.First().ReleaseType.ToString() : message.Release.ReleaseType.ToString());
|
||||
environmentVariables.Add("Sonarr_SourcePath", sourcePath);
|
||||
environmentVariables.Add("Sonarr_SourceFolder", Path.GetDirectoryName(sourcePath));
|
||||
environmentVariables.Add("Sonarr_DestinationPath", message.DestinationPath);
|
||||
environmentVariables.Add("Sonarr_DestinationFolder", Path.GetDirectoryName(message.DestinationPath));
|
||||
|
||||
ExecuteScript(environmentVariables);
|
||||
}
|
||||
|
||||
public override void OnRename(Series series, List<RenamedEpisodeFile> renamedFiles)
|
||||
{
|
||||
var environmentVariables = new StringDictionary();
|
||||
|
||||
@@ -236,6 +236,96 @@ namespace NzbDrone.Core.Notifications.Discord
|
||||
_proxy.SendPayload(payload, Settings);
|
||||
}
|
||||
|
||||
public override void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
var series = message.Series;
|
||||
var episodes = message.Episodes;
|
||||
|
||||
var embed = new Embed
|
||||
{
|
||||
Author = new DiscordAuthor
|
||||
{
|
||||
Name = Settings.Author.IsNullOrWhiteSpace() ? Environment.MachineName : Settings.Author,
|
||||
IconUrl = "https://raw.githubusercontent.com/Sonarr/Sonarr/develop/Logo/256.png"
|
||||
},
|
||||
Url = $"http://thetvdb.com/?tab=series&id={series.TvdbId}",
|
||||
Description = "Import Complete",
|
||||
Title = GetTitle(series, episodes),
|
||||
Color = (int)DiscordColors.Success,
|
||||
Fields = new List<DiscordField>(),
|
||||
Timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
|
||||
};
|
||||
|
||||
if (Settings.ImportFields.Contains((int)DiscordImportFieldType.Poster))
|
||||
{
|
||||
embed.Thumbnail = new DiscordImage
|
||||
{
|
||||
Url = series.Images.FirstOrDefault(x => x.CoverType == MediaCoverTypes.Poster)?.RemoteUrl
|
||||
};
|
||||
}
|
||||
|
||||
if (Settings.ImportFields.Contains((int)DiscordImportFieldType.Fanart))
|
||||
{
|
||||
embed.Image = new DiscordImage
|
||||
{
|
||||
Url = series.Images.FirstOrDefault(x => x.CoverType == MediaCoverTypes.Fanart)?.RemoteUrl
|
||||
};
|
||||
}
|
||||
|
||||
foreach (var field in Settings.ImportFields)
|
||||
{
|
||||
var discordField = new DiscordField();
|
||||
|
||||
switch ((DiscordImportFieldType)field)
|
||||
{
|
||||
case DiscordImportFieldType.Overview:
|
||||
var overview = episodes.First().Overview ?? "";
|
||||
discordField.Name = "Overview";
|
||||
discordField.Value = overview.Length <= 300 ? overview : $"{overview.AsSpan(0, 300)}...";
|
||||
break;
|
||||
case DiscordImportFieldType.Rating:
|
||||
discordField.Name = "Rating";
|
||||
discordField.Value = episodes.First().Ratings.Value.ToString();
|
||||
break;
|
||||
case DiscordImportFieldType.Genres:
|
||||
discordField.Name = "Genres";
|
||||
discordField.Value = series.Genres.Take(5).Join(", ");
|
||||
break;
|
||||
case DiscordImportFieldType.Quality:
|
||||
discordField.Name = "Quality";
|
||||
discordField.Inline = true;
|
||||
discordField.Value = message.ReleaseQuality.Quality.Name;
|
||||
break;
|
||||
case DiscordImportFieldType.Group:
|
||||
discordField.Name = "Group";
|
||||
discordField.Value = message.ReleaseGroup;
|
||||
break;
|
||||
case DiscordImportFieldType.Size:
|
||||
discordField.Name = "Size";
|
||||
discordField.Value = BytesToString(message.Release?.Size ?? message.EpisodeFiles.Sum(f => f.Size));
|
||||
discordField.Inline = true;
|
||||
break;
|
||||
case DiscordImportFieldType.Release:
|
||||
discordField.Name = "Release";
|
||||
discordField.Value = $"```{message.Release?.Title ?? message.SourceTitle}```";
|
||||
break;
|
||||
case DiscordImportFieldType.Links:
|
||||
discordField.Name = "Links";
|
||||
discordField.Value = GetLinksString(series);
|
||||
break;
|
||||
}
|
||||
|
||||
if (discordField.Name.IsNotNullOrWhiteSpace() && discordField.Value.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
embed.Fields.Add(discordField);
|
||||
}
|
||||
}
|
||||
|
||||
var payload = CreatePayload(null, new List<Embed> { embed });
|
||||
|
||||
_proxy.SendPayload(payload, Settings);
|
||||
}
|
||||
|
||||
public override void OnRename(Series series, List<RenamedEpisodeFile> renamedFiles)
|
||||
{
|
||||
var attachments = new List<Embed>
|
||||
|
||||
@@ -43,6 +43,13 @@ namespace NzbDrone.Core.Notifications.Email
|
||||
SendEmail(Settings, EPISODE_DOWNLOADED_TITLE_BRANDED, body);
|
||||
}
|
||||
|
||||
public override void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
var body = $"All expected episode files in {message.Message} downloaded and sorted.";
|
||||
|
||||
SendEmail(Settings, IMPORT_COMPLETE_TITLE, body);
|
||||
}
|
||||
|
||||
public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage)
|
||||
{
|
||||
var body = $"{deleteMessage.Message} deleted.";
|
||||
|
||||
@@ -36,6 +36,11 @@ namespace NzbDrone.Core.Notifications.Gotify
|
||||
SendNotification(EPISODE_DOWNLOADED_TITLE, message.Message, message.Series);
|
||||
}
|
||||
|
||||
public override void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
SendNotification(IMPORT_COMPLETE_TITLE, message.Message, message.Series);
|
||||
}
|
||||
|
||||
public override void OnEpisodeFileDelete(EpisodeDeleteMessage message)
|
||||
{
|
||||
SendNotification(EPISODE_DELETED_TITLE, message.Message, message.Series);
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace NzbDrone.Core.Notifications
|
||||
void OnGrab(GrabMessage grabMessage);
|
||||
void OnDownload(DownloadMessage message);
|
||||
void OnRename(Series series, List<RenamedEpisodeFile> renamedFiles);
|
||||
void OnImportComplete(ImportCompleteMessage message);
|
||||
void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage);
|
||||
void OnSeriesAdd(SeriesAddMessage message);
|
||||
void OnSeriesDelete(SeriesDeleteMessage deleteMessage);
|
||||
@@ -23,6 +24,7 @@ namespace NzbDrone.Core.Notifications
|
||||
bool SupportsOnGrab { get; }
|
||||
bool SupportsOnDownload { get; }
|
||||
bool SupportsOnUpgrade { get; }
|
||||
bool SupportsOnImportComplete { get; }
|
||||
bool SupportsOnRename { get; }
|
||||
bool SupportsOnSeriesAdd { get; }
|
||||
bool SupportsOnSeriesDelete { get; }
|
||||
|
||||
30
src/NzbDrone.Core/Notifications/ImportCompleteMessage.cs
Normal file
30
src/NzbDrone.Core/Notifications/ImportCompleteMessage.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Notifications
|
||||
{
|
||||
public class ImportCompleteMessage
|
||||
{
|
||||
public string Message { get; set; }
|
||||
public Series Series { get; set; }
|
||||
public List<Episode> Episodes { get; set; }
|
||||
public List<EpisodeFile> EpisodeFiles { get; set; }
|
||||
public string SourcePath { get; set; }
|
||||
public string SourceTitle { get; set; }
|
||||
public DownloadClientItemClientInfo DownloadClientInfo { get; set; }
|
||||
public string DownloadId { get; set; }
|
||||
public GrabbedReleaseInfo Release { get; set; }
|
||||
public string DestinationPath { get; set; }
|
||||
public string ReleaseGroup { get; set; }
|
||||
public QualityModel ReleaseQuality { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Message;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,11 @@ namespace NzbDrone.Core.Notifications.Join
|
||||
_proxy.SendNotification(EPISODE_DOWNLOADED_TITLE_BRANDED, message.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
_proxy.SendNotification(IMPORT_COMPLETE_TITLE_BRANDED, message.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage)
|
||||
{
|
||||
_proxy.SendNotification(EPISODE_DELETED_TITLE_BRANDED, deleteMessage.Message, Settings);
|
||||
|
||||
@@ -32,6 +32,11 @@ namespace NzbDrone.Core.Notifications.Mailgun
|
||||
_proxy.SendNotification(EPISODE_DOWNLOADED_TITLE, downloadMessage.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
_proxy.SendNotification(IMPORT_COMPLETE_TITLE, message.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage)
|
||||
{
|
||||
var body = $"{deleteMessage.Message} deleted.";
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Tv;
|
||||
@@ -9,10 +12,18 @@ namespace NzbDrone.Core.Notifications.Emby
|
||||
public class MediaBrowser : NotificationBase<MediaBrowserSettings>
|
||||
{
|
||||
private readonly IMediaBrowserService _mediaBrowserService;
|
||||
private readonly MediaServerUpdateQueue<MediaBrowser, string> _updateQueue;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public MediaBrowser(IMediaBrowserService mediaBrowserService)
|
||||
private static string Created = "Created";
|
||||
private static string Deleted = "Deleted";
|
||||
private static string Modified = "Modified";
|
||||
|
||||
public MediaBrowser(IMediaBrowserService mediaBrowserService, ICacheManager cacheManager, Logger logger)
|
||||
{
|
||||
_mediaBrowserService = mediaBrowserService;
|
||||
_updateQueue = new MediaServerUpdateQueue<MediaBrowser, string>(cacheManager);
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override string Link => "https://emby.media/";
|
||||
@@ -33,18 +44,22 @@ namespace NzbDrone.Core.Notifications.Emby
|
||||
_mediaBrowserService.Notify(Settings, EPISODE_DOWNLOADED_TITLE_BRANDED, message.Message);
|
||||
}
|
||||
|
||||
if (Settings.UpdateLibrary)
|
||||
UpdateIfEnabled(message.Series, Created);
|
||||
}
|
||||
|
||||
public override void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
if (Settings.Notify)
|
||||
{
|
||||
_mediaBrowserService.Update(Settings, message.Series, "Created");
|
||||
_mediaBrowserService.Notify(Settings, IMPORT_COMPLETE_TITLE_BRANDED, message.Message);
|
||||
}
|
||||
|
||||
UpdateIfEnabled(message.Series, Created);
|
||||
}
|
||||
|
||||
public override void OnRename(Series series, List<RenamedEpisodeFile> renamedFiles)
|
||||
{
|
||||
if (Settings.UpdateLibrary)
|
||||
{
|
||||
_mediaBrowserService.Update(Settings, series, "Modified");
|
||||
}
|
||||
UpdateIfEnabled(series, Modified);
|
||||
}
|
||||
|
||||
public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage)
|
||||
@@ -54,10 +69,7 @@ namespace NzbDrone.Core.Notifications.Emby
|
||||
_mediaBrowserService.Notify(Settings, EPISODE_DELETED_TITLE_BRANDED, deleteMessage.Message);
|
||||
}
|
||||
|
||||
if (Settings.UpdateLibrary)
|
||||
{
|
||||
_mediaBrowserService.Update(Settings, deleteMessage.Series, "Deleted");
|
||||
}
|
||||
UpdateIfEnabled(deleteMessage.Series, Deleted);
|
||||
}
|
||||
|
||||
public override void OnSeriesAdd(SeriesAddMessage message)
|
||||
@@ -67,10 +79,7 @@ namespace NzbDrone.Core.Notifications.Emby
|
||||
_mediaBrowserService.Notify(Settings, SERIES_ADDED_TITLE_BRANDED, message.Message);
|
||||
}
|
||||
|
||||
if (Settings.UpdateLibrary)
|
||||
{
|
||||
_mediaBrowserService.Update(Settings, message.Series, "Created");
|
||||
}
|
||||
UpdateIfEnabled(message.Series, Created);
|
||||
}
|
||||
|
||||
public override void OnSeriesDelete(SeriesDeleteMessage deleteMessage)
|
||||
@@ -80,10 +89,7 @@ namespace NzbDrone.Core.Notifications.Emby
|
||||
_mediaBrowserService.Notify(Settings, SERIES_DELETED_TITLE_BRANDED, deleteMessage.Message);
|
||||
}
|
||||
|
||||
if (Settings.UpdateLibrary)
|
||||
{
|
||||
_mediaBrowserService.Update(Settings, deleteMessage.Series, "Deleted");
|
||||
}
|
||||
UpdateIfEnabled(deleteMessage.Series, Deleted);
|
||||
}
|
||||
|
||||
public override void OnHealthIssue(HealthCheck.HealthCheck message)
|
||||
@@ -110,6 +116,34 @@ namespace NzbDrone.Core.Notifications.Emby
|
||||
}
|
||||
}
|
||||
|
||||
public override void ProcessQueue()
|
||||
{
|
||||
_updateQueue.ProcessQueue(Settings.Host, (items) =>
|
||||
{
|
||||
if (Settings.UpdateLibrary)
|
||||
{
|
||||
_logger.Debug("Performing library update for {0} series", items.Count);
|
||||
|
||||
items.ForEach(item =>
|
||||
{
|
||||
// If there is only one update type for the series use that, otherwise send null and let Emby decide
|
||||
var updateType = item.Info.Count == 1 ? item.Info.First() : null;
|
||||
|
||||
_mediaBrowserService.Update(Settings, item.Series, updateType);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateIfEnabled(Series series, string updateType)
|
||||
{
|
||||
if (Settings.UpdateLibrary)
|
||||
{
|
||||
_logger.Debug("Scheduling library update for series {0} {1}", series.Id, series.Title);
|
||||
_updateQueue.Add(Settings.Host, series, updateType);
|
||||
}
|
||||
}
|
||||
|
||||
public override ValidationResult Test()
|
||||
{
|
||||
var failures = new List<ValidationFailure>();
|
||||
|
||||
105
src/NzbDrone.Core/Notifications/MediaServerUpdateQueue.cs
Normal file
105
src/NzbDrone.Core/Notifications/MediaServerUpdateQueue.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Notifications
|
||||
{
|
||||
public class MediaServerUpdateQueue<TQueueHost, TItemInfo>
|
||||
where TQueueHost : class
|
||||
{
|
||||
private class UpdateQueue
|
||||
{
|
||||
public Dictionary<int, UpdateQueueItem<TItemInfo>> Pending { get; } = new Dictionary<int, UpdateQueueItem<TItemInfo>>();
|
||||
public bool Refreshing { get; set; }
|
||||
}
|
||||
|
||||
private readonly ICached<UpdateQueue> _pendingSeriesCache;
|
||||
|
||||
public MediaServerUpdateQueue(ICacheManager cacheManager)
|
||||
{
|
||||
_pendingSeriesCache = cacheManager.GetRollingCache<UpdateQueue>(typeof(TQueueHost), "pendingSeries", TimeSpan.FromDays(1));
|
||||
}
|
||||
|
||||
public void Add(string identifier, Series series, TItemInfo info)
|
||||
{
|
||||
var queue = _pendingSeriesCache.Get(identifier, () => new UpdateQueue());
|
||||
|
||||
lock (queue)
|
||||
{
|
||||
var item = queue.Pending.TryGetValue(series.Id, out var value)
|
||||
? value
|
||||
: new UpdateQueueItem<TItemInfo>(series);
|
||||
|
||||
item.Info.Add(info);
|
||||
|
||||
queue.Pending[series.Id] = item;
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessQueue(string identifier, Action<List<UpdateQueueItem<TItemInfo>>> update)
|
||||
{
|
||||
var queue = _pendingSeriesCache.Find(identifier);
|
||||
|
||||
if (queue == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (queue)
|
||||
{
|
||||
if (queue.Refreshing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
queue.Refreshing = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
List<UpdateQueueItem<TItemInfo>> items;
|
||||
|
||||
lock (queue)
|
||||
{
|
||||
if (queue.Pending.Empty())
|
||||
{
|
||||
queue.Refreshing = false;
|
||||
return;
|
||||
}
|
||||
|
||||
items = queue.Pending.Values.ToList();
|
||||
queue.Pending.Clear();
|
||||
}
|
||||
|
||||
update(items);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
lock (queue)
|
||||
{
|
||||
queue.Refreshing = false;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class UpdateQueueItem<TItemInfo>
|
||||
{
|
||||
public Series Series { get; set; }
|
||||
public HashSet<TItemInfo> Info { get; set; }
|
||||
|
||||
public UpdateQueueItem(Series series)
|
||||
{
|
||||
Series = series;
|
||||
Info = new HashSet<TItemInfo>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,11 @@ namespace NzbDrone.Core.Notifications.Notifiarr
|
||||
_proxy.SendNotification(BuildOnDownloadPayload(message), Settings);
|
||||
}
|
||||
|
||||
public override void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
_proxy.SendNotification(BuildOnImportCompletePayload(message), Settings);
|
||||
}
|
||||
|
||||
public override void OnRename(Series series, List<RenamedEpisodeFile> renamedFiles)
|
||||
{
|
||||
_proxy.SendNotification(BuildOnRenamePayload(series, renamedFiles), Settings);
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace NzbDrone.Core.Notifications
|
||||
{
|
||||
protected const string EPISODE_GRABBED_TITLE = "Episode Grabbed";
|
||||
protected const string EPISODE_DOWNLOADED_TITLE = "Episode Downloaded";
|
||||
protected const string IMPORT_COMPLETE_TITLE = "Import Complete";
|
||||
protected const string EPISODE_DELETED_TITLE = "Episode Deleted";
|
||||
protected const string SERIES_ADDED_TITLE = "Series Added";
|
||||
protected const string SERIES_DELETED_TITLE = "Series Deleted";
|
||||
@@ -22,6 +23,7 @@ namespace NzbDrone.Core.Notifications
|
||||
|
||||
protected const string EPISODE_GRABBED_TITLE_BRANDED = "Sonarr - " + EPISODE_GRABBED_TITLE;
|
||||
protected const string EPISODE_DOWNLOADED_TITLE_BRANDED = "Sonarr - " + EPISODE_DOWNLOADED_TITLE;
|
||||
protected const string IMPORT_COMPLETE_TITLE_BRANDED = "Sonarr - " + IMPORT_COMPLETE_TITLE;
|
||||
protected const string EPISODE_DELETED_TITLE_BRANDED = "Sonarr - " + EPISODE_DELETED_TITLE;
|
||||
protected const string SERIES_ADDED_TITLE_BRANDED = "Sonarr - " + SERIES_ADDED_TITLE;
|
||||
protected const string SERIES_DELETED_TITLE_BRANDED = "Sonarr - " + SERIES_DELETED_TITLE;
|
||||
@@ -51,6 +53,10 @@ namespace NzbDrone.Core.Notifications
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OnRename(Series series, List<RenamedEpisodeFile> renamedFiles)
|
||||
{
|
||||
}
|
||||
@@ -91,6 +97,7 @@ namespace NzbDrone.Core.Notifications
|
||||
public bool SupportsOnRename => HasConcreteImplementation("OnRename");
|
||||
public bool SupportsOnDownload => HasConcreteImplementation("OnDownload");
|
||||
public bool SupportsOnUpgrade => SupportsOnDownload;
|
||||
public bool SupportsOnImportComplete => HasConcreteImplementation("OnImportComplete");
|
||||
public bool SupportsOnSeriesAdd => HasConcreteImplementation("OnSeriesAdd");
|
||||
public bool SupportsOnSeriesDelete => HasConcreteImplementation("OnSeriesDelete");
|
||||
public bool SupportsOnEpisodeFileDelete => HasConcreteImplementation("OnEpisodeFileDelete");
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace NzbDrone.Core.Notifications
|
||||
public bool OnGrab { get; set; }
|
||||
public bool OnDownload { get; set; }
|
||||
public bool OnUpgrade { get; set; }
|
||||
public bool OnImportComplete { get; set; }
|
||||
public bool OnRename { get; set; }
|
||||
public bool OnSeriesAdd { get; set; }
|
||||
public bool OnSeriesDelete { get; set; }
|
||||
@@ -34,6 +35,9 @@ namespace NzbDrone.Core.Notifications
|
||||
[MemberwiseEqualityIgnore]
|
||||
public bool SupportsOnRename { get; set; }
|
||||
|
||||
[MemberwiseEqualityIgnore]
|
||||
public bool SupportsOnImportComplete { get; set; }
|
||||
|
||||
[MemberwiseEqualityIgnore]
|
||||
public bool SupportsOnSeriesAdd { get; set; }
|
||||
|
||||
@@ -59,7 +63,7 @@ namespace NzbDrone.Core.Notifications
|
||||
public bool SupportsOnManualInteractionRequired { get; set; }
|
||||
|
||||
[MemberwiseEqualityIgnore]
|
||||
public override bool Enable => OnGrab || OnDownload || (OnDownload && OnUpgrade) || OnRename || OnSeriesAdd || OnSeriesDelete || OnEpisodeFileDelete || (OnEpisodeFileDelete && OnEpisodeFileDeleteForUpgrade) || OnHealthIssue || OnHealthRestored || OnApplicationUpdate || OnManualInteractionRequired;
|
||||
public override bool Enable => OnGrab || OnDownload || (OnDownload && OnUpgrade) || OnImportComplete || OnRename || OnSeriesAdd || OnSeriesDelete || OnEpisodeFileDelete || (OnEpisodeFileDelete && OnEpisodeFileDeleteForUpgrade) || OnHealthIssue || OnHealthRestored || OnApplicationUpdate || OnManualInteractionRequired;
|
||||
|
||||
public bool Equals(NotificationDefinition other)
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace NzbDrone.Core.Notifications
|
||||
List<INotification> OnGrabEnabled(bool filterBlockedNotifications = true);
|
||||
List<INotification> OnDownloadEnabled(bool filterBlockedNotifications = true);
|
||||
List<INotification> OnUpgradeEnabled(bool filterBlockedNotifications = true);
|
||||
List<INotification> OnImportCompleteEnabled(bool filterBlockedNotifications = true);
|
||||
List<INotification> OnRenameEnabled(bool filterBlockedNotifications = true);
|
||||
List<INotification> OnSeriesAddEnabled(bool filterBlockedNotifications = true);
|
||||
List<INotification> OnSeriesDeleteEnabled(bool filterBlockedNotifications = true);
|
||||
@@ -71,6 +72,16 @@ namespace NzbDrone.Core.Notifications
|
||||
return GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnUpgrade).ToList();
|
||||
}
|
||||
|
||||
public List<INotification> OnImportCompleteEnabled(bool filterBlockedNotifications = true)
|
||||
{
|
||||
if (filterBlockedNotifications)
|
||||
{
|
||||
return FilterBlockedNotifications(GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnImportComplete)).ToList();
|
||||
}
|
||||
|
||||
return GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnImportComplete).ToList();
|
||||
}
|
||||
|
||||
public List<INotification> OnRenameEnabled(bool filterBlockedNotifications = true)
|
||||
{
|
||||
if (filterBlockedNotifications)
|
||||
@@ -184,6 +195,7 @@ namespace NzbDrone.Core.Notifications
|
||||
definition.SupportsOnGrab = provider.SupportsOnGrab;
|
||||
definition.SupportsOnDownload = provider.SupportsOnDownload;
|
||||
definition.SupportsOnUpgrade = provider.SupportsOnUpgrade;
|
||||
definition.SupportsOnImportComplete = provider.SupportsOnImportComplete;
|
||||
definition.SupportsOnRename = provider.SupportsOnRename;
|
||||
definition.SupportsOnSeriesAdd = provider.SupportsOnSeriesAdd;
|
||||
definition.SupportsOnSeriesDelete = provider.SupportsOnSeriesDelete;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
@@ -18,6 +19,8 @@ namespace NzbDrone.Core.Notifications
|
||||
public class NotificationService
|
||||
: IHandle<EpisodeGrabbedEvent>,
|
||||
IHandle<EpisodeImportedEvent>,
|
||||
IHandle<DownloadCompletedEvent>,
|
||||
IHandle<UntrackedDownloadCompletedEvent>,
|
||||
IHandle<SeriesRenamedEvent>,
|
||||
IHandle<SeriesAddCompletedEvent>,
|
||||
IHandle<SeriesDeletedEvent>,
|
||||
@@ -44,19 +47,7 @@ namespace NzbDrone.Core.Notifications
|
||||
|
||||
private string GetMessage(Series series, List<Episode> episodes, QualityModel quality)
|
||||
{
|
||||
var qualityString = quality.Quality.ToString();
|
||||
|
||||
if (quality.Revision.Version > 1)
|
||||
{
|
||||
if (series.SeriesType == SeriesTypes.Anime)
|
||||
{
|
||||
qualityString += " v" + quality.Revision.Version;
|
||||
}
|
||||
else
|
||||
{
|
||||
qualityString += " Proper";
|
||||
}
|
||||
}
|
||||
var qualityString = GetQualityString(series, quality);
|
||||
|
||||
if (series.SeriesType == SeriesTypes.Daily)
|
||||
{
|
||||
@@ -82,6 +73,35 @@ namespace NzbDrone.Core.Notifications
|
||||
qualityString);
|
||||
}
|
||||
|
||||
private string GetFullSeasonMessage(Series series, int seasonNumber, QualityModel quality)
|
||||
{
|
||||
var qualityString = GetQualityString(series, quality);
|
||||
|
||||
return string.Format("{0} - Season {1} [{2}]",
|
||||
series.Title,
|
||||
seasonNumber,
|
||||
qualityString);
|
||||
}
|
||||
|
||||
private string GetQualityString(Series series, QualityModel quality)
|
||||
{
|
||||
var qualityString = quality.Quality.ToString();
|
||||
|
||||
if (quality.Revision.Version > 1)
|
||||
{
|
||||
if (series.SeriesType == SeriesTypes.Anime)
|
||||
{
|
||||
qualityString += " v" + quality.Revision.Version;
|
||||
}
|
||||
else
|
||||
{
|
||||
qualityString += " Proper";
|
||||
}
|
||||
}
|
||||
|
||||
return qualityString;
|
||||
}
|
||||
|
||||
private bool ShouldHandleSeries(ProviderDefinition definition, Series series)
|
||||
{
|
||||
if (definition.Tags.Empty())
|
||||
@@ -189,6 +209,92 @@ namespace NzbDrone.Core.Notifications
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(DownloadCompletedEvent message)
|
||||
{
|
||||
var series = message.TrackedDownload.RemoteEpisode.Series;
|
||||
var episodes = message.TrackedDownload.RemoteEpisode.Episodes;
|
||||
var parsedEpisodeInfo = message.TrackedDownload.RemoteEpisode.ParsedEpisodeInfo;
|
||||
|
||||
var downloadMessage = new ImportCompleteMessage
|
||||
{
|
||||
Message = parsedEpisodeInfo.FullSeason
|
||||
? GetFullSeasonMessage(series, episodes.First().SeasonNumber, parsedEpisodeInfo.Quality)
|
||||
: GetMessage(series, episodes, parsedEpisodeInfo.Quality),
|
||||
Series = series,
|
||||
Episodes = episodes,
|
||||
EpisodeFiles = message.EpisodeFiles,
|
||||
DownloadClientInfo = message.TrackedDownload.DownloadItem.DownloadClientInfo,
|
||||
DownloadId = message.TrackedDownload.DownloadItem.DownloadId,
|
||||
Release = message.Release,
|
||||
SourcePath = message.TrackedDownload.DownloadItem.OutputPath.FullPath,
|
||||
DestinationPath = message.EpisodeFiles.Select(e => Path.Join(series.Path, e.RelativePath)).ToList().GetLongestCommonPath(),
|
||||
ReleaseGroup = parsedEpisodeInfo.ReleaseGroup,
|
||||
ReleaseQuality = parsedEpisodeInfo.Quality
|
||||
};
|
||||
|
||||
foreach (var notification in _notificationFactory.OnImportCompleteEnabled())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ShouldHandleSeries(notification.Definition, series))
|
||||
{
|
||||
if (((NotificationDefinition)notification.Definition).OnImportComplete)
|
||||
{
|
||||
notification.OnImportComplete(downloadMessage);
|
||||
_notificationStatusService.RecordSuccess(notification.Definition.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_notificationStatusService.RecordFailure(notification.Definition.Id);
|
||||
_logger.Warn(ex, "Unable to send OnImportComplete notification to: " + notification.Definition.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(UntrackedDownloadCompletedEvent message)
|
||||
{
|
||||
var series = message.Series;
|
||||
var episodes = message.Episodes;
|
||||
var parsedEpisodeInfo = message.ParsedEpisodeInfo;
|
||||
|
||||
var downloadMessage = new ImportCompleteMessage
|
||||
{
|
||||
Message = parsedEpisodeInfo.FullSeason
|
||||
? GetFullSeasonMessage(series, episodes.First().SeasonNumber, parsedEpisodeInfo.Quality)
|
||||
: GetMessage(series, episodes, parsedEpisodeInfo.Quality),
|
||||
Series = series,
|
||||
Episodes = episodes,
|
||||
EpisodeFiles = message.EpisodeFiles,
|
||||
SourcePath = message.SourcePath,
|
||||
SourceTitle = parsedEpisodeInfo.ReleaseTitle,
|
||||
DestinationPath = message.EpisodeFiles.Select(e => Path.Join(series.Path, e.RelativePath)).ToList().GetLongestCommonPath(),
|
||||
ReleaseGroup = parsedEpisodeInfo.ReleaseGroup,
|
||||
ReleaseQuality = parsedEpisodeInfo.Quality
|
||||
};
|
||||
|
||||
foreach (var notification in _notificationFactory.OnImportCompleteEnabled())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ShouldHandleSeries(notification.Definition, series))
|
||||
{
|
||||
if (((NotificationDefinition)notification.Definition).OnImportComplete)
|
||||
{
|
||||
notification.OnImportComplete(downloadMessage);
|
||||
_notificationStatusService.RecordSuccess(notification.Definition.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_notificationStatusService.RecordFailure(notification.Definition.Id);
|
||||
_logger.Warn(ex, "Unable to send OnImportComplete notification to: " + notification.Definition.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(SeriesRenamedEvent message)
|
||||
{
|
||||
foreach (var notification in _notificationFactory.OnRenameEnabled())
|
||||
|
||||
@@ -28,6 +28,11 @@ namespace NzbDrone.Core.Notifications.Ntfy
|
||||
_proxy.SendNotification(EPISODE_DOWNLOADED_TITLE_BRANDED, message.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
_proxy.SendNotification(IMPORT_COMPLETE_TITLE_BRANDED, message.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage)
|
||||
{
|
||||
_proxy.SendNotification(EPISODE_DELETED_TITLE, deleteMessage.Message, Settings);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
@@ -12,6 +13,7 @@ namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||
{
|
||||
string GetAuthToken(string clientIdentifier, int pinId);
|
||||
bool Ping(string clientIdentifier, string authToken);
|
||||
List<PlexTvResource> GetResources(string clientIdentifier, string authToken);
|
||||
}
|
||||
|
||||
public class PlexTvProxy : IPlexTvProxy
|
||||
@@ -62,6 +64,33 @@ namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<PlexTvResource> GetResources(string clientIdentifier, string authToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Allows us to tell plex.tv that we're still active and tokens should not be expired.
|
||||
|
||||
var request = BuildRequest(clientIdentifier);
|
||||
|
||||
request.ResourceUrl = "/api/v2/resources";
|
||||
request.AddQueryParam("includeHttps", 1);
|
||||
request.AddQueryParam("clientID", clientIdentifier);
|
||||
request.AddQueryParam("X-Plex-Token", authToken);
|
||||
|
||||
if (Json.TryDeserialize<List<PlexTvResource>>(ProcessRequest(request), out var response))
|
||||
{
|
||||
return response;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Catch all exceptions and log at trace, this information could be interesting in debugging, but expired tokens will be handled elsewhere.
|
||||
_logger.Trace(e, "Unable to ping plex.tv");
|
||||
}
|
||||
|
||||
return new List<PlexTvResource>();
|
||||
}
|
||||
|
||||
private HttpRequestBuilder BuildRequest(string clientIdentifier)
|
||||
{
|
||||
var requestBuilder = new HttpRequestBuilder("https://plex.tv")
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||
{
|
||||
public class PlexTvResource
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public bool Owned { get; set; }
|
||||
|
||||
public List<PlexTvResourceConnection> Connections { get; set; }
|
||||
|
||||
[JsonProperty("provides")]
|
||||
public string ProvidesRaw { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public List<string> Provides => ProvidesRaw.Split(",").ToList();
|
||||
}
|
||||
|
||||
public class PlexTvResourceConnection
|
||||
{
|
||||
public string Uri { get; set; }
|
||||
public string Protocol { get; set; }
|
||||
public string Address { get; set; }
|
||||
public int Port { get; set; }
|
||||
public bool Local { get; set; }
|
||||
public string Host => Uri.IsNullOrWhiteSpace() ? Address : new Uri(Uri).Host;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using NzbDrone.Common.Cache;
|
||||
@@ -14,6 +15,7 @@ namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||
PlexTvSignInUrlResponse GetSignInUrl(string callbackUrl, int pinId, string pinCode);
|
||||
string GetAuthToken(int pinId);
|
||||
void Ping(string authToken);
|
||||
List<PlexTvResource> GetServers(string authToken);
|
||||
HttpRequest GetWatchlist(string authToken, int pageSize, int pageOffset);
|
||||
}
|
||||
|
||||
@@ -93,6 +95,16 @@ namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||
_cache.Get(authToken, () => _proxy.Ping(_configService.PlexClientIdentifier, authToken), TimeSpan.FromHours(24));
|
||||
}
|
||||
|
||||
public List<PlexTvResource> GetServers(string authToken)
|
||||
{
|
||||
Ping(authToken);
|
||||
|
||||
var clientIdentifier = _configService.PlexClientIdentifier;
|
||||
var resources = _proxy.GetResources(clientIdentifier, authToken);
|
||||
|
||||
return resources.Where(r => r.Owned && r.Provides.Contains("server")).ToList();
|
||||
}
|
||||
|
||||
public HttpRequest GetWatchlist(string authToken, int pageSize, int pageOffset)
|
||||
{
|
||||
Ping(authToken);
|
||||
|
||||
@@ -5,6 +5,7 @@ using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Notifications.Plex.PlexTv;
|
||||
@@ -17,23 +18,15 @@ namespace NzbDrone.Core.Notifications.Plex.Server
|
||||
{
|
||||
private readonly IPlexServerService _plexServerService;
|
||||
private readonly IPlexTvService _plexTvService;
|
||||
private readonly MediaServerUpdateQueue<PlexServer, bool> _updateQueue;
|
||||
private readonly Logger _logger;
|
||||
|
||||
private class PlexUpdateQueue
|
||||
{
|
||||
public Dictionary<int, Series> Pending { get; } = new Dictionary<int, Series>();
|
||||
public bool Refreshing { get; set; }
|
||||
}
|
||||
|
||||
private readonly ICached<PlexUpdateQueue> _pendingSeriesCache;
|
||||
|
||||
public PlexServer(IPlexServerService plexServerService, IPlexTvService plexTvService, ICacheManager cacheManager, Logger logger)
|
||||
{
|
||||
_plexServerService = plexServerService;
|
||||
_plexTvService = plexTvService;
|
||||
_updateQueue = new MediaServerUpdateQueue<PlexServer, bool>(cacheManager);
|
||||
_logger = logger;
|
||||
|
||||
_pendingSeriesCache = cacheManager.GetRollingCache<PlexUpdateQueue>(GetType(), "pendingSeries", TimeSpan.FromDays(1));
|
||||
}
|
||||
|
||||
public override string Link => "https://www.plex.tv/";
|
||||
@@ -44,6 +37,11 @@ namespace NzbDrone.Core.Notifications.Plex.Server
|
||||
UpdateIfEnabled(message.Series);
|
||||
}
|
||||
|
||||
public override void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
UpdateIfEnabled(message.Series);
|
||||
}
|
||||
|
||||
public override void OnRename(Series series, List<RenamedEpisodeFile> renamedFiles)
|
||||
{
|
||||
UpdateIfEnabled(series);
|
||||
@@ -74,66 +72,20 @@ namespace NzbDrone.Core.Notifications.Plex.Server
|
||||
if (Settings.UpdateLibrary)
|
||||
{
|
||||
_logger.Debug("Scheduling library update for series {0} {1}", series.Id, series.Title);
|
||||
var queue = _pendingSeriesCache.Get(Settings.Host, () => new PlexUpdateQueue());
|
||||
lock (queue)
|
||||
{
|
||||
queue.Pending[series.Id] = series;
|
||||
}
|
||||
_updateQueue.Add(Settings.Host, series, false);
|
||||
}
|
||||
}
|
||||
|
||||
public override void ProcessQueue()
|
||||
{
|
||||
var queue = _pendingSeriesCache.Find(Settings.Host);
|
||||
|
||||
if (queue == null)
|
||||
_updateQueue.ProcessQueue(Settings.Host, (items) =>
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (queue)
|
||||
{
|
||||
if (queue.Refreshing)
|
||||
if (Settings.UpdateLibrary)
|
||||
{
|
||||
return;
|
||||
_logger.Debug("Performing library update for {0} series", items.Count);
|
||||
_plexServerService.UpdateLibrary(items.Select(i => i.Series), Settings);
|
||||
}
|
||||
|
||||
queue.Refreshing = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
List<Series> refreshingSeries;
|
||||
lock (queue)
|
||||
{
|
||||
if (queue.Pending.Empty())
|
||||
{
|
||||
queue.Refreshing = false;
|
||||
return;
|
||||
}
|
||||
|
||||
refreshingSeries = queue.Pending.Values.ToList();
|
||||
queue.Pending.Clear();
|
||||
}
|
||||
|
||||
if (Settings.UpdateLibrary)
|
||||
{
|
||||
_logger.Debug("Performing library update for {0} series", refreshingSeries.Count);
|
||||
_plexServerService.UpdateLibrary(refreshingSeries, Settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
lock (queue)
|
||||
{
|
||||
queue.Refreshing = false;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public override ValidationResult Test()
|
||||
@@ -193,6 +145,72 @@ namespace NzbDrone.Core.Notifications.Plex.Server
|
||||
};
|
||||
}
|
||||
|
||||
if (action == "servers")
|
||||
{
|
||||
Settings.Validate().Filter("AuthToken").ThrowOnError();
|
||||
|
||||
if (Settings.AuthToken.IsNullOrWhiteSpace())
|
||||
{
|
||||
return new { };
|
||||
}
|
||||
|
||||
var servers = _plexTvService.GetServers(Settings.AuthToken);
|
||||
var options = servers.SelectMany(s =>
|
||||
{
|
||||
var result = new List<FieldSelectStringOption>();
|
||||
|
||||
s.Connections.ForEach(c =>
|
||||
{
|
||||
var isSecure = c.Protocol == "https";
|
||||
var additionalProperties = new Dictionary<string, object>();
|
||||
var hints = new List<string>();
|
||||
|
||||
additionalProperties.Add("host", c.Host);
|
||||
additionalProperties.Add("port", c.Port);
|
||||
additionalProperties.Add("useSsl", isSecure);
|
||||
hints.Add(c.Local ? "Local" : "Remote");
|
||||
|
||||
if (isSecure)
|
||||
{
|
||||
hints.Add("Secure");
|
||||
}
|
||||
|
||||
result.Add(new FieldSelectStringOption
|
||||
{
|
||||
Value = c.Uri,
|
||||
Name = $"{s.Name} ({c.Host})",
|
||||
Hint = string.Join(", ", hints),
|
||||
AdditionalProperties = additionalProperties
|
||||
});
|
||||
|
||||
if (isSecure)
|
||||
{
|
||||
var uri = $"http://{c.Address}:{c.Port}";
|
||||
var insecureAdditionalProperties = new Dictionary<string, object>();
|
||||
|
||||
insecureAdditionalProperties.Add("host", c.Address);
|
||||
insecureAdditionalProperties.Add("port", c.Port);
|
||||
insecureAdditionalProperties.Add("useSsl", false);
|
||||
|
||||
result.Add(new FieldSelectStringOption
|
||||
{
|
||||
Value = uri,
|
||||
Name = $"{s.Name} ({c.Address})",
|
||||
Hint = c.Local ? "Local" : "Remote",
|
||||
AdditionalProperties = insecureAdditionalProperties
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
return new
|
||||
{
|
||||
options
|
||||
};
|
||||
}
|
||||
|
||||
return new { };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using FluentValidation;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Validation;
|
||||
@@ -22,40 +23,45 @@ namespace NzbDrone.Core.Notifications.Plex.Server
|
||||
|
||||
public PlexServerSettings()
|
||||
{
|
||||
Host = "";
|
||||
Port = 32400;
|
||||
UpdateLibrary = true;
|
||||
SignIn = "startOAuth";
|
||||
}
|
||||
|
||||
[FieldDefinition(0, Label = "Host")]
|
||||
[JsonIgnore]
|
||||
[FieldDefinition(0, Label = "NotificationsPlexSettingsServer", Type = FieldType.Select, SelectOptionsProviderAction = "servers", HelpText = "NotificationsPlexSettingsServerHelpText")]
|
||||
public string Server { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Host")]
|
||||
public string Host { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Port")]
|
||||
[FieldDefinition(2, Label = "Port")]
|
||||
public int Port { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "NotificationsSettingsUseSslHelpText")]
|
||||
[FieldDefinition(3, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "NotificationsSettingsUseSslHelpText")]
|
||||
[FieldToken(TokenField.HelpText, "UseSsl", "serviceName", "Plex")]
|
||||
public bool UseSsl { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "ConnectionSettingsUrlBaseHelpText")]
|
||||
[FieldDefinition(4, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "ConnectionSettingsUrlBaseHelpText")]
|
||||
[FieldToken(TokenField.HelpText, "UrlBase", "connectionName", "Plex")]
|
||||
[FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/plex")]
|
||||
public string UrlBase { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "NotificationsPlexSettingsAuthToken", Type = FieldType.Textbox, Privacy = PrivacyLevel.ApiKey, Advanced = true)]
|
||||
[FieldDefinition(5, Label = "NotificationsPlexSettingsAuthToken", Type = FieldType.Textbox, Privacy = PrivacyLevel.ApiKey, Advanced = true)]
|
||||
public string AuthToken { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "NotificationsPlexSettingsAuthenticateWithPlexTv", Type = FieldType.OAuth)]
|
||||
[FieldDefinition(6, Label = "NotificationsPlexSettingsAuthenticateWithPlexTv", Type = FieldType.OAuth)]
|
||||
public string SignIn { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "NotificationsSettingsUpdateLibrary", Type = FieldType.Checkbox)]
|
||||
[FieldDefinition(7, Label = "NotificationsSettingsUpdateLibrary", Type = FieldType.Checkbox)]
|
||||
public bool UpdateLibrary { get; set; }
|
||||
|
||||
[FieldDefinition(7, Label = "NotificationsSettingsUpdateMapPathsFrom", Type = FieldType.Textbox, Advanced = true, HelpText = "NotificationsSettingsUpdateMapPathsFromHelpText")]
|
||||
[FieldDefinition(8, Label = "NotificationsSettingsUpdateMapPathsFrom", Type = FieldType.Textbox, Advanced = true, HelpText = "NotificationsSettingsUpdateMapPathsFromHelpText")]
|
||||
[FieldToken(TokenField.HelpText, "NotificationsSettingsUpdateMapPathsFrom", "serviceName", "Plex")]
|
||||
public string MapFrom { get; set; }
|
||||
|
||||
[FieldDefinition(8, Label = "NotificationsSettingsUpdateMapPathsTo", Type = FieldType.Textbox, Advanced = true, HelpText = "NotificationsSettingsUpdateMapPathsToHelpText")]
|
||||
[FieldDefinition(9, Label = "NotificationsSettingsUpdateMapPathsTo", Type = FieldType.Textbox, Advanced = true, HelpText = "NotificationsSettingsUpdateMapPathsToHelpText")]
|
||||
[FieldToken(TokenField.HelpText, "NotificationsSettingsUpdateMapPathsTo", "serviceName", "Plex")]
|
||||
public string MapTo { get; set; }
|
||||
|
||||
|
||||
@@ -26,6 +26,11 @@ namespace NzbDrone.Core.Notifications.Prowl
|
||||
_prowlProxy.SendNotification(EPISODE_DOWNLOADED_TITLE, message.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
_prowlProxy.SendNotification(IMPORT_COMPLETE_TITLE, message.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage)
|
||||
{
|
||||
_prowlProxy.SendNotification(EPISODE_DELETED_TITLE, deleteMessage.Message, Settings);
|
||||
|
||||
@@ -29,6 +29,11 @@ namespace NzbDrone.Core.Notifications.PushBullet
|
||||
_proxy.SendNotification(EPISODE_DOWNLOADED_TITLE_BRANDED, message.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
_proxy.SendNotification(IMPORT_COMPLETE_TITLE_BRANDED, message.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage)
|
||||
{
|
||||
_proxy.SendNotification(EPISODE_DELETED_TITLE, deleteMessage.Message, Settings);
|
||||
|
||||
@@ -36,6 +36,11 @@ namespace NzbDrone.Core.Notifications.Pushcut
|
||||
_proxy.SendNotification(EPISODE_DOWNLOADED_TITLE, downloadMessage.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
_proxy.SendNotification(IMPORT_COMPLETE_TITLE, message.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage)
|
||||
{
|
||||
_proxy.SendNotification(EPISODE_DELETED_TITLE, deleteMessage.Message, Settings);
|
||||
|
||||
@@ -26,6 +26,11 @@ namespace NzbDrone.Core.Notifications.Pushover
|
||||
_proxy.SendNotification(EPISODE_DOWNLOADED_TITLE, message.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
_proxy.SendNotification(IMPORT_COMPLETE_TITLE, message.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage)
|
||||
{
|
||||
_proxy.SendNotification(EPISODE_DELETED_TITLE, deleteMessage.Message, Settings);
|
||||
|
||||
@@ -32,6 +32,11 @@ namespace NzbDrone.Core.Notifications.SendGrid
|
||||
_proxy.SendNotification(EPISODE_DOWNLOADED_TITLE, message.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
_proxy.SendNotification(IMPORT_COMPLETE_TITLE, message.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage)
|
||||
{
|
||||
_proxy.SendNotification(EPISODE_DELETED_TITLE, deleteMessage.Message, Settings);
|
||||
|
||||
@@ -26,6 +26,11 @@ namespace NzbDrone.Core.Notifications.Signal
|
||||
_proxy.SendNotification(EPISODE_DOWNLOADED_TITLE, message.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
_proxy.SendNotification(IMPORT_COMPLETE_TITLE, message.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage)
|
||||
{
|
||||
_proxy.SendNotification(EPISODE_DELETED_TITLE, deleteMessage.Message, Settings);
|
||||
|
||||
@@ -26,6 +26,11 @@ namespace NzbDrone.Core.Notifications.Simplepush
|
||||
_proxy.SendNotification(EPISODE_DOWNLOADED_TITLE, message.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
_proxy.SendNotification(IMPORT_COMPLETE_TITLE, message.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage)
|
||||
{
|
||||
_proxy.SendNotification(EPISODE_DELETED_TITLE, deleteMessage.Message, Settings);
|
||||
|
||||
@@ -59,6 +59,23 @@ namespace NzbDrone.Core.Notifications.Slack
|
||||
_proxy.SendPayload(payload, Settings);
|
||||
}
|
||||
|
||||
public override void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
var attachments = new List<Attachment>
|
||||
{
|
||||
new Attachment
|
||||
{
|
||||
Fallback = message.Message,
|
||||
Title = message.Series.Title,
|
||||
Text = message.Message,
|
||||
Color = "good"
|
||||
}
|
||||
};
|
||||
var payload = CreatePayload($"Imported all expected episodes: {message.Message}", attachments);
|
||||
|
||||
_proxy.SendPayload(payload, Settings);
|
||||
}
|
||||
|
||||
public override void OnRename(Series series, List<RenamedEpisodeFile> renamedFiles)
|
||||
{
|
||||
var attachments = new List<Attachment>
|
||||
|
||||
@@ -42,6 +42,14 @@ namespace NzbDrone.Core.Notifications.Synology
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
if (Settings.UpdateLibrary)
|
||||
{
|
||||
_indexerProxy.UpdateFolder(message.Series.Path);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnRename(Series series, List<RenamedEpisodeFile> renamedFiles)
|
||||
{
|
||||
if (Settings.UpdateLibrary)
|
||||
|
||||
@@ -30,6 +30,13 @@ namespace NzbDrone.Core.Notifications.Telegram
|
||||
_proxy.SendNotification(title, message.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
var title = Settings.IncludeAppNameInTitle ? EPISODE_DOWNLOADED_TITLE_BRANDED : EPISODE_DOWNLOADED_TITLE;
|
||||
|
||||
_proxy.SendNotification(title, message.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage)
|
||||
{
|
||||
var title = Settings.IncludeAppNameInTitle ? EPISODE_DELETED_TITLE_BRANDED : EPISODE_DELETED_TITLE;
|
||||
|
||||
@@ -39,6 +39,13 @@ namespace NzbDrone.Core.Notifications.Trakt
|
||||
AddEpisodeToCollection(Settings, message.Series, message.EpisodeFile);
|
||||
}
|
||||
|
||||
public override void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
RefreshTokenIfNecessary();
|
||||
|
||||
message.EpisodeFiles.ForEach(f => AddEpisodeToCollection(Settings, message.Series, f));
|
||||
}
|
||||
|
||||
public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage)
|
||||
{
|
||||
RefreshTokenIfNecessary();
|
||||
|
||||
@@ -28,6 +28,11 @@ namespace NzbDrone.Core.Notifications.Twitter
|
||||
_twitterService.SendNotification($"Imported: {message.Message}", Settings);
|
||||
}
|
||||
|
||||
public override void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
_twitterService.SendNotification($"Imported: {message.Message}", Settings);
|
||||
}
|
||||
|
||||
public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage)
|
||||
{
|
||||
_twitterService.SendNotification($"Episode Deleted: {deleteMessage.Message}", Settings);
|
||||
|
||||
@@ -33,6 +33,11 @@ namespace NzbDrone.Core.Notifications.Webhook
|
||||
_proxy.SendWebhook(BuildOnDownloadPayload(message), Settings);
|
||||
}
|
||||
|
||||
public override void OnImportComplete(ImportCompleteMessage message)
|
||||
{
|
||||
_proxy.SendWebhook(BuildOnImportCompletePayload(message), Settings);
|
||||
}
|
||||
|
||||
public override void OnRename(Series series, List<RenamedEpisodeFile> renamedFiles)
|
||||
{
|
||||
_proxy.SendWebhook(BuildOnRenamePayload(series, renamedFiles), Settings);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user