1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-04-16 21:15:33 -04:00

Compare commits

..

67 Commits

Author SHA1 Message Date
Weblate
f77e27bace Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Altair <villagermd@outlook.com>
Co-authored-by: Ano10 <arnaudthommeray+github@ik.me>
Co-authored-by: Fonkio <maxime.fabre10@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Jacopo Luca Maria Latrofa <jacopo.latrofa@gmail.com>
Co-authored-by: Mailme Dashite <mailmedashite@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: YSLG <1451164040@qq.com>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: myrad2267 <myrad2267@gmail.com>
Co-authored-by: toeiazarothis <patrickdealmeida89000@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2024-04-19 17:25:34 +03:00
Servarr
8ea6d59d59 Automated API Docs update 2024-04-19 17:24:01 +03:00
Bogdan
98668d0d25 Bump SixLabors.ImageSharp to 3.1.4 2024-04-19 07:59:50 +03:00
Gauthier
649d57a234 Improve Multi Language Regex and field translations
(cherry picked from commit 6c232b062c5c11b76a2f205fcd949619e4346d16)

Closes #9931
2024-04-16 12:53:04 +03:00
Josh McKinney
dc7c8bf800 Add dev container workspace
Allows the linting and style settings for the frontend to be applied even when you load the main repo as a workspace

(cherry picked from commit d6278fced49b26be975c3a6039b38a94f700864b)

Closes #9929
2024-04-16 11:41:14 +03:00
Bogdan
8d90c7678f Fixed: Re-testing edited providers will forcibly test them
(cherry picked from commit e9662544621b2d1fb133ff9d96d0eb20b8198725)

Closes #9933
2024-04-16 11:39:43 +03:00
Bogdan
02518e2116 Fixed: Validate provider's settings in Test All endpoint 2024-04-16 11:39:35 +03:00
Mark McDowall
3191a883dc New: Improve multi-language negate Custom Format
(cherry picked from commit 42b11528b4699b8343887185c93a02b139192d83)

Closes #9720
2024-04-13 11:03:37 +03:00
Bogdan
31a714e6b3 Bump version to 5.5.0 2024-04-13 08:46:17 +03:00
Mark McDowall
f7ca0b8b06 New: Auto tag movies based on tags present/absent on movies
(cherry picked from commit f4c19a384bd9bb4e35c9fa0ca5d9a448c04e409e)

Closes #9916
2024-04-10 23:40:26 +03:00
Josh McKinney
56be9502af Add DevContainer, VSCode config and extensions.json
(cherry picked from commit 5061dc4b5e5ea9925740496a5939a1762788b793)

Closes #9914
2024-04-10 22:59:13 +03:00
Mark McDowall
77381d3f72 New: Option to prefix app name on Telegram notification titles
(cherry picked from commit 37863a8deb339ef730b2dd5be61e1da1311fdd23)

Closes #9913
2024-04-10 22:34:52 +03:00
Bogdan
198e6324e0 Truncate long names for import lists 2024-04-09 18:19:26 +03:00
Alan Collins
81c9537e5a New: 'Custom Format:Format Name' rename token
cherry picked from commit 48cb5d227187a06930aad5ee1b4e7b76422d8421)

New: Update Custom Format renaming token to allow excluding specific formats

(cherry picked from commit 6584d95331d0e0763e1688a397a3ccaf5fa6ca38)

Closes #9835
Closes #9826
2024-04-09 08:04:57 +03:00
Bogdan
d3cbb9be8d New: Detect shfs mounts 2024-04-08 22:25:42 +03:00
Bogdan
2e043c0cf7 Bump version to 5.4.6 2024-04-07 07:58:52 +03:00
Weblate
ada33dc065 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Michael5564445 <michaelvelosk@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2024-04-06 16:34:41 +03:00
Till Krüss
badb68b817 Improve text for file deleted through UI/API (#9882) 2024-04-06 16:32:25 +03:00
Mark McDowall
3bd1b3e972 Fixed: Sending ntfy.sh notifications with unicode characters
(cherry picked from commit a169ebff2adda5c8585c6aae6249b1c1f7c12264)
2024-04-06 16:27:30 +03:00
Stevie Robinson
6851de42a7 New: Informational text on Custom Formats modal
(cherry picked from commit 238ba85f0a2639608d9890292dfe0b96c0212f10)
2024-04-06 16:27:19 +03:00
Cuki
dd0b7c91f9 Fixed: Use widely supported display mode for PWA
(cherry picked from commit 1562d3bae3002947f9e428321d2b162ad69c3309)
2024-04-06 16:27:08 +03:00
Mark McDowall
45ac69e2d9 Fixed: Cleanse BHD RSS key in log files
(cherry picked from commit 60ee7cc716d344fc904fa6fb28f7be0386ae710d)
2024-04-06 16:26:49 +03:00
Bogdan
9ccf0ecdb1 Fix translation token for Include Health Warnings 2024-04-06 03:31:36 +03:00
Servarr
48a3467572 Automated API Docs update 2024-03-29 14:53:19 +02:00
Mark McDowall
d0a10379f9 Fixed: Use custom formats from import during rename
(cherry picked from commit d338425951af50a710c6c4411a72f05d14737ddd)

Closes #9867
2024-03-28 12:42:21 +02:00
Bogdan
caab5e3614 Add missing import after 4e4769 2024-03-28 12:38:28 +02:00
Alex Cortelyou
4e47695f89 New: Add additional fields to Webhook Manual Interaction Required events
(cherry picked from commit 1ec1ce58e9f095222e7fe4a8c74a0720fed71558)

Closes #9874
2024-03-28 11:15:56 +02:00
Bogdan
83bd4d0686 New: Advanced settings toggle in import list, notification and download client modals
(cherry picked from commit 13c925b3418d1d48ec041e3d97ab51aaf2b8977a)

Closes #9869
2024-03-28 11:14:26 +02:00
Mark McDowall
a75619c8ef Fixed: Task with removed movie causing error
(cherry picked from commit fc6494c569324c839debdb1d08dde23b8f1b8d76)

Closes #9866
2024-03-28 11:08:32 +02:00
Louis R
28689006fb Fixed: Exceptions when checking for routable IPv4 addresses
(cherry picked from commit 060b789bc6f10f667795697eb536d4bd3851da49)
2024-03-28 10:29:30 +02:00
Carlos Gustavo Sarmiento
43b0589bea Fixed: qBittorrent not correctly handling retention during testing
(cherry picked from commit 588372fd950fc85f5e9a4275fbcb423b247ed0ee)
2024-03-28 10:29:17 +02:00
Stevie Robinson
c4aad5800c Fixed: Handling torrents with relative path in rTorrent
(cherry picked from commit 35d0e6a6f806c68756450a7d199600d7fb49d6c5)
2024-03-28 10:28:53 +02:00
Bogdan
0c998dac5c New: Allow HEAD requests to ping endpoint
(cherry picked from commit 7353fe479dbb8d0dab76993ebed92d48e1b05524)
2024-03-28 10:28:41 +02:00
Bogdan
d41c0f0ab7 Fixed: Movie search label on overflow views
Fixed #9865
2024-03-27 15:16:56 +02:00
Weblate
85b13b7e41 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Altair <villagermd@outlook.com>
Co-authored-by: Casselluu <jack10193@163.com>
Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Jason54 <jason54700.jg@gmail.com>
Co-authored-by: Stanislav <prekop3@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: shimmyx <shimmygodx@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2024-03-27 13:16:17 +02:00
Bogdan
2a545a84b4 Bump version to 5.4.5 2024-03-24 17:45:46 +02:00
Mark McDowall
280083f4d7 Fixed: Task progress messages in the UI
(cherry picked from commit c6417337812f3578a27f9dc1e44fdad80f557271)

Closes #9855
2024-03-22 11:15:16 +02:00
Mark McDowall
d6dcae3d6a Fixed: Plex Watchlist import list
(cherry picked from commit 88de9274358d7005fa9c677bb8c86f046a2a23a9)
2024-03-22 11:06:39 +02:00
Bogdan
ebde4d3bc8 New: Critic Rating for Kodi/Emby metadata 2024-03-21 19:22:51 +02:00
Yurii
1ee30290ef Use branded message title for Telegram nitifications 2024-03-17 13:20:57 +00:00
Bogdan
d303eae7c6 New: Company filters for TMDb Popular List 2024-03-17 13:50:41 +02:00
Bogdan
584910514a Bump version to 5.4.4 2024-03-17 13:50:03 +02:00
Weblate
a253181d7d Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dennis Langthjem <dennis@langthjem.dk>
Co-authored-by: DimitriDR <dimitridroeck@gmail.com>
Co-authored-by: Gianmarco Novelli <rinogaetano94@live.it>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Ihor Mudryi <mudryy33@gmail.com>
Co-authored-by: MadaxDeLuXe <madaxdeluxe@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: infoaitek24 <info@aitekph.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: vfaergestad <vgf@hotmail.no>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translation: Servarr/Radarr
2024-03-16 18:15:57 +02:00
Bogdan
7ea6918327 Remove leftover QueuedTasks.js 2024-03-14 15:40:13 +02:00
Bogdan
953d3ad3fb Ensure not allowed cursor is shown for disabled select inputs 2024-03-14 14:31:09 +02:00
Mark McDowall
b9f4073514 Fixed: Disabled select option still selectable
(cherry picked from commit 063dba22a803295adee4fdcbe42718af3e85ca78)

Closes #9838
2024-03-14 13:20:33 +02:00
Stevie Robinson
86a17e7984 Fixed: Wrapping of naming tokens with alternate separators
(cherry picked from commit 80630bf97f5bb3b49d4824dc039d2edfc74e4797)

Closes #9743
Closes #9836
2024-03-14 11:33:54 +02:00
Bogdan
f38545f852 Ensure movies are populated in PageConnector 2024-03-14 11:21:56 +02:00
Mark McDowall
a7720e829d New: Show movie titles after task name when applicable
(cherry picked from commit 6d552f2a60f44052079b5e8944f5e1bbabac56e0)

Closes #9837
2024-03-14 11:15:39 +02:00
Mark McDowall
3a4eac4d59 Fixed: Release push with only Magnet URL
(cherry picked from commit 9f705e4161af3f4dd55b399d56b0b9c5a36e181b)
2024-03-14 07:12:12 +02:00
Bogdan
04f792c55a Fixed: Map covers to local for Movie Editor 2024-03-12 22:48:27 +02:00
Stevie Robinson
ada326e4dd Update release profile download client warning
(cherry picked from commit 2ec071a5ecab8f5056d179feaaef0147abb944ca)

Closes #9828
2024-03-10 20:16:09 +02:00
Bogdan
cae58d620b New: Collection Refresh Complete Event to trigger root folder check for collections 2024-03-10 20:13:35 +02:00
Bogdan
e84df18e8d Bump ImageSharp, Polly 2024-03-10 13:06:26 +02:00
Bogdan
a51ae70938 Bump version to 5.4.3 2024-03-10 09:08:55 +02:00
Weblate
7cc04245ec Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Jason54 <jason54700.jg@gmail.com>
Co-authored-by: Mark Martines <mark-martines@hotmail.com>
Co-authored-by: Maxence Winandy <maxence.winandy@gmail.com>
Co-authored-by: Stevie Robinson <stevie.robinson@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: linkin931 <931linkin@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2024-03-08 11:08:04 +02:00
Mark McDowall
2caf3c6725 Fixed: Error sending Manual Interaction Required notification
(cherry picked from commit a12cdb34bc0ab78937e3c3677012bf030923aebf)
2024-03-08 08:52:48 +02:00
Bogdan
41ff9352b9 Prevent NullRef in naming when truncating a null Release Group
(cherry picked from commit 13e29bd257ccfccb09e66c940ffabeb6503c05b5)
2024-03-08 08:52:48 +02:00
Helvio Pedreschi
d7b9b2ccb2 Fixed: WebApp functionality on Apple devices
(cherry picked from commit c7dd7abf892eead7796fcc482aa2f2aabaf88712)
2024-03-08 08:52:48 +02:00
Mark McDowall
e90a50a3aa Fixed: Overly aggressive exception release group parsing
(cherry picked from commit 0183812cc58dad0e555125ddd8b33a85cbdecbf2)
2024-03-08 08:52:48 +02:00
Bogdan
a0dd26c353 Configurable URL Base setting for Kodi connections 2024-03-04 03:16:23 +02:00
Bogdan
2286055d6a Fixed: URL Base setting for Kodi connections 2024-03-03 13:48:25 +02:00
Mark McDowall
0a5a4e0a6f New: URL Base setting for Media Server connections
(cherry picked from commit 9fd193d2a82d5c2cdc0f36c1f984e4b6b68aaa8d)
2024-03-03 12:29:47 +02:00
Mark McDowall
619c38c493 Queue Manual Import commands at high priority
(cherry picked from commit 64c6a8879beb1b17122c8f6f74bf7b3cf4dd1570)
2024-03-03 12:29:45 +02:00
Louis R
0b8694c627 Fixed: Don't disable IPv6 in IPv6-only Environment
(cherry picked from commit 13af6f57796e54c3949cf340e03f020e6f8575c4)
2024-03-03 12:23:59 +02:00
Bogdan
e2793e56e9 Bump version to 5.4.2 2024-03-03 12:23:41 +02:00
nopoz
68f61da321 New: Add download directory & move completed for Deluge 2024-03-03 12:23:22 +02:00
161 changed files with 3265 additions and 1372 deletions

View File

@@ -0,0 +1,13 @@
// This file is used to open the backend and frontend in the same workspace, which is necessary as
// the frontend has vscode settings that are distinct from the backend
{
"folders": [
{
"path": ".."
},
{
"path": "../frontend"
}
],
"settings": {}
}

View File

@@ -0,0 +1,19 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
{
"name": "Radarr",
"image": "mcr.microsoft.com/devcontainers/dotnet:1-6.0",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"nodeGypDependencies": true,
"version": "16",
"nvmVersion": "latest"
}
},
"forwardPorts": [7878],
"customizations": {
"vscode": {
"extensions": ["esbenp.prettier-vscode"]
}
}
}

12
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for more information:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
# https://containers.dev/guide/dependabot
version: 2
updates:
- package-ecosystem: "devcontainers"
directory: "/"
schedule:
interval: weekly

1
.gitignore vendored
View File

@@ -126,6 +126,7 @@ coverage*.xml
coverage*.json
setup/Output/
*.~is
.mono
# VS outout folders
bin

7
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"recommendations": [
"esbenp.prettier-vscode",
"ms-dotnettools.csdevkit",
"ms-vscode-remote.remote-containers"
]
}

26
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,26 @@
{
"version": "0.2.0",
"configurations": [
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md
"name": "Run Radarr",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build dotnet",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/_output/net6.0/Radarr",
"args": [],
"cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "integratedTerminal",
"stopAtEntry": false
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}

44
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,44 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build dotnet",
"command": "dotnet",
"type": "process",
"args": [
"msbuild",
"-restore",
"${workspaceFolder}/src/Radarr.sln",
"-p:GenerateFullPaths=true",
"-p:Configuration=Debug",
"-p:Platform=Posix",
"-consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/src/Radarr.sln",
"-property:GenerateFullPaths=true",
"-consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/src/Radarr.sln"
],
"problemMatcher": "$msCompile"
}
]
}

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '5.4.1'
majorVersion: '5.5.0'
minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)'

View File

@@ -20,7 +20,7 @@ const monitoredOptions = [
get value() {
return translate('NoChange');
},
disabled: true
isDisabled: true
},
{
key: 'monitored',
@@ -42,7 +42,7 @@ const searchOnAddOptions = [
get value() {
return translate('NoChange');
},
disabled: true
isDisabled: true
},
{
key: 'yes',

View File

@@ -13,6 +13,7 @@ export interface CommandBody {
trigger: string;
suppressMessages: boolean;
movieId?: number;
movieIds?: number[];
}
interface Command extends ModelBase {

View File

@@ -36,7 +36,7 @@ function AvailabilitySelectInput(props) {
values.unshift({
key: 'noChange',
value: translate('NoChange'),
disabled: true
isDisabled: true
});
}
@@ -44,7 +44,7 @@ function AvailabilitySelectInput(props) {
values.unshift({
key: 'mixed',
value: '(Mixed)',
disabled: true
isDisabled: true
});
}

View File

@@ -19,7 +19,7 @@
.isDisabled {
opacity: 0.7;
cursor: not-allowed;
cursor: not-allowed !important;
}
.dropdownArrowContainer {

View File

@@ -17,6 +17,7 @@ import IndexerSelectInputConnector from './IndexerSelectInputConnector';
import KeyValueListInput from './KeyValueListInput';
import LanguageSelectInputConnector from './LanguageSelectInputConnector';
import MovieMonitoredSelectInput from './MovieMonitoredSelectInput';
import MovieTagInput from './MovieTagInput';
import NumberInput from './NumberInput';
import OAuthInputConnector from './OAuthInputConnector';
import PasswordInput from './PasswordInput';
@@ -89,6 +90,10 @@ function getComponent(type) {
case inputTypes.DYNAMIC_SELECT:
return EnhancedSelectInputConnector;
case inputTypes.MOVIE_TAG:
return MovieTagInput;
case inputTypes.TAG:
return TagInputConnector;

View File

@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import monitorOptions from 'Utilities/Movie/monitorOptions';
import translate from 'Utilities/String/translate';
import SelectInput from './SelectInput';
import EnhancedSelectInput from './EnhancedSelectInput';
function MovieMonitoredSelectInput(props) {
const values = [...monitorOptions];
@@ -16,7 +16,7 @@ function MovieMonitoredSelectInput(props) {
values.unshift({
key: 'noChange',
value: translate('NoChange'),
disabled: true
isDisabled: true
});
}
@@ -24,12 +24,12 @@ function MovieMonitoredSelectInput(props) {
values.unshift({
key: 'mixed',
value: '(Mixed)',
disabled: true
isDisabled: true
});
}
return (
<SelectInput
<EnhancedSelectInput
{...props}
values={values}
/>

View File

@@ -0,0 +1,53 @@
import React, { useCallback } from 'react';
import TagInputConnector from './TagInputConnector';
interface MovieTagInputProps {
name: string;
value: number | number[];
onChange: ({
name,
value,
}: {
name: string;
value: number | number[];
}) => void;
}
export default function MovieTagInput(props: MovieTagInputProps) {
const { value, onChange, ...otherProps } = props;
const isArray = Array.isArray(value);
const handleChange = useCallback(
({ name, value: newValue }: { name: string; value: number[] }) => {
if (isArray) {
onChange({ name, value: newValue });
} else {
onChange({
name,
value: newValue.length ? newValue[newValue.length - 1] : 0,
});
}
},
[isArray, onChange]
);
let finalValue: number[] = [];
if (isArray) {
finalValue = value;
} else if (value === 0) {
finalValue = [];
} else {
finalValue = [value];
}
return (
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore 2786 'TagInputConnector' isn't typed yet
<TagInputConnector
{...otherProps}
value={finalValue}
onChange={handleChange}
/>
);
}

View File

@@ -27,6 +27,8 @@ function getType({ type, selectOptionsProviderAction }) {
return inputTypes.DYNAMIC_SELECT;
}
return inputTypes.SELECT;
case 'movieTag':
return inputTypes.MOVIE_TAG;
case 'tag':
return inputTypes.TEXT_TAG;
case 'tagSelect':

View File

@@ -26,7 +26,7 @@ function createMapStateToProps() {
values.unshift({
key: 'noChange',
value: translate('NoChange'),
disabled: includeNoChangeDisabled
isDisabled: includeNoChangeDisabled
});
}
@@ -34,7 +34,7 @@ function createMapStateToProps() {
values.unshift({
key: 'mixed',
value: '(Mixed)',
disabled: true
isDisabled: true
});
}

View File

@@ -45,6 +45,7 @@ const selectAppProps = createSelector(
);
const selectIsPopulated = createSelector(
(state) => state.movies.isPopulated,
(state) => state.customFilters.isPopulated,
(state) => state.tags.isPopulated,
(state) => state.settings.ui.isPopulated,
@@ -56,6 +57,7 @@ const selectIsPopulated = createSelector(
(state) => state.movieCollections.isPopulated,
(state) => state.app.translations.isPopulated,
(
moviesIsPopulated,
customFiltersIsPopulated,
tagsIsPopulated,
uiSettingsIsPopulated,
@@ -68,6 +70,7 @@ const selectIsPopulated = createSelector(
translationsIsPopulated
) => {
return (
moviesIsPopulated &&
customFiltersIsPopulated &&
tagsIsPopulated &&
uiSettingsIsPopulated &&
@@ -83,6 +86,7 @@ const selectIsPopulated = createSelector(
);
const selectErrors = createSelector(
(state) => state.movies.error,
(state) => state.customFilters.error,
(state) => state.tags.error,
(state) => state.settings.ui.error,
@@ -94,6 +98,7 @@ const selectErrors = createSelector(
(state) => state.movieCollections.error,
(state) => state.app.translations.error,
(
moviesError,
customFiltersError,
tagsError,
uiSettingsError,
@@ -106,6 +111,7 @@ const selectErrors = createSelector(
translationsError
) => {
const hasError = !!(
moviesError ||
customFiltersError ||
tagsError ||
uiSettingsError ||

View File

@@ -15,5 +15,5 @@
"start_url": "../../../../",
"theme_color": "#3a3f51",
"background_color": "#3a3f51",
"display": "minimal-ui"
"display": "standalone"
}

View File

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

View File

@@ -17,6 +17,7 @@ export const INDEXER_FLAGS_SELECT = 'indexerFlagsSelect';
export const LANGUAGE_SELECT = 'languageSelect';
export const DOWNLOAD_CLIENT_SELECT = 'downloadClientSelect';
export const SELECT = 'select';
export const MOVIE_TAG = 'movieTag';
export const DYNAMIC_SELECT = 'dynamicSelect';
export const TAG = 'tag';
export const TEXT = 'text';
@@ -45,6 +46,7 @@ export const all = [
INDEXER_FLAGS_SELECT,
LANGUAGE_SELECT,
SELECT,
MOVIE_TAG,
DYNAMIC_SELECT,
TAG,
TEXT,

View File

@@ -44,6 +44,7 @@ import MovieIndexViewMenu from './Menus/MovieIndexViewMenu';
import MovieIndexFooter from './MovieIndexFooter';
import MovieIndexRefreshMovieButton from './MovieIndexRefreshMovieButton';
import MovieIndexSearchButton from './MovieIndexSearchButton';
import MovieIndexSearchMenuItem from './MovieIndexSearchMenuItem';
import MovieIndexOverviews from './Overview/MovieIndexOverviews';
import MovieIndexOverviewOptionsModal from './Overview/Options/MovieIndexOverviewOptionsModal';
import MovieIndexPosters from './Posters/MovieIndexPosters';
@@ -247,6 +248,7 @@ const MovieIndex = withScrollPosition((props: MovieIndexProps) => {
<MovieIndexSearchButton
isSelectMode={isSelectMode}
selectedFilterKey={selectedFilterKey}
overflowComponent={MovieIndexSearchMenuItem}
/>
<PageToolbarButton

View File

@@ -16,6 +16,7 @@ import getSelectedIds from 'Utilities/Table/getSelectedIds';
interface MovieIndexSearchButtonProps {
isSelectMode: boolean;
selectedFilterKey: string;
overflowComponent: React.FunctionComponent<never>;
}
function MovieIndexSearchButton(props: MovieIndexSearchButtonProps) {

View File

@@ -0,0 +1,72 @@
import React, { useCallback, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useSelect } from 'App/SelectContext';
import ClientSideCollectionAppState from 'App/State/ClientSideCollectionAppState';
import MoviesAppState, { MovieIndexAppState } from 'App/State/MoviesAppState';
import { MOVIE_SEARCH } from 'Commands/commandNames';
import PageToolbarOverflowMenuItem from 'Components/Page/Toolbar/PageToolbarOverflowMenuItem';
import { icons } from 'Helpers/Props';
import { executeCommand } from 'Store/Actions/commandActions';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import createMovieClientSideCollectionItemsSelector from 'Store/Selectors/createMovieClientSideCollectionItemsSelector';
import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
interface MovieIndexSearchMenuItemProps {
isSelectMode: boolean;
selectedFilterKey: string;
}
function MovieIndexSearchMenuItem(props: MovieIndexSearchMenuItemProps) {
const isSearching = useSelector(createCommandExecutingSelector(MOVIE_SEARCH));
const {
items,
}: MoviesAppState & MovieIndexAppState & ClientSideCollectionAppState =
useSelector(createMovieClientSideCollectionItemsSelector('movieIndex'));
const dispatch = useDispatch();
const { isSelectMode, selectedFilterKey } = props;
const [selectState] = useSelect();
const { selectedState } = selectState;
const selectedMovieIds = useMemo(() => {
return getSelectedIds(selectedState);
}, [selectedState]);
const moviesToSearch =
isSelectMode && selectedMovieIds.length > 0
? selectedMovieIds
: items.map((m) => m.id);
const searchIndexLabel =
selectedFilterKey === 'all'
? translate('SearchAll')
: translate('SearchFiltered');
const searchSelectLabel =
selectedMovieIds.length > 0
? translate('SearchSelected')
: translate('SearchAll');
const onPress = useCallback(() => {
dispatch(
executeCommand({
name: MOVIE_SEARCH,
movieIds: moviesToSearch,
})
);
}, [dispatch, moviesToSearch]);
return (
<PageToolbarOverflowMenuItem
label={isSelectMode ? searchSelectLabel : searchIndexLabel}
isSpinning={isSearching}
isDisabled={!items.length}
iconName={icons.SEARCH}
onPress={onPress}
/>
);
}
export default MovieIndexSearchMenuItem;

View File

@@ -34,7 +34,7 @@ const monitoredOptions = [
get value() {
return translate('NoChange');
},
disabled: true,
isDisabled: true,
},
{
key: 'monitored',

View File

@@ -151,6 +151,11 @@ class EditCustomFormatModalContent extends Component {
</Form>
<FieldSet legend={translate('Conditions')}>
<Alert kind={kinds.INFO}>
<div>
{translate('CustomFormatsSettingsTriggerInfo')}
</div>
</Alert>
<div className={styles.customFormats}>
{
specifications.map((tag) => {

View File

@@ -15,6 +15,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import AdvancedSettingsButton from 'Settings/AdvancedSettingsButton';
import translate from 'Utilities/String/translate';
import styles from './EditDownloadClientModalContent.css';
@@ -37,6 +38,7 @@ class EditDownloadClientModalContent extends Component {
onModalClose,
onSavePress,
onTestPress,
onAdvancedSettingsPress,
onDeleteDownloadClientPress,
...otherProps
} = this.props;
@@ -199,6 +201,12 @@ class EditDownloadClientModalContent extends Component {
</Button>
}
<AdvancedSettingsButton
advancedSettings={advancedSettings}
onAdvancedSettingsPress={onAdvancedSettingsPress}
showLabel={false}
/>
<SpinnerErrorButton
isSpinning={isTesting}
error={saveError}
@@ -239,6 +247,7 @@ EditDownloadClientModalContent.propTypes = {
onModalClose: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onTestPress: PropTypes.func.isRequired,
onAdvancedSettingsPress: PropTypes.func.isRequired,
onDeleteDownloadClientPress: PropTypes.func
};

View File

@@ -2,7 +2,13 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { saveDownloadClient, setDownloadClientFieldValue, setDownloadClientValue, testDownloadClient } from 'Store/Actions/settingsActions';
import {
saveDownloadClient,
setDownloadClientFieldValue,
setDownloadClientValue,
testDownloadClient,
toggleAdvancedSettings
} from 'Store/Actions/settingsActions';
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
import EditDownloadClientModalContent from './EditDownloadClientModalContent';
@@ -23,7 +29,8 @@ const mapDispatchToProps = {
setDownloadClientValue,
setDownloadClientFieldValue,
saveDownloadClient,
testDownloadClient
testDownloadClient,
toggleAdvancedSettings
};
class EditDownloadClientModalContentConnector extends Component {
@@ -56,6 +63,10 @@ class EditDownloadClientModalContentConnector extends Component {
this.props.testDownloadClient({ id: this.props.id });
};
onAdvancedSettingsPress = () => {
this.props.toggleAdvancedSettings();
};
//
// Render
@@ -65,6 +76,7 @@ class EditDownloadClientModalContentConnector extends Component {
{...this.props}
onSavePress={this.onSavePress}
onTestPress={this.onTestPress}
onAdvancedSettingsPress={this.onAdvancedSettingsPress}
onInputChange={this.onInputChange}
onFieldChange={this.onFieldChange}
/>
@@ -82,6 +94,7 @@ EditDownloadClientModalContentConnector.propTypes = {
setDownloadClientFieldValue: PropTypes.func.isRequired,
saveDownloadClient: PropTypes.func.isRequired,
testDownloadClient: PropTypes.func.isRequired,
toggleAdvancedSettings: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -32,7 +32,7 @@ const enableOptions = [
get value() {
return translate('NoChange');
},
disabled: true,
isDisabled: true,
},
{
key: 'enabled',

View File

@@ -17,6 +17,8 @@
}
.name {
@add-mixin truncate;
text-align: center;
font-weight: lighter;
font-size: 24px;

View File

@@ -14,6 +14,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import AdvancedSettingsButton from 'Settings/AdvancedSettingsButton';
import formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan';
import translate from 'Utilities/String/translate';
import styles from './EditImportListModalContent.css';
@@ -33,6 +34,7 @@ function EditImportListModalContent(props) {
onModalClose,
onSavePress,
onTestPress,
onAdvancedSettingsPress,
onDeleteImportListPress,
...otherProps
} = props;
@@ -234,6 +236,12 @@ function EditImportListModalContent(props) {
</Button>
}
<AdvancedSettingsButton
advancedSettings={advancedSettings}
onAdvancedSettingsPress={onAdvancedSettingsPress}
showLabel={false}
/>
<SpinnerErrorButton
isSpinning={isTesting}
error={saveError}
@@ -274,6 +282,7 @@ EditImportListModalContent.propTypes = {
onModalClose: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onTestPress: PropTypes.func.isRequired,
onAdvancedSettingsPress: PropTypes.func.isRequired,
onDeleteImportListPress: PropTypes.func
};

View File

@@ -2,7 +2,13 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { saveImportList, setImportListFieldValue, setImportListValue, testImportList } from 'Store/Actions/settingsActions';
import {
saveImportList,
setImportListFieldValue,
setImportListValue,
testImportList,
toggleAdvancedSettings
} from 'Store/Actions/settingsActions';
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
import EditImportListModalContent from './EditImportListModalContent';
@@ -33,7 +39,8 @@ const mapDispatchToProps = {
setImportListValue,
setImportListFieldValue,
saveImportList,
testImportList
testImportList,
toggleAdvancedSettings
};
class EditImportListModalContentConnector extends Component {
@@ -66,6 +73,10 @@ class EditImportListModalContentConnector extends Component {
this.props.testImportList({ id: this.props.id });
};
onAdvancedSettingsPress = () => {
this.props.toggleAdvancedSettings();
};
//
// Render
@@ -75,6 +86,7 @@ class EditImportListModalContentConnector extends Component {
{...this.props}
onSavePress={this.onSavePress}
onTestPress={this.onTestPress}
onAdvancedSettingsPress={this.onAdvancedSettingsPress}
onInputChange={this.onInputChange}
onFieldChange={this.onFieldChange}
/>
@@ -92,6 +104,7 @@ EditImportListModalContentConnector.propTypes = {
setImportListFieldValue: PropTypes.func.isRequired,
saveImportList: PropTypes.func.isRequired,
testImportList: PropTypes.func.isRequired,
toggleAdvancedSettings: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -33,7 +33,7 @@ const enableOptions = [
get value() {
return translate('NoChange');
},
disabled: true,
isDisabled: true,
},
{
key: 'enabled',

View File

@@ -32,7 +32,7 @@ const enableOptions = [
get value() {
return translate('NoChange');
},
disabled: true,
isDisabled: true,
},
{
key: 'enabled',

View File

@@ -120,7 +120,8 @@ const editionTokens = [
];
const customFormatTokens = [
{ token: '{Custom Formats}', example: 'Surround Sound x264' }
{ token: '{Custom Formats}', example: 'Surround Sound x264' },
{ token: '{Custom Format:FormatName}', example: 'AMZN' }
];
const originalTokens = [

View File

@@ -17,7 +17,7 @@
}
.small {
width: 480px;
width: 490px;
}
.large {
@@ -26,8 +26,8 @@
.token {
flex: 0 0 50%;
padding: 6px 16px;
background-color: var(--popoverTitleBorderColor);
padding: 6px;
background-color: var(--popoverTitleBackgroundColor);
font-family: $monoSpaceFontFamily;
}
@@ -37,8 +37,8 @@
align-self: stretch;
justify-content: space-between;
flex: 0 0 50%;
padding: 6px 16px;
background-color: var(--popoverTitleBackgroundColor);
padding: 6px;
background-color: var(--popoverBodyBackgroundColor);
.footNote {
padding: 2px;

View File

@@ -14,6 +14,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import AdvancedSettingsButton from 'Settings/AdvancedSettingsButton';
import translate from 'Utilities/String/translate';
import NotificationEventItems from './NotificationEventItems';
import styles from './EditNotificationModalContent.css';
@@ -32,6 +33,7 @@ function EditNotificationModalContent(props) {
onModalClose,
onSavePress,
onTestPress,
onAdvancedSettingsPress,
onDeleteNotificationPress,
...otherProps
} = props;
@@ -136,6 +138,12 @@ function EditNotificationModalContent(props) {
</Button>
}
<AdvancedSettingsButton
advancedSettings={advancedSettings}
onAdvancedSettingsPress={onAdvancedSettingsPress}
showLabel={false}
/>
<SpinnerErrorButton
isSpinning={isTesting}
error={saveError}
@@ -175,6 +183,7 @@ EditNotificationModalContent.propTypes = {
onModalClose: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onTestPress: PropTypes.func.isRequired,
onAdvancedSettingsPress: PropTypes.func.isRequired,
onDeleteNotificationPress: PropTypes.func
};

View File

@@ -2,7 +2,13 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { saveNotification, setNotificationFieldValue, setNotificationValue, testNotification } from 'Store/Actions/settingsActions';
import {
saveNotification,
setNotificationFieldValue,
setNotificationValue,
testNotification,
toggleAdvancedSettings
} from 'Store/Actions/settingsActions';
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
import EditNotificationModalContent from './EditNotificationModalContent';
@@ -23,7 +29,8 @@ const mapDispatchToProps = {
setNotificationValue,
setNotificationFieldValue,
saveNotification,
testNotification
testNotification,
toggleAdvancedSettings
};
class EditNotificationModalContentConnector extends Component {
@@ -56,6 +63,10 @@ class EditNotificationModalContentConnector extends Component {
this.props.testNotification({ id: this.props.id });
};
onAdvancedSettingsPress = () => {
this.props.toggleAdvancedSettings();
};
//
// Render
@@ -65,6 +76,7 @@ class EditNotificationModalContentConnector extends Component {
{...this.props}
onSavePress={this.onSavePress}
onTestPress={this.onTestPress}
onAdvancedSettingsPress={this.onAdvancedSettingsPress}
onInputChange={this.onInputChange}
onFieldChange={this.onFieldChange}
/>
@@ -82,6 +94,7 @@ EditNotificationModalContentConnector.propTypes = {
setNotificationFieldValue: PropTypes.func.isRequired,
saveNotification: PropTypes.func.isRequired,
testNotification: PropTypes.func.isRequired,
toggleAdvancedSettings: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -12,7 +12,7 @@ export default function TagInUse(props) {
return null;
}
if (count > 1 && labelPlural ) {
if (count > 1 && labelPlural) {
return (
<div>
{count} {labelPlural.toLowerCase()}

View File

@@ -1,8 +1,11 @@
import $ from 'jquery';
import _ from 'lodash';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import getProviderState from 'Utilities/State/getProviderState';
import { set } from '../baseActions';
const abortCurrentRequests = {};
let lastTestData = null;
export function createCancelTestProviderHandler(section) {
return function(getState, payload, dispatch) {
@@ -17,10 +20,25 @@ function createTestProviderHandler(section, url) {
return function(getState, payload, dispatch) {
dispatch(set({ section, isTesting: true }));
const testData = getProviderState(payload, getState, section);
const {
queryParams = {},
...otherPayload
} = payload;
const testData = getProviderState({ ...otherPayload }, getState, section);
const params = { ...queryParams };
// If the user is re-testing the same provider without changes
// force it to be tested.
if (_.isEqual(testData, lastTestData)) {
params.forceTest = true;
}
lastTestData = testData;
const ajaxOptions = {
url: `${url}/test`,
url: `${url}/test?${$.param(params, true)}`,
method: 'POST',
contentType: 'application/json',
dataType: 'json',
@@ -32,6 +50,8 @@ function createTestProviderHandler(section, url) {
abortCurrentRequests[section] = abortRequest;
request.done((data) => {
lastTestData = null;
dispatch(set({
section,
isTesting: false,

View File

@@ -0,0 +1,23 @@
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import Movie from 'Movie/Movie';
function createMultiMoviesSelector(movieIds: number[]) {
return createSelector(
(state: AppState) => state.movies.itemMap,
(state: AppState) => state.movies.items,
(itemMap, allMovies) => {
return movieIds.reduce((acc: Movie[], movieId) => {
const movie = allMovies[itemMap[movieId]];
if (movie) {
acc.push(movie);
}
return acc;
}, []);
}
);
}
export default createMultiMoviesSelector;

View File

@@ -10,15 +10,6 @@
width: 100%;
}
.commandName {
display: inline-block;
min-width: 220px;
}
.userAgent {
color: #b0b0b0;
}
.queued,
.started,
.ended {

View File

@@ -2,14 +2,12 @@
// Please do not change this file!
interface CssExports {
'actions': string;
'commandName': string;
'duration': string;
'ended': string;
'queued': string;
'started': string;
'trigger': string;
'triggerContent': string;
'userAgent': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@@ -1,279 +0,0 @@
import moment from 'moment';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Icon from 'Components/Icon';
import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow';
import { icons, kinds } from 'Helpers/Props';
import formatDate from 'Utilities/Date/formatDate';
import formatDateTime from 'Utilities/Date/formatDateTime';
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
import styles from './QueuedTaskRow.css';
function getStatusIconProps(status, message) {
const title = titleCase(status);
switch (status) {
case 'queued':
return {
name: icons.PENDING,
title
};
case 'started':
return {
name: icons.REFRESH,
isSpinning: true,
title
};
case 'completed':
return {
name: icons.CHECK,
kind: kinds.SUCCESS,
title: message === 'Completed' ? title : `${title}: ${message}`
};
case 'failed':
return {
name: icons.FATAL,
kind: kinds.DANGER,
title: `${title}: ${message}`
};
default:
return {
name: icons.UNKNOWN,
title
};
}
}
function getFormattedDates(props) {
const {
queued,
started,
ended,
showRelativeDates,
shortDateFormat
} = props;
if (showRelativeDates) {
return {
queuedAt: moment(queued).fromNow(),
startedAt: started ? moment(started).fromNow() : '-',
endedAt: ended ? moment(ended).fromNow() : '-'
};
}
return {
queuedAt: formatDate(queued, shortDateFormat),
startedAt: started ? formatDate(started, shortDateFormat) : '-',
endedAt: ended ? formatDate(ended, shortDateFormat) : '-'
};
}
class QueuedTaskRow extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
...getFormattedDates(props),
isCancelConfirmModalOpen: false
};
this._updateTimeoutId = null;
}
componentDidMount() {
this.setUpdateTimer();
}
componentDidUpdate(prevProps) {
const {
queued,
started,
ended
} = this.props;
if (
queued !== prevProps.queued ||
started !== prevProps.started ||
ended !== prevProps.ended
) {
this.setState(getFormattedDates(this.props));
}
}
componentWillUnmount() {
if (this._updateTimeoutId) {
this._updateTimeoutId = clearTimeout(this._updateTimeoutId);
}
}
//
// Control
setUpdateTimer() {
this._updateTimeoutId = setTimeout(() => {
this.setState(getFormattedDates(this.props));
this.setUpdateTimer();
}, 30000);
}
//
// Listeners
onCancelPress = () => {
this.setState({
isCancelConfirmModalOpen: true
});
};
onAbortCancel = () => {
this.setState({
isCancelConfirmModalOpen: false
});
};
//
// Render
render() {
const {
trigger,
commandName,
queued,
started,
ended,
status,
duration,
message,
clientUserAgent,
longDateFormat,
timeFormat,
onCancelPress
} = this.props;
const {
queuedAt,
startedAt,
endedAt,
isCancelConfirmModalOpen
} = this.state;
let triggerIcon = icons.QUICK;
if (trigger === 'manual') {
triggerIcon = icons.INTERACTIVE;
} else if (trigger === 'scheduled') {
triggerIcon = icons.SCHEDULED;
}
return (
<TableRow>
<TableRowCell className={styles.trigger}>
<span className={styles.triggerContent}>
<Icon
name={triggerIcon}
title={titleCase(trigger)}
/>
<Icon
{...getStatusIconProps(status, message)}
/>
</span>
</TableRowCell>
<TableRowCell>
<span className={styles.commandName}>
{commandName}
</span>
{
clientUserAgent ?
<span className={styles.userAgent} title={translate('TaskUserAgentTooltip')}>
{translate('From')}: {clientUserAgent}
</span> :
null
}
</TableRowCell>
<TableRowCell
className={styles.queued}
title={formatDateTime(queued, longDateFormat, timeFormat)}
>
{queuedAt}
</TableRowCell>
<TableRowCell
className={styles.started}
title={formatDateTime(started, longDateFormat, timeFormat)}
>
{startedAt}
</TableRowCell>
<TableRowCell
className={styles.ended}
title={formatDateTime(ended, longDateFormat, timeFormat)}
>
{endedAt}
</TableRowCell>
<TableRowCell className={styles.duration}>
{formatTimeSpan(duration)}
</TableRowCell>
<TableRowCell
className={styles.actions}
>
{
status === 'queued' &&
<IconButton
title={translate('RemovedFromTaskQueue')}
name={icons.REMOVE}
onPress={this.onCancelPress}
/>
}
</TableRowCell>
<ConfirmModal
isOpen={isCancelConfirmModalOpen}
kind={kinds.DANGER}
title={translate('Cancel')}
message={translate('CancelPendingTask')}
confirmLabel={translate('YesCancel')}
cancelLabel={translate('NoLeaveIt')}
onConfirm={onCancelPress}
onCancel={this.onAbortCancel}
/>
</TableRow>
);
}
}
QueuedTaskRow.propTypes = {
trigger: PropTypes.string.isRequired,
commandName: PropTypes.string.isRequired,
queued: PropTypes.string.isRequired,
started: PropTypes.string,
ended: PropTypes.string,
status: PropTypes.string.isRequired,
duration: PropTypes.string,
message: PropTypes.string,
clientUserAgent: PropTypes.string,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
longDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
onCancelPress: PropTypes.func.isRequired
};
export default QueuedTaskRow;

View File

@@ -0,0 +1,238 @@
import moment from 'moment';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { CommandBody } from 'Commands/Command';
import Icon from 'Components/Icon';
import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow';
import useModalOpenState from 'Helpers/Hooks/useModalOpenState';
import { icons, kinds } from 'Helpers/Props';
import { cancelCommand } from 'Store/Actions/commandActions';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import formatDate from 'Utilities/Date/formatDate';
import formatDateTime from 'Utilities/Date/formatDateTime';
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
import QueuedTaskRowNameCell from './QueuedTaskRowNameCell';
import styles from './QueuedTaskRow.css';
function getStatusIconProps(status: string, message: string | undefined) {
const title = titleCase(status);
switch (status) {
case 'queued':
return {
name: icons.PENDING,
title,
};
case 'started':
return {
name: icons.REFRESH,
isSpinning: true,
title,
};
case 'completed':
return {
name: icons.CHECK,
kind: kinds.SUCCESS,
title: message === 'Completed' ? title : `${title}: ${message}`,
};
case 'failed':
return {
name: icons.FATAL,
kind: kinds.DANGER,
title: `${title}: ${message}`,
};
default:
return {
name: icons.UNKNOWN,
title,
};
}
}
function getFormattedDates(
queued: string,
started: string | undefined,
ended: string | undefined,
showRelativeDates: boolean,
shortDateFormat: string
) {
if (showRelativeDates) {
return {
queuedAt: moment(queued).fromNow(),
startedAt: started ? moment(started).fromNow() : '-',
endedAt: ended ? moment(ended).fromNow() : '-',
};
}
return {
queuedAt: formatDate(queued, shortDateFormat),
startedAt: started ? formatDate(started, shortDateFormat) : '-',
endedAt: ended ? formatDate(ended, shortDateFormat) : '-',
};
}
interface QueuedTimes {
queuedAt: string;
startedAt: string;
endedAt: string;
}
export interface QueuedTaskRowProps {
id: number;
trigger: string;
commandName: string;
queued: string;
started?: string;
ended?: string;
status: string;
duration?: string;
message?: string;
body: CommandBody;
clientUserAgent?: string;
}
export default function QueuedTaskRow(props: QueuedTaskRowProps) {
const {
id,
trigger,
commandName,
queued,
started,
ended,
status,
duration,
message,
body,
clientUserAgent,
} = props;
const dispatch = useDispatch();
const { longDateFormat, shortDateFormat, showRelativeDates, timeFormat } =
useSelector(createUISettingsSelector());
const updateTimeTimeoutId = useRef<ReturnType<typeof setTimeout> | null>(
null
);
const [times, setTimes] = useState<QueuedTimes>(
getFormattedDates(
queued,
started,
ended,
showRelativeDates,
shortDateFormat
)
);
const [
isCancelConfirmModalOpen,
openCancelConfirmModal,
closeCancelConfirmModal,
] = useModalOpenState(false);
const handleCancelPress = useCallback(() => {
dispatch(cancelCommand({ id }));
}, [id, dispatch]);
useEffect(() => {
updateTimeTimeoutId.current = setTimeout(() => {
setTimes(
getFormattedDates(
queued,
started,
ended,
showRelativeDates,
shortDateFormat
)
);
}, 30000);
return () => {
if (updateTimeTimeoutId.current) {
clearTimeout(updateTimeTimeoutId.current);
}
};
}, [queued, started, ended, showRelativeDates, shortDateFormat, setTimes]);
const { queuedAt, startedAt, endedAt } = times;
let triggerIcon = icons.QUICK;
if (trigger === 'manual') {
triggerIcon = icons.INTERACTIVE;
} else if (trigger === 'scheduled') {
triggerIcon = icons.SCHEDULED;
}
return (
<TableRow>
<TableRowCell className={styles.trigger}>
<span className={styles.triggerContent}>
<Icon name={triggerIcon} title={titleCase(trigger)} />
<Icon {...getStatusIconProps(status, message)} />
</span>
</TableRowCell>
<QueuedTaskRowNameCell
commandName={commandName}
body={body}
clientUserAgent={clientUserAgent}
/>
<TableRowCell
className={styles.queued}
title={formatDateTime(queued, longDateFormat, timeFormat)}
>
{queuedAt}
</TableRowCell>
<TableRowCell
className={styles.started}
title={formatDateTime(started, longDateFormat, timeFormat)}
>
{startedAt}
</TableRowCell>
<TableRowCell
className={styles.ended}
title={formatDateTime(ended, longDateFormat, timeFormat)}
>
{endedAt}
</TableRowCell>
<TableRowCell className={styles.duration}>
{formatTimeSpan(duration)}
</TableRowCell>
<TableRowCell className={styles.actions}>
{status === 'queued' && (
<IconButton
title={translate('RemovedFromTaskQueue')}
name={icons.REMOVE}
onPress={openCancelConfirmModal}
/>
)}
</TableRowCell>
<ConfirmModal
isOpen={isCancelConfirmModalOpen}
kind={kinds.DANGER}
title={translate('Cancel')}
message={translate('CancelPendingTask')}
confirmLabel={translate('YesCancel')}
cancelLabel={translate('NoLeaveIt')}
onConfirm={handleCancelPress}
onCancel={closeCancelConfirmModal}
/>
</TableRow>
);
}

View File

@@ -1,31 +0,0 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { cancelCommand } from 'Store/Actions/commandActions';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import QueuedTaskRow from './QueuedTaskRow';
function createMapStateToProps() {
return createSelector(
createUISettingsSelector(),
(uiSettings) => {
return {
showRelativeDates: uiSettings.showRelativeDates,
shortDateFormat: uiSettings.shortDateFormat,
longDateFormat: uiSettings.longDateFormat,
timeFormat: uiSettings.timeFormat
};
}
);
}
function createMapDispatchToProps(dispatch, props) {
return {
onCancelPress() {
dispatch(cancelCommand({
id: props.id
}));
}
};
}
export default connect(createMapStateToProps, createMapDispatchToProps)(QueuedTaskRow);

View File

@@ -0,0 +1,8 @@
.commandName {
display: inline-block;
min-width: 220px;
}
.userAgent {
color: #b0b0b0;
}

View File

@@ -0,0 +1,8 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'commandName': string;
'userAgent': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@@ -0,0 +1,49 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { CommandBody } from 'Commands/Command';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import createMultiMoviesSelector from 'Store/Selectors/createMultiMoviesSelector';
import translate from 'Utilities/String/translate';
import styles from './QueuedTaskRowNameCell.css';
export interface QueuedTaskRowNameCellProps {
commandName: string;
body: CommandBody;
clientUserAgent?: string;
}
export default function QueuedTaskRowNameCell(
props: QueuedTaskRowNameCellProps
) {
const { commandName, body, clientUserAgent } = props;
const movieIds = [...(body.movieIds ?? [])];
if (body.movieId) {
movieIds.push(body.movieId);
}
const movies = useSelector(createMultiMoviesSelector(movieIds));
const sortedMovies = movies.sort((a, b) =>
a.sortTitle.localeCompare(b.sortTitle)
);
return (
<TableRowCell>
<span className={styles.commandName}>
{commandName}
{sortedMovies.length ? (
<span> - {sortedMovies.map((m) => m.title).join(', ')}</span>
) : null}
</span>
{clientUserAgent ? (
<span
className={styles.userAgent}
title={translate('TaskUserAgentTooltip')}
>
{translate('From')}: {clientUserAgent}
</span>
) : null}
</TableRowCell>
);
}

View File

@@ -1,90 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import FieldSet from 'Components/FieldSet';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import translate from 'Utilities/String/translate';
import QueuedTaskRowConnector from './QueuedTaskRowConnector';
const columns = [
{
name: 'trigger',
label: () => translate('Trigger'),
isVisible: true
},
{
name: 'commandName',
label: () => translate('Name'),
isVisible: true
},
{
name: 'queued',
label: () => translate('Queued'),
isVisible: true
},
{
name: 'started',
label: () => translate('Started'),
isVisible: true
},
{
name: 'ended',
label: () => translate('Ended'),
isVisible: true
},
{
name: 'duration',
label: () => translate('Duration'),
isVisible: true
},
{
name: 'actions',
isVisible: true
}
];
function QueuedTasks(props) {
const {
isFetching,
isPopulated,
items
} = props;
return (
<FieldSet legend={translate('Queue')}>
{
isFetching && !isPopulated &&
<LoadingIndicator />
}
{
isPopulated &&
<Table
columns={columns}
>
<TableBody>
{
items.map((item) => {
return (
<QueuedTaskRowConnector
key={item.id}
{...item}
/>
);
})
}
</TableBody>
</Table>
}
</FieldSet>
);
}
QueuedTasks.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
items: PropTypes.array.isRequired
};
export default QueuedTasks;

View File

@@ -0,0 +1,74 @@
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import AppState from 'App/State/AppState';
import FieldSet from 'Components/FieldSet';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import { fetchCommands } from 'Store/Actions/commandActions';
import translate from 'Utilities/String/translate';
import QueuedTaskRow from './QueuedTaskRow';
const columns = [
{
name: 'trigger',
label: '',
isVisible: true,
},
{
name: 'commandName',
label: () => translate('Name'),
isVisible: true,
},
{
name: 'queued',
label: () => translate('Queued'),
isVisible: true,
},
{
name: 'started',
label: () => translate('Started'),
isVisible: true,
},
{
name: 'ended',
label: () => translate('Ended'),
isVisible: true,
},
{
name: 'duration',
label: () => translate('Duration'),
isVisible: true,
},
{
name: 'actions',
isVisible: true,
},
];
export default function QueuedTasks() {
const dispatch = useDispatch();
const { isFetching, isPopulated, items } = useSelector(
(state: AppState) => state.commands
);
useEffect(() => {
dispatch(fetchCommands());
}, [dispatch]);
return (
<FieldSet legend={translate('Queue')}>
{isFetching && !isPopulated && <LoadingIndicator />}
{isPopulated && (
<Table columns={columns}>
<TableBody>
{items.map((item) => {
return <QueuedTaskRow key={item.id} {...item} />;
})}
</TableBody>
</Table>
)}
</FieldSet>
);
}

View File

@@ -1,46 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchCommands } from 'Store/Actions/commandActions';
import QueuedTasks from './QueuedTasks';
function createMapStateToProps() {
return createSelector(
(state) => state.commands,
(commands) => {
return commands;
}
);
}
const mapDispatchToProps = {
dispatchFetchCommands: fetchCommands
};
class QueuedTasksConnector extends Component {
//
// Lifecycle
componentDidMount() {
this.props.dispatchFetchCommands();
}
//
// Render
render() {
return (
<QueuedTasks
{...this.props}
/>
);
}
}
QueuedTasksConnector.propTypes = {
dispatchFetchCommands: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(QueuedTasksConnector);

View File

@@ -2,7 +2,7 @@ import React from 'react';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import translate from 'Utilities/String/translate';
import QueuedTasksConnector from './Queued/QueuedTasksConnector';
import QueuedTasks from './Queued/QueuedTasks';
import ScheduledTasksConnector from './Scheduled/ScheduledTasksConnector';
function Tasks() {
@@ -10,7 +10,7 @@ function Tasks() {
<PageContent title={translate('Tasks')}>
<PageContentBody>
<ScheduledTasksConnector />
<QueuedTasksConnector />
<QueuedTasks />
</PageContentBody>
</PageContent>
);

View File

@@ -3,13 +3,16 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- Chrome, Safari, Opera, and Firefox OS -->
<meta name="theme-color" content="#595959" />
<!-- Windows Phone -->
<meta name="msapplication-navbutton-color" content="#3a3f51" />
<meta name="msapplication-navbutton-color" content="#464b51" />
<!-- Android/Apple Phone -->
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="format-detection" content="telephone=no">
<meta name="description" content="Radarr" />

View File

@@ -3,13 +3,16 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- Chrome, Safari, Opera, and Firefox OS -->
<meta name="theme-color" content="#595959" />
<!-- Windows Phone -->
<meta name="msapplication-navbutton-color" content="#464b51" />
<!-- Android/Apple Phone -->
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="format-detection" content="telephone=no">
<meta name="description" content="Radarr (Preview)" />

View File

@@ -11,7 +11,7 @@
"lint": "eslint --config frontend/.eslintrc.js --ignore-path frontend/.eslintignore frontend/",
"lint-fix": "yarn lint --fix",
"stylelint-linux": "stylelint $(find frontend -name '*.css') --config frontend/.stylelintrc",
"stylelint-windows": "stylelint frontend/**/*.css --config frontend/.stylelintrc"
"stylelint-windows": "stylelint \"frontend/**/*.css\" --config frontend/.stylelintrc"
},
"repository": "https://github.com/Radarr/Radarr",
"author": "Team Radarr",

View File

@@ -147,16 +147,46 @@
</Otherwise>
</Choose>
<!--
Set architecture to RuntimeInformation.ProcessArchitecture if not specified -->
<Choose>
<When Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture)' == 'X64'">
<PropertyGroup>
<Architecture>x64</Architecture>
</PropertyGroup>
</When>
<When Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture)' == 'X86'">
<PropertyGroup>
<Architecture>x86</Architecture>
</PropertyGroup>
</When>
<When Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture)' == 'Arm64'">
<PropertyGroup>
<Architecture>arm64</Architecture>
</PropertyGroup>
</When>
<When Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture)' == 'Arm'">
<PropertyGroup>
<Architecture>arm</Architecture>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<Architecture></Architecture>
</PropertyGroup>
</Otherwise>
</Choose>
<PropertyGroup Condition="'$(IsWindows)' == 'true' and
'$(RuntimeIdentifier)' == ''">
<_UsingDefaultRuntimeIdentifier>true</_UsingDefaultRuntimeIdentifier>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<RuntimeIdentifier>win-$(Architecture)</RuntimeIdentifier>
</PropertyGroup>
<PropertyGroup Condition="'$(IsLinux)' == 'true' and
'$(RuntimeIdentifier)' == ''">
<_UsingDefaultRuntimeIdentifier>true</_UsingDefaultRuntimeIdentifier>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<RuntimeIdentifier>linux-$(Architecture)</RuntimeIdentifier>
</PropertyGroup>
<PropertyGroup Condition="'$(IsOSX)' == 'true' and

View File

@@ -19,6 +19,8 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"https://baconbits.org/feeds.php?feed=torrents_tv&user=12345&auth=2b51db35e1910123321025a12b9933d2&passkey=mySecret&authkey=2b51db35e1910123321025a12b9933d2")]
[TestCase(@"http://127.0.0.1:9117/dl/indexername?jackett_apikey=flwjiefewklfjacketmySecretsdfldskjfsdlk&path=we0re9f0sdfbase64sfdkfjsdlfjk&file=The+Torrent+File+Name.torrent")]
[TestCase(@"http://nzb.su/getnzb/2b51db35e1912ffc138825a12b9933d2.nzb&i=37292&r=2b51db35e1910123321025a12b9933d2")]
[TestCase(@"https://b-hd.me/torrent/download/auto.343756.is1t1pl127p1sfwur8h4kgyhg1wcsn05")]
[TestCase(@"https://b-hd.me/torrent/download/a-slug-in-the-url.343756.is1t1pl127p1sfwur8h4kgyhg1wcsn05")]
// NzbGet
[TestCase(@"{ ""Name"" : ""ControlUsername"", ""Value"" : ""mySecret"" }, { ""Name"" : ""ControlPassword"", ""Value"" : ""mySecret"" }, ")]

View File

@@ -1,12 +1,15 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.NetworkInformation;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http.Proxy;
@@ -28,11 +31,14 @@ namespace NzbDrone.Common.Http.Dispatchers
private readonly ICached<System.Net.Http.HttpClient> _httpClientCache;
private readonly ICached<CredentialCache> _credentialCache;
private readonly Logger _logger;
public ManagedHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider,
ICreateManagedWebProxy createManagedWebProxy,
ICertificateValidationService certificateValidationService,
IUserAgentBuilder userAgentBuilder,
ICacheManager cacheManager)
ICacheManager cacheManager,
Logger logger)
{
_proxySettingsProvider = proxySettingsProvider;
_createManagedWebProxy = createManagedWebProxy;
@@ -41,6 +47,8 @@ namespace NzbDrone.Common.Http.Dispatchers
_httpClientCache = cacheManager.GetCache<System.Net.Http.HttpClient>(typeof(ManagedHttpDispatcher));
_credentialCache = cacheManager.GetCache<CredentialCache>(typeof(ManagedHttpDispatcher), "credentialcache");
_logger = logger;
}
public async Task<HttpResponse> GetResponseAsync(HttpRequest request, CookieContainer cookies)
@@ -246,7 +254,27 @@ namespace NzbDrone.Common.Http.Dispatchers
return _credentialCache.Get("credentialCache", () => new CredentialCache());
}
private static async ValueTask<Stream> onConnect(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
private bool HasRoutableIPv4Address()
{
// Get all IPv4 addresses from all interfaces and return true if there are any with non-loopback addresses
try
{
var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
return networkInterfaces.Any(ni =>
ni.OperationalStatus == OperationalStatus.Up &&
ni.GetIPProperties().UnicastAddresses.Any(ip =>
ip.Address.AddressFamily == AddressFamily.InterNetwork &&
!IPAddress.IsLoopback(ip.Address)));
}
catch (Exception e)
{
_logger.Debug(e, "Caught exception while GetAllNetworkInterfaces assuming IPv4 connectivity: {0}", e.Message);
return true;
}
}
private async ValueTask<Stream> onConnect(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
{
// Until .NET supports an implementation of Happy Eyeballs (https://tools.ietf.org/html/rfc8305#section-2), let's make IPv4 fallback work in a simple way.
// This issue is being tracked at https://github.com/dotnet/runtime/issues/26177 and expected to be fixed in .NET 6.
@@ -269,10 +297,10 @@ namespace NzbDrone.Common.Http.Dispatchers
}
catch
{
// very naively fallback to ipv4 permanently for this execution based on the response of the first connection attempt.
// note that this may cause users to eventually get switched to ipv4 (on a random failure when they are switching networks, for instance)
// but in the interest of keeping this implementation simple, this is acceptable.
useIPv6 = false;
// Do not retry IPv6 if a routable IPv4 address is available, otherwise continue to attempt IPv6 connections.
var routableIPv4 = HasRoutableIPv4Address();
_logger.Info("IPv4 is available: {0}, IPv6 will be {1}", routableIPv4, routableIPv4 ? "disabled" : "left enabled");
useIPv6 = !routableIPv4;
}
finally
{

View File

@@ -18,6 +18,7 @@ namespace NzbDrone.Common.Instrumentation
new (@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
new (@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"-hd.me/torrent/[a-z0-9-]\.[0-9]+\.(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Trackers Announce Keys; Designed for Qbit Json; should work for all in theory
new (@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce", RegexOptions.Compiled | RegexOptions.IgnoreCase),

View File

@@ -0,0 +1,71 @@
using System.Collections.Generic;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.CustomFormats.Specifications.LanguageSpecification
{
[TestFixture]
public class MultiLanguageFixture : CoreTest<Core.CustomFormats.LanguageSpecification>
{
private CustomFormatInput _input;
[SetUp]
public void Setup()
{
_input = new CustomFormatInput
{
MovieInfo = Builder<ParsedMovieInfo>.CreateNew().Build(),
Movie = Builder<Movie>.CreateNew().With(m => m.MovieMetadata.Value.OriginalLanguage = Language.English).Build(),
Size = 100.Megabytes(),
Languages = new List<Language>
{
Language.English,
Language.French
},
Filename = "Movie.Title.2024"
};
}
[Test]
public void should_match_one_language()
{
Subject.Value = Language.French.Id;
Subject.Negate = false;
Subject.IsSatisfiedBy(_input).Should().BeTrue();
}
[Test]
public void should_not_match_different_language()
{
Subject.Value = Language.Spanish.Id;
Subject.Negate = false;
Subject.IsSatisfiedBy(_input).Should().BeFalse();
}
[Test]
public void should_not_match_negated_when_one_language_matches()
{
Subject.Value = Language.French.Id;
Subject.Negate = true;
Subject.IsSatisfiedBy(_input).Should().BeFalse();
}
[Test]
public void should_not_match_negated_when_all_languages_do_not_match()
{
Subject.Value = Language.Spanish.Id;
Subject.Negate = true;
Subject.IsSatisfiedBy(_input).Should().BeTrue();
}
}
}

View File

@@ -0,0 +1,80 @@
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.CustomFormats.Specifications.LanguageSpecification
{
[TestFixture]
public class OriginalLanguageFixture : CoreTest<Core.CustomFormats.LanguageSpecification>
{
private CustomFormatInput _input;
[SetUp]
public void Setup()
{
_input = new CustomFormatInput
{
MovieInfo = Builder<ParsedMovieInfo>.CreateNew().Build(),
Movie = Builder<Movie>.CreateNew().With(m => m.MovieMetadata.Value.OriginalLanguage = Language.English).Build(),
Size = 100.Megabytes(),
Languages = new List<Language>
{
Language.French
},
Filename = "Movie.Title.2024"
};
}
public void GivenLanguages(params Language[] languages)
{
_input.Languages = languages.ToList();
}
[Test]
public void should_match_same_single_language()
{
GivenLanguages(Language.English);
Subject.Value = Language.Original.Id;
Subject.Negate = false;
Subject.IsSatisfiedBy(_input).Should().BeTrue();
}
[Test]
public void should_not_match_different_single_language()
{
Subject.Value = Language.Original.Id;
Subject.Negate = false;
Subject.IsSatisfiedBy(_input).Should().BeFalse();
}
[Test]
public void should_not_match_negated_same_single_language()
{
GivenLanguages(Language.English);
Subject.Value = Language.Original.Id;
Subject.Negate = true;
Subject.IsSatisfiedBy(_input).Should().BeFalse();
}
[Test]
public void should_match_negated_different_single_language()
{
Subject.Value = Language.Original.Id;
Subject.Negate = true;
Subject.IsSatisfiedBy(_input).Should().BeTrue();
}
}
}

View File

@@ -0,0 +1,70 @@
using System.Collections.Generic;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.CustomFormats.Specifications.LanguageSpecification
{
[TestFixture]
public class SingleLanguageFixture : CoreTest<Core.CustomFormats.LanguageSpecification>
{
private CustomFormatInput _input;
[SetUp]
public void Setup()
{
_input = new CustomFormatInput
{
MovieInfo = Builder<ParsedMovieInfo>.CreateNew().Build(),
Movie = Builder<Movie>.CreateNew().With(m => m.MovieMetadata.Value.OriginalLanguage = Language.English).Build(),
Size = 100.Megabytes(),
Languages = new List<Language>
{
Language.French
},
Filename = "Movie.Title.2024"
};
}
[Test]
public void should_match_same_language()
{
Subject.Value = Language.French.Id;
Subject.Negate = false;
Subject.IsSatisfiedBy(_input).Should().BeTrue();
}
[Test]
public void should_not_match_different_language()
{
Subject.Value = Language.Spanish.Id;
Subject.Negate = false;
Subject.IsSatisfiedBy(_input).Should().BeFalse();
}
[Test]
public void should_not_match_negated_same_language()
{
Subject.Value = Language.French.Id;
Subject.Negate = true;
Subject.IsSatisfiedBy(_input).Should().BeFalse();
}
[Test]
public void should_match_negated_different_language()
{
Subject.Value = Language.Spanish.Id;
Subject.Negate = true;
Subject.IsSatisfiedBy(_input).Should().BeTrue();
}
}
}

View File

@@ -25,7 +25,7 @@ namespace NzbDrone.Core.Test.Framework
Mocker.SetConstant<IHttpProxySettingsProvider>(new HttpProxySettingsProvider(Mocker.Resolve<ConfigService>()));
Mocker.SetConstant<ICreateManagedWebProxy>(new ManagedWebProxyFactory(Mocker.Resolve<CacheManager>()));
Mocker.SetConstant<ICertificateValidationService>(new X509CertificateValidationService(Mocker.Resolve<ConfigService>(), TestLogger));
Mocker.SetConstant<IHttpDispatcher>(new ManagedHttpDispatcher(Mocker.Resolve<IHttpProxySettingsProvider>(), Mocker.Resolve<ICreateManagedWebProxy>(), Mocker.Resolve<ICertificateValidationService>(), Mocker.Resolve<UserAgentBuilder>(), Mocker.Resolve<CacheManager>()));
Mocker.SetConstant<IHttpDispatcher>(new ManagedHttpDispatcher(Mocker.Resolve<IHttpProxySettingsProvider>(), Mocker.Resolve<ICreateManagedWebProxy>(), Mocker.Resolve<ICertificateValidationService>(), Mocker.Resolve<UserAgentBuilder>(), Mocker.Resolve<CacheManager>(), TestLogger));
Mocker.SetConstant<IHttpClient>(new HttpClient(Array.Empty<IHttpRequestInterceptor>(), Mocker.Resolve<CacheManager>(), Mocker.Resolve<RateLimitService>(), Mocker.Resolve<IHttpDispatcher>(), TestLogger));
Mocker.SetConstant<IRadarrCloudRequestBuilder>(new RadarrCloudRequestBuilder());
}

View File

@@ -1,6 +1,9 @@
using System.Collections.Generic;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.AutoTagging;
using NzbDrone.Core.AutoTagging.Specifications;
using NzbDrone.Core.Housekeeping.Housekeepers;
using NzbDrone.Core.Profiles.Releases;
using NzbDrone.Core.Tags;
@@ -43,5 +46,35 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
Subject.Clean();
AllStoredModels.Should().HaveCount(1);
}
[Test]
public void should_not_delete_used_auto_tagging_tag_specification_tags()
{
var tags = Builder<Tag>
.CreateListOfSize(2)
.All()
.With(x => x.Id = 0)
.BuildList();
Db.InsertMany(tags);
var autoTags = Builder<AutoTag>.CreateListOfSize(1)
.All()
.With(x => x.Id = 0)
.With(x => x.Specifications = new List<IAutoTaggingSpecification>
{
new TagSpecification
{
Name = "Test",
Value = tags[0].Id
}
})
.BuildList();
Mocker.GetMock<IAutoTaggingRepository>().Setup(s => s.All())
.Returns(autoTags);
Subject.Clean();
AllStoredModels.Should().HaveCount(1);
}
}
}

View File

@@ -0,0 +1,138 @@
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
{
[TestFixture]
public class CustomFormatsFixture : CoreTest<FileNameBuilder>
{
private Movie _movie;
private MovieFile _movieFile;
private NamingConfig _namingConfig;
private List<CustomFormat> _customFormats;
[SetUp]
public void Setup()
{
_movie = Builder<Movie>
.CreateNew()
.With(s => s.Title = "South Park")
.Build();
_namingConfig = NamingConfig.Default;
_namingConfig.RenameMovies = true;
Mocker.GetMock<INamingConfigService>()
.Setup(c => c.GetConfig()).Returns(_namingConfig);
_movieFile = new MovieFile { Quality = new QualityModel(Quality.HDTV720p), ReleaseGroup = "RadarrTest" };
_customFormats = new List<CustomFormat>()
{
new CustomFormat()
{
Name = "INTERNAL",
IncludeCustomFormatWhenRenaming = true
},
new CustomFormat()
{
Name = "AMZN",
IncludeCustomFormatWhenRenaming = true
},
new CustomFormat()
{
Name = "NAME WITH SPACES",
IncludeCustomFormatWhenRenaming = true
},
new CustomFormat()
{
Name = "NotIncludedFormat",
IncludeCustomFormatWhenRenaming = false
}
};
Mocker.GetMock<IQualityDefinitionService>()
.Setup(v => v.Get(Moq.It.IsAny<Quality>()))
.Returns<Quality>(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v));
}
[TestCase("{Custom Formats}", "INTERNAL AMZN NAME WITH SPACES")]
public void should_replace_custom_formats(string format, string expected)
{
_namingConfig.StandardMovieFormat = format;
Subject.BuildFileName(_movie, _movieFile, customFormats: _customFormats)
.Should().Be(expected);
}
[TestCase("{Custom Formats}", "")]
public void should_replace_custom_formats_with_no_custom_formats(string format, string expected)
{
_namingConfig.StandardMovieFormat = format;
Subject.BuildFileName(_movie, _movieFile, customFormats: new List<CustomFormat>())
.Should().Be(expected);
}
[TestCase("{Custom Formats:-INTERNAL}", "AMZN NAME WITH SPACES")]
[TestCase("{Custom Formats:-NAME WITH SPACES}", "INTERNAL AMZN")]
[TestCase("{Custom Formats:-INTERNAL,NAME WITH SPACES}", "AMZN")]
[TestCase("{Custom Formats:INTERNAL}", "INTERNAL")]
[TestCase("{Custom Formats:NAME WITH SPACES}", "NAME WITH SPACES")]
[TestCase("{Custom Formats:INTERNAL,NAME WITH SPACES}", "INTERNAL NAME WITH SPACES")]
public void should_replace_custom_formats_with_filtered_names(string format, string expected)
{
_namingConfig.StandardMovieFormat = format;
Subject.BuildFileName(_movie, _movieFile, customFormats: _customFormats)
.Should().Be(expected);
}
[TestCase("{Custom Formats:-}", "{Custom Formats:-}")]
[TestCase("{Custom Formats:}", "{Custom Formats:}")]
public void should_not_replace_custom_formats_due_to_invalid_token(string format, string expected)
{
_namingConfig.StandardMovieFormat = format;
Subject.BuildFileName(_movie, _movieFile, customFormats: _customFormats)
.Should().Be(expected);
}
[TestCase("{Custom Format}", "")]
[TestCase("{Custom Format:INTERNAL}", "INTERNAL")]
[TestCase("{Custom Format:AMZN}", "AMZN")]
[TestCase("{Custom Format:NAME WITH SPACES}", "NAME WITH SPACES")]
[TestCase("{Custom Format:DOESNOTEXIST}", "")]
[TestCase("{Custom Format:INTERNAL} - {Custom Format:AMZN}", "INTERNAL - AMZN")]
[TestCase("{Custom Format:AMZN} - {Custom Format:INTERNAL}", "AMZN - INTERNAL")]
public void should_replace_custom_format(string format, string expected)
{
_namingConfig.StandardMovieFormat = format;
Subject.BuildFileName(_movie, _movieFile, customFormats: _customFormats)
.Should().Be(expected);
}
[TestCase("{Custom Format}", "")]
[TestCase("{Custom Format:INTERNAL}", "")]
[TestCase("{Custom Format:AMZN}", "")]
public void should_replace_custom_format_with_no_custom_formats(string format, string expected)
{
_namingConfig.StandardMovieFormat = format;
Subject.BuildFileName(_movie, _movieFile, customFormats: new List<CustomFormat>())
.Should().Be(expected);
}
}
}

View File

@@ -55,7 +55,8 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie.Title.2019.1080p.AMZN.WEB-Rip.DDP.5.1.HEVC", null)]
[TestCase("Movie Name (2017) [2160p REMUX] [HEVC DV HYBRID HDR10+ Dolby TrueHD Atmos 7 1 24-bit Audio English] [Data Lass]", null)]
[TestCase("Movie Name (2017) [2160p REMUX] [HEVC DV HYBRID HDR10+ Dolby TrueHD Atmos 7 1 24-bit Audio English]-DataLass", "DataLass")]
public void should_parse_expected_release_group(string title, string expected)
[TestCase("Movie Name (2017) (Showtime) (1080p.BD.DD5.1.x265-TheSickle[TAoE])", "TheSickle")]
public void should_parse_release_group(string title, string expected)
{
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
}

View File

@@ -84,7 +84,8 @@ namespace NzbDrone.Core.Annotations
Device,
TagSelect,
RootFolder,
QualityProfile
QualityProfile,
MovieTag
}
public enum HiddenType

View File

@@ -0,0 +1,36 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.AutoTagging.Specifications
{
public class TagSpecificationValidator : AbstractValidator<TagSpecification>
{
public TagSpecificationValidator()
{
RuleFor(c => c.Value).GreaterThan(0);
}
}
public class TagSpecification : AutoTaggingSpecificationBase
{
private static readonly TagSpecificationValidator Validator = new ();
public override int Order => 1;
public override string ImplementationName => "Tag";
[FieldDefinition(1, Label = "AutoTaggingSpecificationTag", Type = FieldType.MovieTag)]
public int Value { get; set; }
protected override bool IsSatisfiedByWithoutNegate(Movie movie)
{
return movie.Tags.Contains(Value);
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Datastore;

View File

@@ -20,7 +20,7 @@ namespace NzbDrone.Core.CustomFormats
public abstract NzbDroneValidationResult Validate();
public bool IsSatisfiedBy(CustomFormatInput input)
public virtual bool IsSatisfiedBy(CustomFormatInput input)
{
var match = IsSatisfiedByWithoutNegate(input);

View File

@@ -30,6 +30,16 @@ namespace NzbDrone.Core.CustomFormats
[FieldDefinition(1, Label = "Language", Type = FieldType.Select, SelectOptions = typeof(LanguageFieldConverter))]
public int Value { get; set; }
public override bool IsSatisfiedBy(CustomFormatInput input)
{
if (Negate)
{
return IsSatisfiedByWithNegate(input);
}
return IsSatisfiedByWithoutNegate(input);
}
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
{
var comparedLanguage = input.MovieInfo != null && input.Movie != null && Value == Language.Original.Id && input.Movie.MovieMetadata.Value.OriginalLanguage != Language.Unknown
@@ -39,6 +49,15 @@ namespace NzbDrone.Core.CustomFormats
return input?.Languages?.Contains(comparedLanguage) ?? false;
}
private bool IsSatisfiedByWithNegate(CustomFormatInput input)
{
var comparedLanguage = input.MovieInfo != null && input.Movie != null && Value == Language.Original.Id && input.Movie.MovieMetadata.Value.OriginalLanguage != Language.Unknown
? input.Movie.MovieMetadata.Value.OriginalLanguage
: (Language)Value;
return !input.Languages?.Contains(comparedLanguage) ?? false;
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Net;
using Newtonsoft.Json.Linq;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
@@ -101,11 +103,21 @@ namespace NzbDrone.Core.Download.Clients.Deluge
public string AddTorrentFromMagnet(string magnetLink, DelugeSettings settings)
{
var options = new
dynamic options = new ExpandoObject();
options.add_paused = settings.AddPaused;
options.remove_at_ratio = false;
if (settings.DownloadDirectory.IsNotNullOrWhiteSpace())
{
add_paused = settings.AddPaused,
remove_at_ratio = false
};
options.download_location = settings.DownloadDirectory;
}
if (settings.CompletedDirectory.IsNotNullOrWhiteSpace())
{
options.move_completed_path = settings.CompletedDirectory;
options.move_completed = true;
}
var response = ProcessRequest<string>(settings, "core.add_torrent_magnet", magnetLink, options);
@@ -114,11 +126,21 @@ namespace NzbDrone.Core.Download.Clients.Deluge
public string AddTorrentFromFile(string filename, byte[] fileContent, DelugeSettings settings)
{
var options = new
dynamic options = new ExpandoObject();
options.add_paused = settings.AddPaused;
options.remove_at_ratio = false;
if (settings.DownloadDirectory.IsNotNullOrWhiteSpace())
{
add_paused = settings.AddPaused,
remove_at_ratio = false
};
options.download_location = settings.DownloadDirectory;
}
if (settings.CompletedDirectory.IsNotNullOrWhiteSpace())
{
options.move_completed_path = settings.CompletedDirectory;
options.move_completed = true;
}
var response = ProcessRequest<string>(settings, "core.add_torrent_file", filename, fileContent, options);

View File

@@ -61,6 +61,12 @@ namespace NzbDrone.Core.Download.Clients.Deluge
[FieldDefinition(9, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox)]
public bool AddPaused { get; set; }
[FieldDefinition(10, Label = "DownloadClientDelugeSettingsDirectory", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientDelugeSettingsDirectoryHelpText")]
public string DownloadDirectory { get; set; }
[FieldDefinition(11, Label = "DownloadClientDelugeSettingsDirectoryCompleted", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientDelugeSettingsDirectoryCompletedHelpText")]
public string CompletedDirectory { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -388,16 +388,20 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
}
}
var minimumRetention = 60 * 24 * 14;
return new DownloadClientInfo
{
IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "localhost",
OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, destDir) },
RemovesCompletedDownloads = (config.MaxRatioEnabled || (config.MaxSeedingTimeEnabled && config.MaxSeedingTime < minimumRetention)) && (config.MaxRatioAction == QBittorrentMaxRatioAction.Remove || config.MaxRatioAction == QBittorrentMaxRatioAction.DeleteFiles)
RemovesCompletedDownloads = RemovesCompletedDownloads(config)
};
}
private bool RemovesCompletedDownloads(QBittorrentPreferences config)
{
var minimumRetention = 60 * 24 * 14; // 14 days in minutes
return (config.MaxRatioEnabled || (config.MaxSeedingTimeEnabled && config.MaxSeedingTime < minimumRetention)) && (config.MaxRatioAction == QBittorrentMaxRatioAction.Remove || config.MaxRatioAction == QBittorrentMaxRatioAction.DeleteFiles);
}
protected override void Test(List<ValidationFailure> failures)
{
failures.AddIfNotNull(TestConnection());
@@ -448,7 +452,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
// Complain if qBittorrent is configured to remove torrents on max ratio
var config = Proxy.GetConfig(Settings);
if ((config.MaxRatioEnabled || config.MaxSeedingTimeEnabled) && (config.MaxRatioAction == QBittorrentMaxRatioAction.Remove || config.MaxRatioAction == QBittorrentMaxRatioAction.DeleteFiles))
if (RemovesCompletedDownloads(config))
{
return new NzbDroneValidationFailure(string.Empty, _localizationService.GetLocalizedString("DownloadClientQbittorrentValidationRemovesAtRatioLimit"))
{

View File

@@ -139,12 +139,14 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
// Ignore torrents with an empty path
if (torrent.Path.IsNullOrWhiteSpace())
{
_logger.Warn("Torrent '{0}' has an empty download path and will not be processed. Adjust this to an absolute path in rTorrent", torrent.Name);
continue;
}
if (torrent.Path.StartsWith("."))
{
throw new DownloadClientException("Download paths must be absolute. Please specify variable \"directory\" in rTorrent.");
_logger.Warn("Torrent '{0}' has a download path starting with '.' and will not be processed. Adjust this to an absolute path in rTorrent", torrent.Name);
continue;
}
var item = new DownloadClientItem();

View File

@@ -156,13 +156,13 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
details.Add(new XElement("sorttitle", Parser.Parser.NormalizeTitle(metadataTitle)));
if (movie.MovieMetadata.Value.Ratings.Tmdb?.Votes > 0 || movie.MovieMetadata.Value.Ratings.Imdb?.Votes > 0)
if (movie.MovieMetadata.Value.Ratings?.Tmdb?.Votes > 0 || movie.MovieMetadata.Value.Ratings?.Imdb?.Votes > 0 || movie.MovieMetadata.Value.Ratings?.RottenTomatoes?.Value > 0)
{
var setRating = new XElement("ratings");
var defaultRatingSet = false;
if (movie.MovieMetadata.Value.Ratings.Imdb?.Votes > 0)
if (movie.MovieMetadata.Value.Ratings?.Imdb?.Votes > 0)
{
var setRateImdb = new XElement("rating", new XAttribute("name", "imdb"), new XAttribute("max", "10"), new XAttribute("default", "true"));
setRateImdb.Add(new XElement("value", movie.MovieMetadata.Value.Ratings.Imdb.Value));
@@ -172,18 +172,32 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
setRating.Add(setRateImdb);
}
if (movie.MovieMetadata.Value.Ratings.Tmdb?.Votes > 0)
if (movie.MovieMetadata.Value.Ratings?.Tmdb?.Votes > 0)
{
var setRatethemoviedb = new XElement("rating", new XAttribute("name", "themoviedb"), new XAttribute("max", "10"));
setRatethemoviedb.Add(new XElement("value", movie.MovieMetadata.Value.Ratings.Tmdb.Value));
setRatethemoviedb.Add(new XElement("votes", movie.MovieMetadata.Value.Ratings.Tmdb.Votes));
var setRateTheMovieDb = new XElement("rating", new XAttribute("name", "themoviedb"), new XAttribute("max", "10"));
setRateTheMovieDb.Add(new XElement("value", movie.MovieMetadata.Value.Ratings.Tmdb.Value));
setRateTheMovieDb.Add(new XElement("votes", movie.MovieMetadata.Value.Ratings.Tmdb.Votes));
if (!defaultRatingSet)
{
setRatethemoviedb.SetAttributeValue("default", "true");
defaultRatingSet = true;
setRateTheMovieDb.SetAttributeValue("default", "true");
}
setRating.Add(setRatethemoviedb);
setRating.Add(setRateTheMovieDb);
}
if (movie.MovieMetadata.Value.Ratings?.RottenTomatoes?.Value > 0)
{
var setRateRottenTomatoes = new XElement("rating", new XAttribute("name", "tomatometerallcritics"), new XAttribute("max", "100"));
setRateRottenTomatoes.Add(new XElement("value", movie.MovieMetadata.Value.Ratings.RottenTomatoes.Value));
if (!defaultRatingSet)
{
setRateRottenTomatoes.SetAttributeValue("default", "true");
}
setRating.Add(setRateRottenTomatoes);
}
details.Add(setRating);
@@ -194,6 +208,11 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
details.Add(new XElement("rating", movie.MovieMetadata.Value.Ratings.Tmdb.Value));
}
if (movie.MovieMetadata.Value.Ratings?.RottenTomatoes?.Value > 0)
{
details.Add(new XElement("criticrating", movie.MovieMetadata.Value.Ratings.RottenTomatoes.Value));
}
details.Add(new XElement("userrating"));
details.Add(new XElement("top250"));

View File

@@ -19,7 +19,6 @@ namespace NzbDrone.Core.HealthCheck.Checks
[CheckOn(typeof(ProviderDeletedEvent<IDownloadClient>))]
[CheckOn(typeof(ModelEvent<RootFolder>))]
[CheckOn(typeof(ModelEvent<RemotePathMapping>))]
public class DownloadClientRootFolderCheck : HealthCheckBase, IProvideHealthCheck
{
private readonly IProvideDownloadClient _downloadClientProvider;
@@ -58,7 +57,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
_localizationService.GetLocalizedString("DownloadClientCheckDownloadingToRoot", new Dictionary<string, object>
{
{ "downloadClientName", client.Definition.Name },
{ "path", folder.FullPath }
{ "rootFolderPath", folder.FullPath }
}),
"#downloads-in-root-folder");
}

View File

@@ -5,10 +5,12 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Movies.Collections;
using NzbDrone.Core.Movies.Events;
using NzbDrone.Core.RootFolders;
namespace NzbDrone.Core.HealthCheck.Checks
{
[CheckOn(typeof(CollectionRefreshCompleteEvent))]
[CheckOn(typeof(ModelEvent<RootFolder>))]
public class MovieCollectionRootFolderCheck : HealthCheckBase
{

View File

@@ -3,6 +3,8 @@ using System.Data;
using System.Linq;
using Dapper;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.AutoTagging;
using NzbDrone.Core.AutoTagging.Specifications;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers
@@ -10,17 +12,24 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public class CleanupUnusedTags : IHousekeepingTask
{
private readonly IMainDatabase _database;
private readonly IAutoTaggingRepository _autoTaggingRepository;
public CleanupUnusedTags(IMainDatabase database)
public CleanupUnusedTags(IMainDatabase database, IAutoTaggingRepository autoTaggingRepository)
{
_database = database;
_autoTaggingRepository = autoTaggingRepository;
}
public void Clean()
{
using var mapper = _database.OpenConnection();
var usedTags = new[] { "Movies", "Notifications", "DelayProfiles", "ReleaseProfiles", "ImportLists", "Indexers", "AutoTagging", "DownloadClients" }
var usedTags = new[]
{
"Movies", "Notifications", "DelayProfiles", "ReleaseProfiles", "ImportLists", "Indexers",
"AutoTagging", "DownloadClients"
}
.SelectMany(v => GetUsedTags(v, mapper))
.Concat(GetAutoTaggingTagSpecificationTags(mapper))
.Distinct()
.ToList();
@@ -45,10 +54,31 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
private int[] GetUsedTags(string table, IDbConnection mapper)
{
return mapper.Query<List<int>>($"SELECT DISTINCT \"Tags\" FROM \"{table}\" WHERE NOT \"Tags\" = '[]' AND NOT \"Tags\" IS NULL")
return mapper
.Query<List<int>>(
$"SELECT DISTINCT \"Tags\" FROM \"{table}\" WHERE NOT \"Tags\" = '[]' AND NOT \"Tags\" IS NULL")
.SelectMany(x => x)
.Distinct()
.ToArray();
}
private List<int> GetAutoTaggingTagSpecificationTags(IDbConnection mapper)
{
var tags = new List<int>();
var autoTags = _autoTaggingRepository.All();
foreach (var autoTag in autoTags)
{
foreach (var specification in autoTag.Specifications)
{
if (specification is TagSpecification tagSpec)
{
tags.Add(tagSpec.Value);
}
}
}
return tags;
}
}
}

View File

@@ -36,6 +36,8 @@ namespace NzbDrone.Core.ImportLists.TMDb.Popular
var certification = Settings.FilterCriteria.Certification;
var includeGenreIds = Settings.FilterCriteria.IncludeGenreIds;
var excludeGenreIds = Settings.FilterCriteria.ExcludeGenreIds;
var includeCompanyIds = Settings.FilterCriteria.IncludeCompanyIds;
var excludeCompanyIds = Settings.FilterCriteria.ExcludeCompanyIds;
var languageCode = (TMDbLanguageCodes)Settings.FilterCriteria.LanguageCode;
var todaysDate = DateTime.Now.ToString("yyyy-MM-dd");
@@ -92,6 +94,16 @@ namespace NzbDrone.Core.ImportLists.TMDb.Popular
requestBuilder.AddQueryParam("without_genres", excludeGenreIds);
}
if (includeCompanyIds.IsNotNullOrWhiteSpace())
{
requestBuilder.AddQueryParam("with_companies", includeCompanyIds);
}
if (excludeCompanyIds.IsNotNullOrWhiteSpace())
{
requestBuilder.AddQueryParam("without_companies", excludeCompanyIds);
}
requestBuilder
.AddQueryParam("with_original_language", languageCode)
.Accept(HttpAccept.Json);

View File

@@ -38,6 +38,18 @@ namespace NzbDrone.Core.ImportLists.TMDb
.Matches(@"^\d+([,|]\d+)*$", RegexOptions.IgnoreCase)
.When(c => c.ExcludeGenreIds.IsNotNullOrWhiteSpace())
.WithMessage("Genre Ids must be comma (,) or pipe (|) separated number ids");
// CSV of numbers
RuleFor(c => c.IncludeCompanyIds)
.Matches(@"^\d+([,|]\d+)*$", RegexOptions.IgnoreCase)
.When(c => c.IncludeCompanyIds.IsNotNullOrWhiteSpace())
.WithMessage("Company Ids must be comma (,) or pipe (|) separated number ids");
// CSV of numbers
RuleFor(c => c.ExcludeCompanyIds)
.Matches(@"^\d+([,|]\d+)*$", RegexOptions.IgnoreCase)
.When(c => c.ExcludeCompanyIds.IsNotNullOrWhiteSpace())
.WithMessage("Company Ids must be comma (,) or pipe (|) separated number ids");
}
}
@@ -48,8 +60,10 @@ namespace NzbDrone.Core.ImportLists.TMDb
MinVoteAverage = "5";
MinVotes = "1";
LanguageCode = (int)TMDbLanguageCodes.en;
ExcludeGenreIds = "";
IncludeGenreIds = "";
ExcludeGenreIds = "";
IncludeCompanyIds = "";
ExcludeCompanyIds = "";
}
[FieldDefinition(1, Label = "Minimum Vote Average", HelpText = "Filter movies by votes (0.0-10.0)")]
@@ -67,7 +81,13 @@ namespace NzbDrone.Core.ImportLists.TMDb
[FieldDefinition(5, Label = "Exclude Genre Ids", HelpText = "Filter movies by TMDb Genre Ids (Comma Separated)")]
public string ExcludeGenreIds { get; set; }
[FieldDefinition(6, Label = "Original Language", Type = FieldType.Select, SelectOptions = typeof(TMDbLanguageCodes), HelpText = "Filter by Language")]
[FieldDefinition(6, Label = "Include Company Ids", HelpText = "Filter movies by TMDb Company Ids (Comma Separated)")]
public string IncludeCompanyIds { get; set; }
[FieldDefinition(7, Label = "Exclude Company Ids", HelpText = "Filter movies by TMDb Company Ids (Comma Separated)")]
public string ExcludeCompanyIds { get; set; }
[FieldDefinition(8, Label = "Original Language", Type = FieldType.Select, SelectOptions = typeof(TMDbLanguageCodes), HelpText = "Filter by Language")]
public int LanguageCode { get; set; }
}
}

View File

@@ -112,7 +112,7 @@ namespace NzbDrone.Core.IndexerSearch
var reports = batch.SelectMany(x => x).ToList();
_logger.Debug("Total of {0} reports were found for {1} from {2} indexers", reports.Count, criteriaBase, indexers.Count);
_logger.ProgressDebug("Total of {0} reports were found for {1} from {2} indexers", reports.Count, criteriaBase, indexers.Count);
// Update the last search time for movie if at least 1 indexer was searched.
if (indexers.Any())

View File

@@ -46,27 +46,27 @@ namespace NzbDrone.Core.Indexers.FileList
[FieldDefinition(1, Label = "Passkey", Privacy = PrivacyLevel.ApiKey)]
public string Passkey { get; set; }
[FieldDefinition(2, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "Multi Languages", HelpText = "What languages are normally in a multi release on this indexer?", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(3, Label = "API URL", Advanced = true, HelpText = "Do not change this unless you know what you're doing. Since your API key will be sent to that host.")]
[FieldDefinition(2, Label = "API URL", Advanced = true, HelpText = "Do not change this unless you know what you're doing. Since your API key will be sent to that host.")]
public string BaseUrl { get; set; }
[FieldDefinition(4, Label = "Categories", Type = FieldType.Select, SelectOptions = typeof(FileListCategories), HelpText = "Categories for use in search and feeds. If unspecified, all options are used.")]
[FieldDefinition(3, Label = "Categories", Type = FieldType.Select, SelectOptions = typeof(FileListCategories), HelpText = "Categories for use in search and feeds. If unspecified, all options are used.")]
public IEnumerable<int> Categories { get; set; }
[FieldDefinition(5, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
[FieldDefinition(4, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
public int MinimumSeeders { get; set; }
[FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)]
public IEnumerable<int> RequiredFlags { get; set; }
[FieldDefinition(7)]
[FieldDefinition(5)]
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
[FieldDefinition(8, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
[FieldDefinition(6, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
[FieldDefinition(7, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(8, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)]
public IEnumerable<int> RequiredFlags { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -53,21 +53,21 @@ namespace NzbDrone.Core.Indexers.HDBits
[FieldDefinition(5, Label = "Mediums", Type = FieldType.Select, SelectOptions = typeof(HdBitsMedium), Advanced = true, HelpText = "If unspecified, all options are used.")]
public IEnumerable<int> Mediums { get; set; }
[FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "Multi Languages", HelpText = "What languages are normally in a multi release on this indexer?", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(7, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
[FieldDefinition(6, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
public int MinimumSeeders { get; set; }
[FieldDefinition(8, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)]
public IEnumerable<int> RequiredFlags { get; set; }
[FieldDefinition(9)]
[FieldDefinition(7)]
public SeedCriteriaSettings SeedCriteria { get; set; } = new ();
[FieldDefinition(10, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
[FieldDefinition(8, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
[FieldDefinition(9, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(10, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)]
public IEnumerable<int> RequiredFlags { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -41,21 +41,21 @@ namespace NzbDrone.Core.Indexers.IPTorrents
[FieldDefinition(0, Label = "Feed URL", HelpText = "The full RSS feed url generated by IPTorrents, using only the categories you selected (HD, SD, x264, etc ...)")]
public string BaseUrl { get; set; }
[FieldDefinition(1, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "Multi Languages", HelpText = "What languages are normally in a multi release on this indexer?", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(2, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
[FieldDefinition(1, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
public int MinimumSeeders { get; set; }
[FieldDefinition(3, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)]
public IEnumerable<int> RequiredFlags { get; set; }
[FieldDefinition(4)]
[FieldDefinition(2)]
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
[FieldDefinition(5, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
[FieldDefinition(3, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
[FieldDefinition(4, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(5, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)]
public IEnumerable<int> RequiredFlags { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -19,7 +19,7 @@ namespace NzbDrone.Core.Indexers
public abstract class IndexerBase<TSettings> : IIndexer
where TSettings : IIndexerSettings, new()
{
private static readonly Regex MultiRegex = new (@"\b(?<multi>multi)\b", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex MultiRegex = new (@"[_. ](?<multi>multi)[_. ]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
protected readonly IIndexerStatusService _indexerStatusService;
protected readonly IConfigService _configService;

View File

@@ -65,23 +65,19 @@ namespace NzbDrone.Core.Indexers.Newznab
[FieldDefinition(1, Label = "API Path", HelpText = "Path to the api, usually /api", Advanced = true)]
public string ApiPath { get; set; }
[FieldDefinition(1, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "Multi Languages", HelpText = "What languages are normally in a multi release on this indexer?", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
[FieldDefinition(3, Label = "Categories", Type = FieldType.Select, SelectOptionsProviderAction = "newznabCategories", HelpText = "Drop down list; at least one category must be selected.")]
public IEnumerable<int> Categories { get; set; }
[FieldDefinition(5, Label = "Additional Parameters", HelpText = "Additional Newznab parameters", Advanced = true)]
[FieldDefinition(4, Label = "Additional Parameters", HelpText = "Additional Newznab parameters", Advanced = true)]
public string AdditionalParameters { get; set; }
[FieldDefinition(6,
Label = "Remove year from search string",
HelpText = "Should Radarr remove the year after the title when searching this indexer?",
Advanced = true,
Type = FieldType.Checkbox)]
[FieldDefinition(5, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(6, Label = "Remove year from search string", HelpText = "Should Radarr remove the year after the title when searching this indexer?", Type = FieldType.Checkbox, Advanced = true)]
public bool RemoveYear { get; set; }
// Field 8 is used by TorznabSettings MinimumSeeders

View File

@@ -36,24 +36,24 @@ namespace NzbDrone.Core.Indexers.Nyaa
[FieldDefinition(0, Label = "Website URL")]
public string BaseUrl { get; set; }
[FieldDefinition(1, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "Multi Languages", HelpText = "What languages are normally in a multi release on this indexer?", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(2, Label = "Additional Parameters", Advanced = true, HelpText = "Please note if you change the category you will have to add required/restricted rules about the subgroups to avoid foreign language releases.")]
[FieldDefinition(1, Label = "Additional Parameters", Advanced = true, HelpText = "Please note if you change the category you will have to add required/restricted rules about the subgroups to avoid foreign language releases.")]
public string AdditionalParameters { get; set; }
[FieldDefinition(3, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
[FieldDefinition(2, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
public int MinimumSeeders { get; set; }
[FieldDefinition(4, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)]
public IEnumerable<int> RequiredFlags { get; set; }
[FieldDefinition(5)]
[FieldDefinition(3)]
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
[FieldDefinition(6, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
[FieldDefinition(4, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
[FieldDefinition(5, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)]
public IEnumerable<int> RequiredFlags { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -35,27 +35,27 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
[FieldDefinition(0, Label = "URL", Advanced = true, HelpText = "Do not change this unless you know what you're doing. Since your cookie will be sent to that host.")]
public string BaseUrl { get; set; }
[FieldDefinition(1, Label = "APIUser", HelpText = "These settings are found in your PassThePopcorn security settings (Edit Profile > Security).", Privacy = PrivacyLevel.UserName)]
[FieldDefinition(1, Label = "API User", HelpText = "These settings are found in your PassThePopcorn security settings (Edit Profile > Security).", Privacy = PrivacyLevel.UserName)]
public string APIUser { get; set; }
[FieldDefinition(2, Label = "APIKey", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
[FieldDefinition(2, Label = "API Key", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string APIKey { get; set; }
[FieldDefinition(3, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "Multi Languages", HelpText = "What languages are normally in a multi release on this indexer?", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(4, Type = FieldType.Textbox, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
[FieldDefinition(3, Type = FieldType.Textbox, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
public int MinimumSeeders { get; set; }
[FieldDefinition(5, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)]
public IEnumerable<int> RequiredFlags { get; set; }
[FieldDefinition(6)]
[FieldDefinition(4)]
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
[FieldDefinition(7, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
[FieldDefinition(5, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
[FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(7, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)]
public IEnumerable<int> RequiredFlags { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -5,7 +5,6 @@ namespace NzbDrone.Core.Indexers
public class RssSyncCommand : Command
{
public override bool SendUpdatesToClient => true;
public override bool IsLongRunning => true;
}
}

View File

@@ -39,21 +39,21 @@ namespace NzbDrone.Core.Indexers.TorrentPotato
[FieldDefinition(2, Label = "Passkey", HelpText = "The password you use at your Indexer.", Privacy = PrivacyLevel.Password)]
public string Passkey { get; set; }
[FieldDefinition(3, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "Multi Languages", HelpText = "What languages are normally in a multi release on this indexer?", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(4, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
[FieldDefinition(3, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
public int MinimumSeeders { get; set; }
[FieldDefinition(5, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", Advanced = true)]
public IEnumerable<int> RequiredFlags { get; set; }
[FieldDefinition(6)]
[FieldDefinition(4)]
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
[FieldDefinition(7, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
[FieldDefinition(5, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
[FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(7, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", Advanced = true)]
public IEnumerable<int> RequiredFlags { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -40,21 +40,21 @@ namespace NzbDrone.Core.Indexers.TorrentRss
[FieldDefinition(2, Type = FieldType.Checkbox, Label = "Allow Zero Size", HelpText = "Enabling this will allow you to use feeds that don't specify release size, but be careful, size related checks will not be performed.")]
public bool AllowZeroSize { get; set; }
[FieldDefinition(3, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "Multi Languages", HelpText = "What languages are normally in a multi release on this indexer?", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(4, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
[FieldDefinition(3, Type = FieldType.Number, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
public int MinimumSeeders { get; set; }
[FieldDefinition(5, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)]
public IEnumerable<int> RequiredFlags { get; set; }
[FieldDefinition(6)]
[FieldDefinition(4)]
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
[FieldDefinition(7, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
[FieldDefinition(5, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
[FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(7, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)]
public IEnumerable<int> RequiredFlags { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -58,12 +58,12 @@ namespace NzbDrone.Core.Indexers.Torznab
[FieldDefinition(9)]
public SeedCriteriaSettings SeedCriteria { get; set; } = new ();
[FieldDefinition(10, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)]
public IEnumerable<int> RequiredFlags { get; set; }
[FieldDefinition(11, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
[FieldDefinition(10, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
[FieldDefinition(11, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)]
public IEnumerable<int> RequiredFlags { get; set; }
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -353,7 +353,6 @@
"IncludeUnmonitored": "تضمين غير مراقب",
"IncludeRecommendationsHelpText": "قم بتضمين أفلام {appName} الموصى بها في عرض الاكتشاف",
"IncludeRadarrRecommendations": "تضمين توصيات الرادار",
"IncludeHealthWarningsHelpText": "قم بتضمين التحذيرات الصحية",
"IncludeCustomFormatWhenRenamingHelpText": "تضمين في تنسيق إعادة تسمية {تنسيقات مخصصة}",
"IncludeCustomFormatWhenRenaming": "قم بتضمين تنسيق مخصص عند إعادة التسمية",
"InCinemasMsg": "الفيلم في دور السينما",

View File

@@ -813,7 +813,6 @@
"ICalFeedHelpText": "Копирайте този URL адрес на вашите клиенти или кликнете, за да се абонирате, ако браузърът ви поддържа webcal",
"Imported": "Внос",
"IllRestartLater": "Ще рестартирам по-късно",
"IncludeHealthWarningsHelpText": "Включете здравни предупреждения",
"Medium": "Среден",
"MinAvailability": "Минимална наличност",
"MinimumAge": "Минимална възраст",

View File

@@ -25,7 +25,7 @@
"LastWriteTime": "La darrera hora d'escriptura",
"LocalPath": "Camí local",
"Logs": "Registres",
"MinutesNinety": "90 minuts: {0}",
"MinutesNinety": "90 minuts: {ninety}",
"NoListRecommendations": "No s'han trobat elements de llista ni recomanacions; per a començar, podeu afegir una pel·lícula nova, importar-ne algunes existents o afegir una llista.",
"NotAvailable": "No disponible",
"PreferAndUpgrade": "Marca preferit i actualitza",
@@ -287,7 +287,7 @@
"MovieChat": "Xat de pel·lícula",
"MovieDetailsNextMovie": "Detalls de la pel·lícula: propera pel·lícula",
"MovieInvalidFormat": "Pel·lícula: Format no vàlid",
"NegateHelpText": "Si està marcat, el format personalitzat no s'aplicarà si la condició {0} coincideix.",
"NegateHelpText": "Si està marcat, el format personalitzat no s'aplicarà si la condició {implementationName} coincideix.",
"NetCore": ".NET",
"NoLeaveIt": "No, deixa-ho",
"TotalMovies": "Total de pel·lícules",
@@ -410,8 +410,8 @@
"Custom": "Personalitzat",
"CustomFilters": "Filtres personalitzats",
"CustomFormats": "Formats personalitzats",
"CustomFormatUnknownCondition": "Condició de format personalitzada desconeguda '{0}'",
"CustomFormatUnknownConditionOption": "Opció desconeguda '{0}' per a la condició '{1}'",
"CustomFormatUnknownCondition": "Condició de format personalitzada desconeguda '{implementation}'",
"CustomFormatUnknownConditionOption": "Opció desconeguda '{key}' per a la condició '{implementation}'",
"UpgradeUntilCustomFormatScoreMovieHelpText": "Un cop s'arribi a aquesta puntuació de format personalitzat, {appName} ja no baixarà pel·lícules",
"UpgradeUntilMovieHelpText": "Un cop s'assoleixi aquesta qualitat, {appName} ja no baixarà pel·lícules",
"Database": "Base de dades",
@@ -448,7 +448,7 @@
"DoNotPrefer": "No ho prefereixo",
"DoNotUpgradeAutomatically": "No actualitzeu automàticament",
"DownloadClientCheckDownloadingToRoot": "El client de baixada {downloadClientName} col·loca les baixades a la carpeta arrel {path}. No s'hauria de baixar a una carpeta arrel.",
"DownloadClientCheckUnableToCommunicateMessage": "No es pot comunicar amb {downloadClientName}.",
"DownloadClientCheckUnableToCommunicateMessage": "No es pot comunicar amb {downloadClientName}. {errorMessage}",
"DownloadClientsSettingsSummary": "Descàrrega de clients, gestió de descàrregues i mapes de camins remots",
"DownloadClientUnavailable": "El client de descàrrega no està disponible",
"Downloaded": "S'ha baixat",
@@ -531,7 +531,6 @@
"InCinemasMsg": "Estrena de pel·lícula",
"IncludeCustomFormatWhenRenaming": "Inclou el format personalitzat en canviar el nom",
"IncludeCustomFormatWhenRenamingHelpText": "Inclou en {Custom Formats} el format de canvi de nom",
"IncludeHealthWarningsHelpText": "Inclou advertències de salut",
"IncludeRecommendationsHelpText": "Inclou les pel·lícules recomanades per {appName} a la vista de descobriment",
"IndexerJackettAll": "Indexadors que utilitzen el punt final \"tot\" no compatible amb Jackett: {indexerNames}",
"IndexerLongTermStatusCheckAllClientMessage": "Tots els indexadors no estan disponibles a causa d'errors durant més de 6 hores",
@@ -559,7 +558,7 @@
"Links": "Enllaços",
"ImportListExclusions": "Llista d'exclusions",
"ImportLists": "Llistes",
"ImportListsSettingsSummary": "Importa llistes, exclusions de llista",
"ImportListsSettingsSummary": "Importa des d'una altra instància {appName} o llistes de Trakt i gestiona les exclusions de llistes",
"ListSyncLevelHelpText": "Les pel·lícules de la biblioteca es gestionaran en funció de la vostra selecció si cauen o no apareixen a les vostres llistes",
"ListSyncLevelHelpTextWarning": "Els fitxers de pel·lícules se suprimiran permanentment; això pot provocar que esborraràs la teva biblioteca si les teves llistes estan buides",
"ListTagsHelpText": "Els elements de la llista d'etiquetes s'afegiran amb",
@@ -598,8 +597,8 @@
"MinimumFreeSpace": "Espai lliure mínim",
"MinimumFreeSpaceHelpText": "Eviteu la importació si quedara menys d'aquesta quantitat d'espai disponible en disc",
"Minutes": "Minuts",
"MinutesHundredTwenty": "120 minuts: {0}",
"MinutesSixty": "60 minuts: {0}",
"MinutesHundredTwenty": "120 minuts: {hundredTwenty}",
"MinutesSixty": "60 minuts: {sixty}",
"Missing": "Absents",
"MonitorCollection": "Monitora col·leccions",
"MonitoredCollectionHelpText": "Monitora per a afegir automàticament pel·lícules d'aquesta col·lecció a la biblioteca",
@@ -716,7 +715,7 @@
"RefreshLists": "Actualitza llistes",
"RefreshMonitoredIntervalHelpText": "Amb quina freqüència s'actualitzen les baixades monitorades dels clients de descàrrega, mínim 1 minut",
"RefreshMovie": "Actualitza pel·lícula",
"RegularExpressionsCanBeTested": "Es poden provar expressions regulars ",
"RegularExpressionsCanBeTested": "Les expressions regulars es poden provar [aquí](http://regexstorm.net/tester).",
"RejectionCount": "Recompte de rebuigs",
"RelativePath": "Camí relatiu",
"ReleaseBranchCheckOfficialBranchMessage": "La branca {0} no és una branca de llançament de {appName} vàlida, no rebreu actualitzacions",
@@ -877,7 +876,7 @@
"Tomorrow": "Demà",
"TorrentDelay": "Retard del torrent",
"TorrentDelayHelpText": "Retard en minuts per a esperar abans de capturar un torrent",
"TorrentDelayTime": "Retard del torrent: {0}",
"TorrentDelayTime": "Retard del torrent: {torrentDelay}",
"Torrents": "Torrents",
"TorrentsDisabled": "Torrents desactivats",
"TotalFileSize": "Mida total del fitxer",
@@ -917,7 +916,7 @@
"UpdateCheckStartupNotWritableMessage": "L'actualització no es pot instal·lar perquè la carpeta d'inici '{startupFolder}' no té permisos d'escriptura per a l'usuari '{userName}'.",
"UpdateSelected": "Actualització seleccionada",
"UpdateCheckStartupTranslocationMessage": "No es pot instal·lar l'actualització perquè la carpeta d'inici \"{startupFolder}\" es troba en una carpeta de translocació d'aplicacions.",
"UpdateCheckUINotWritableMessage": "L'actualització no es pot instal·lar perquè la carpeta UI '{startupFolder}' no té permisos d'escriptura per a l'usuari '{userName}'.",
"UpdateCheckUINotWritableMessage": "L'actualització no es pot instal·lar perquè la carpeta UI '{uiFolder}' no té permisos d'escriptura per a l'usuari '{userName}'.",
"UpdateMechanismHelpText": "Utilitzeu l'actualitzador integrat de {appName} o un script",
"UpdateScriptPathHelpText": "Camí a un script personalitzat que pren un paquet d'actualització i gestiona la resta del procés d'actualització",
"UpgradesAllowedHelpText": "Si les qualitats estan desactivades no s'actualitzaran",
@@ -929,7 +928,7 @@
"UseHardlinksInsteadOfCopy": "Utilitzeu enllaços durs en lloc de copiar",
"Usenet": "Usenet",
"UsenetDelayHelpText": "Retard en minuts per esperar abans de capturar una versió d'Usenet",
"UsenetDelayTime": "Retard d'Usenet: {0}",
"UsenetDelayTime": "Retard d'Usenet: {usenetDelay}",
"UsenetDisabled": "Usenet desactivat",
"UseProxy": "Utilitzeu el servidor intermediari",
"Username": "Nom d'usuari",
@@ -994,7 +993,7 @@
"NoIssuesWithYourConfiguration": "No hi ha cap problema amb la configuració",
"HiddenClickToShow": "Amagat, feu clic per a mostrar",
"Max": "Màx",
"RequiredHelpText": "La condició {0} ha de coincidir perquè s'apliqui el format personalitzat. En cas contrari, n'hi ha prou amb una única coincidència de {1}.",
"RequiredHelpText": "La condició {implementationName} ha de coincidir perquè s'apliqui el format personalitzat. En cas contrari, n'hi ha prou amb una única coincidència de {implementationName}.",
"ShowTitle": "Mostra el títol",
"Ignored": "Ignorat",
"IgnoredAddresses": "Adreces ignorades",
@@ -1094,8 +1093,8 @@
"ApplyTagsHelpTextHowToApplyDownloadClients": "Com aplicar etiquetes als clients de baixada seleccionats",
"ApplyTagsHelpTextHowToApplyImportLists": "Com aplicar etiquetes a les llistes d'importació seleccionades",
"MoveAutomatically": "Mou automàticament",
"AutoTaggingNegateHelpText": "Si està marcat, el format personalitzat no s'aplicarà si la condició {0} coincideix.",
"AutoTaggingRequiredHelpText": "La condició {0} ha de coincidir perquè s'apliqui el format personalitzat. En cas contrari, n'hi ha prou amb una única coincidència de {0}.",
"AutoTaggingNegateHelpText": "Si està marcat, el format personalitzat no s'aplicarà si la condició {implementationName} coincideix.",
"AutoTaggingRequiredHelpText": "La condició {implementationName} ha de coincidir perquè s'apliqui el format personalitzat. En cas contrari, n'hi ha prou amb una única coincidència de {implementationName}.",
"DeleteAutoTagHelpText": "Esteu segur que voleu suprimir l'etiqueta '{name}'?",
"DeleteRootFolderMessageText": "Esteu segur que voleu suprimir la carpeta arrel '{path}'?",
"AddConnection": "Afegeix una connexió",
@@ -1113,7 +1112,7 @@
"GrabId": "Captura ID",
"OrganizeNothingToRename": "Èxit! La feina està acabada, no hi ha fitxers per a canviar el nom.",
"OrganizeLoadError": "S'ha produït un error en carregar les previsualitzacions",
"BlocklistReleaseHelpText": "Impedeix que {appName} torni a capturar aquesta versió automàticament",
"BlocklistReleaseHelpText": "Impedeix que {appName} baixi aquesta versió mitjançant RSS o cerca automàtica",
"ConnectionLostReconnect": "{appName} intentarà connectar-se automàticament, o podeu fer clic a recarregar.",
"ConnectionLostToBackend": "{appName} ha perdut la connexió amb el backend i s'haurà de tornar a carregar per a restaurar la funcionalitat.",
"DelayingDownloadUntil": "S'està retardant la baixada fins al {date} a les {time}",
@@ -1127,7 +1126,7 @@
"TablePageSize": "Mida de la pàgina",
"TablePageSizeHelpText": "Nombre d'elements per mostrar a cada pàgina",
"MovieFileDeletedTooltip": "S'ha suprimit el fitxer de pel·lícula",
"RetryingDownloadOn": "S'està retardant la baixada fins a les {0} a les {1}",
"RetryingDownloadOn": "S'està retardant la baixada fins al {date} a les {time}",
"FormatAgeHours": "hores",
"FormatAgeMinute": "minut",
"FormatAgeMinutes": "minuts",
@@ -1202,7 +1201,7 @@
"FailedToUpdateSettings": "No s'ha pogut actualitzar la configuració",
"InteractiveImportNoImportMode": "S'ha de seleccionar un mode d'importació",
"InteractiveImportNoMovie": "S'ha de triar una pel·lícula triar per a cada fitxer seleccionat",
"InteractiveSearchResultsFailedErrorMessage": "La cerca ha fallat per {missatge}. Actualitza la informació de la pel·lícula i verifica que hi hagi la informació necessària abans de tornar a cercar.",
"InteractiveSearchResultsFailedErrorMessage": "La cerca ha fallat per {message}. Actualitza la informació de la pel·lícula i verifica que hi hagi la informació necessària abans de tornar a cercar.",
"LogFilesLocation": "Els fitxers de registre es troben a: {location}",
"ManageDownloadClients": "Gestiona els clients de descàrrega",
"MovieGrabbedHistoryTooltip": "Pel·lícula captura de {indexer} i enviada a {downloadClient}",
@@ -1211,7 +1210,7 @@
"FullColorEventsHelpText": "Estil alterat per a pintar tot l'esdeveniment amb el color d'estat, en lloc de només la vora esquerra. No s'aplica a l'Agenda",
"InteractiveImportNoFilesFound": "No s'ha trobat cap fitxer de vídeo a la carpeta seleccionada",
"InteractiveImportNoLanguage": "S'ha de triar l'idioma per a cada fitxer seleccionat",
"ListWillRefreshEveryInterval": "La llista s'actualitzarà cada {0}",
"ListWillRefreshEveryInterval": "La llista s'actualitzarà cada {refreshInterval}",
"MovieMatchType": "Tipus de concordança de pel·lícula",
"NoDownloadClientsFound": "No s'han trobat clients de baixada",
"NoIndexersFound": "No s'han trobat indexadors",
@@ -1289,5 +1288,54 @@
"Rejections": "Rebutjats",
"NotificationsPushoverSettingsExpire": "Venciment",
"NotificationsEmailSettingsServer": "Servidor",
"NotificationsSettingsWebhookMethod": "Mètode"
"NotificationsSettingsWebhookMethod": "Mètode",
"BlocklistAndSearch": "Llista de bloqueig i cerca",
"BlocklistAndSearchHint": "Comença una cerca per reemplaçar després d'haver bloquejat",
"AddDelayProfileError": "No s'ha pogut afegir un perfil realentit, torna-ho a probar",
"DoNotBlocklistHint": "Elimina sense afegir a la llista de bloqueig",
"DoNotBlocklist": "No afegiu a la llista de bloqueig",
"BlackholeWatchFolder": "Monitora la carpeta",
"BlackholeFolderHelpText": "Carpeta on {appName} emmagatzemarà els fitxers {extension}",
"BlocklistAndSearchMultipleHint": "Comença una cerca per reemplaçar després d'haver bloquejat",
"BlackholeWatchFolderHelpText": "Carpeta des de la qual {appName} hauria d'importar les baixades finalitzades",
"BlocklistMultipleOnlyHint": "Afegeix a la llista de bloqueig sense cercar substituts",
"BlocklistOnly": "Sols afegir a la llista de bloqueig",
"Category": "Categoria",
"ChangeCategoryHint": "Canvia la baixada a la \"Categoria post-importació\" des del client de descàrrega",
"Directory": "Directori",
"Destination": "Destinació",
"DownloadClientDelugeTorrentStateError": "Deluge està informant d'un error",
"ClickToChangeIndexerFlags": "Feu clic per canviar els indicadors de l'indexador",
"ConditionUsingRegularExpressions": "Aquesta condició coincideix amb expressions regulars. Tingueu en compte que els caràcters `\\^$.|?*+()[{` tenen significats especials i cal escapar amb un `\\`",
"Umask": "UMask",
"DownloadClientAriaSettingsDirectoryHelpText": "Ubicació opcional per a les baixades, deixeu-lo en blanc per utilitzar la ubicació predeterminada d'Aria2",
"AddAutoTagError": "No es pot afegir una etiqueta automàtica nova, torneu-ho a provar.",
"AddReleaseProfile": "Afegeix un perfil de llançament",
"ConnectionSettingsUrlBaseHelpText": "Afegeix un prefix a l'URL {connectionName}, com ara {url}",
"CustomFormatsSpecificationRegularExpression": "Expressió regular",
"CustomFormatsSpecificationRegularExpressionHelpText": "El format personalitzat RegEx no distingeix entre majúscules i minúscules",
"AddListExclusion": "Afegeix una llista d'exclusió",
"ChangeCategory": "Canvia categoria",
"AutoTaggingLoadError": "No es pot carregar l'etiquetatge automàtic",
"BlocklistOnlyHint": "Afegir a la llista de bloqueig sense cercar substituts",
"ChangeCategoryMultipleHint": "Canvia les baixades a la \"Categoria post-importació\" des del client de descàrrega",
"ChownGroup": "Canvia el grup propietari",
"Clone": "Clona",
"DelayMinutes": "{delay} Minuts",
"DelayProfileMovieTagsHelpText": "S'aplica a pel·lícules amb almenys una etiqueta coincident",
"DelayProfileProtocol": "Protocol: {preferredProtocol}",
"DeleteReleaseProfileMessageText": "Esteu segur que voleu suprimir el perfil de llançament '{name}'?",
"DeleteSpecification": "Esborra especificació",
"CutoffNotMet": "Tall no assolit",
"CustomFilter": "Filtres personalitzats",
"CustomFormatsSpecificationFlag": "Bandera",
"Dash": "Guió",
"DeleteSpecificationHelpText": "Esteu segur que voleu suprimir l'especificació '{name}'?",
"DownloadClientDelugeSettingsDirectory": "Directori de baixada",
"DownloadClientDelugeSettingsDirectoryCompleted": "Directori al qual es mou quan s'hagi completat",
"DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Ubicació opcional de les baixades completades, deixeu-lo en blanc per utilitzar la ubicació predeterminada de Deluge",
"DownloadClientDelugeSettingsDirectoryHelpText": "Ubicació opcional de les baixades completades, deixeu-lo en blanc per utilitzar la ubicació predeterminada de Deluge",
"DeleteReleaseProfile": "Suprimeix el perfil de llançament",
"Donate": "Dona",
"DownloadClientDelugeSettingsUrlBaseHelpText": "Afegeix un prefix a l'url json del Deluge, vegeu {url}"
}

View File

@@ -175,7 +175,7 @@
"ChmodFolder": "Složka chmod",
"CertificationCountryHelpText": "Vyberte zemi pro certifikaci filmu",
"Cancel": "Zrušit",
"ChownGroupHelpTextWarning": "Funguje to pouze v případě, že je uživatel souboru {appName} vlastníkem souboru. Je lepší zajistit, aby stahovací klient používal stejnou skupinu jako {appName}.",
"ChownGroupHelpTextWarning": "Toto funguje pouze v případě, že uživatel, který spustil {appName}, je vlastníkem souboru. Je lepší zajistit, aby klient stahování používal stejnou skupinu jako {appName}.",
"ClientPriority": "Priorita klienta",
"Component": "Komponenta",
"ClickToChangeQuality": "Kliknutím změníte kvalitu",
@@ -212,13 +212,12 @@
"AddNewMessage": "Přidání nového filmu je snadné, stačí začít psát název filmu, který chcete přidat",
"AddNewMovie": "Přidat nový film",
"AddNewTmdbIdMessage": "Můžete také vyhledávat pomocí ID TMDb filmu. např. 'tmdb: 71663'",
"IncludeHealthWarningsHelpText": "Zahrnout zdravotní varování",
"AddListExclusionMovieHelpText": "Zabraňte přidávání filmů do {appName}u pomocí seznamů",
"ImportHeader": "Chcete-li přidat filmy do {appName}u, importujte existující organizovanou knihovnu",
"ImportListStatusCheckSingleClientMessage": "Seznamy nejsou k dispozici z důvodu selhání: {importListNames}",
"ChmodFolderHelpText": "Octal, aplikováno během importu / přejmenování na mediální složky a soubory (bez provádění bitů)",
"AgeWhenGrabbed": "Stáří (kdy bylo získáno)",
"ChmodFolderHelpTextWarning": "Funguje to pouze v případě, že je uživatel souboru {appName} vlastníkem souboru. Je lepší zajistit, aby stahovací klient správně nastavil oprávnění.",
"ChmodFolderHelpTextWarning": "Toto funguje pouze v případě, že uživatel, který spustil {appName}, je vlastníkem souboru. Je lepší zajistit, aby klient pro stahování správně nastavil oprávnění.",
"Updates": "Aktualizace",
"Uptime": "Provozuschopnost",
"ChownGroupHelpText": "Název skupiny nebo gid. Použijte gid pro vzdálené systémy souborů.",
@@ -1098,7 +1097,7 @@
"DeletedReasonMissingFromDisk": "{appName}u se nepodařilo najít soubor na disku, proto byl soubor odpojen od filmu v databázi",
"DeletedReasonUpgrade": "Soubor byl odstraněn pro import lepší verze",
"DeleteSelectedMovieFilesHelpText": "Opravdu chcete smazat vybrané soubory filmů?",
"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 {1}.",
"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}.",
"DownloadClientsLoadError": "Nelze načíst klienty pro stahování",
"EditSelectedMovies": "Upravit vybrané filmy",
"EditSelectedDownloadClients": "Upravit vybrané klienty pro stahování",

View File

@@ -94,11 +94,11 @@
"Agenda": "Dagsorden",
"Age": "Alder",
"AddNewTmdbIdMessage": "Du kan også søge ved at bruge TMDB Id'et af en film. f.eks 'tmdb:71663'",
"AddNewMovie": "Tilføj Ny Film",
"AddNewMovie": "Tilføj ny film",
"AddNewMessage": "Det er nemt at tilføje en ny film, bare start ved at skrive navnet på filmen du vil tilføje",
"AddNew": "Tilføj Ny",
"AddMovies": "Tilføj Film",
"AddExclusion": "Tilføj Undtagelse",
"AddExclusion": "Tilføj undtagelse",
"Added": "Tilføjet",
"Activity": "Aktivitet",
"Actions": "Handlinger",
@@ -106,7 +106,7 @@
"AnalyseVideoFiles": "Analyser videofiler",
"System": "System",
"AutoRedownloadFailedHelpText": "Søg automatisk efter og forsøg at downloade en anden udgivelse",
"TestAllClients": "Test alle klienter",
"TestAllClients": "Afprøv alle klienter",
"UnableToLoadAltTitle": "Kan ikke indlæse alternative titler.",
"WhatsNew": "Hvad er nyt?",
"ProtocolHelpText": "Vælg hvilke protokol (r) du vil bruge, og hvilken der foretrækkes, når du vælger mellem ellers lige udgivelser",
@@ -198,7 +198,7 @@
"ChangeHasNotBeenSavedYet": "Ændring er endnu ikke gemt",
"CheckDownloadClientForDetails": "tjek download klient for flere detaljer",
"CheckForFinishedDownloadsInterval": "Kontroller for færdige downloadsinterval",
"AddIndexer": "Tilføj indeksør",
"AddIndexer": "Tilføj indekser",
"ChmodFolder": "chmod mappe",
"ChmodFolderHelpText": "Oktal, anvendt under import / omdøbning til mediemapper og filer (uden udførelse af bits)",
"ChmodFolderHelpTextWarning": "Dette fungerer kun, hvis den bruger, der kører {appName}, er ejeren af filen. Det er bedre at sikre, at downloadklienten indstiller tilladelserne korrekt.",
@@ -281,7 +281,6 @@
"CancelPendingTask": "Er du sikker på, at du vil annullere denne afventende opgave?",
"DeleteDownloadClient": "Slet Download Client",
"ImportIncludeQuality": "Sørg for, at dine filer inkluderer kvaliteten i deres filnavne. f.eks. {0}",
"IncludeHealthWarningsHelpText": "Inkluder sundhedsadvarsler",
"Max": "Maks.",
"Medium": "Medium",
"MovieFilesTotaling": "Totale filmfiler",
@@ -327,8 +326,8 @@
"ShowGenres": "Vis genrer",
"Size": "Størrelse",
"SuggestTranslationChange": "Foreslå ændring af oversættelsen",
"TestAllIndexers": "Test alle indeksører",
"TestAllLists": "Test alle lister",
"TestAllIndexers": "Afprøv alle indeks",
"TestAllLists": "Afprøv alle lister",
"Queued": "I kø",
"TMDb": "TMDb",
"Tomorrow": "I morgen",
@@ -392,7 +391,7 @@
"PortNumber": "Portnummer",
"UpgradesAllowedHelpText": "Hvis deaktiveret, vil kvalitet ikke vil blive opgraderet",
"PackageVersion": "Pakkeversion",
"AddMovie": "Tilføj Film",
"AddMovie": "Tilføj film",
"AddRestriction": "Tilføj begrænsning",
"Password": "Adgangskode",
"Path": "Sti",
@@ -831,7 +830,7 @@
"Style": "Stil",
"SubfolderWillBeCreatedAutomaticallyInterp": "Undermappen '{0}' oprettes automatisk",
"Sunday": "Søndag",
"Table": "Bord",
"Table": "Tabel",
"TableOptions": "Tabelindstillinger",
"TableOptionsColumnsMessage": "Vælg hvilke kolonner der er synlige og hvilken rækkefølge de vises i",
"TagDetails": "Tagdetaljer - {0}",
@@ -839,8 +838,8 @@
"Tags": "Mærker",
"ICalTagsMoviesHelpText": "Gælder film med mindst et matchende tag",
"Tasks": "Opgaver",
"Test": "Prøve",
"TestAll": "Test alle",
"Test": "Afprøv",
"TestAll": "Afprøv alle",
"TheLogLevelDefault": "Logniveauet er som standard 'Info' og kan ændres i",
"ThisCannotBeCancelled": "Dette kan ikke annulleres en gang startet uden genstart af {appName}.",
"Title": "Titel",
@@ -901,13 +900,13 @@
"UnmappedFolders": "Ikke-kortlagte mapper",
"Unmonitored": "Uovervåget",
"ICalIncludeUnmonitoredMoviesHelpText": "Inkluder ikke-overvågede film i iCal-feedet",
"Unreleased": "Ikke tilgængelig",
"Unreleased": "Ikke udgivet",
"UnsavedChanges": "Ugemte ændringer",
"UnselectAll": "Fravælg alle",
"UpdateAll": "Opdater alle",
"UpdateAutomaticallyHelpText": "Download og installer opdateringer automatisk. Du kan stadig installere fra System: Updates",
"UpdateCheckStartupTranslocationMessage": "Kan ikke installere opdatering, fordi startmappen '{startupFolder}' er i en App Translocation-mappe.",
"UpdateCheckUINotWritableMessage": "Kan ikke installere opdatering, fordi brugergrænsefladen \"{startupFolder}\" ikke kan skrives af brugeren \"{userName}\".",
"UpdateCheckUINotWritableMessage": "Kan ikke installere opdatering, fordi '{userName}' ikke kan skrive til mappen for brugergrænseflade '{uiFolder}'.",
"UpdateMechanismHelpText": "Brug {appName}s indbyggede opdatering eller et script",
"UpdateScriptPathHelpText": "Sti til et brugerdefineret script, der tager en udpakket opdateringspakke og håndterer resten af opdateringsprocessen",
"UpdateSelected": "Opdatering valgt",
@@ -993,5 +992,18 @@
"ApplyTagsHelpTextAdd": "Tilføj: Føj tags til den eksisterende liste over tags",
"ApplyTagsHelpTextHowToApplyIndexers": "Sådan anvendes tags på de valgte film",
"DeleteSelectedDownloadClients": "Slet Download Client",
"DeleteSelectedIndexers": "Slet Indexer"
"DeleteSelectedIndexers": "Slet Indexer",
"AnnouncedMsg": "Film er annonceret",
"AddConditionImplementation": "Tilføj betingelse - {implementationName}",
"AddConnection": "Tilføj forbindelse",
"AddConnectionImplementation": "Tilføj forbindelse - {implementationName}",
"AddImportList": "Tilføj importliste",
"AddDownloadClientImplementation": "Tilføj downloadklient - {implementationName}",
"AddImportListImplementation": "Tilføj importliste - {implementationName}",
"ApplyChanges": "Anvend ændringer",
"AddCondition": "Tilføj betingelse",
"AllTitles": "All titler",
"TablePageSize": "Sidestørrelse",
"AddRootFolderError": "Kunne ikke tilføje rodmappe",
"Unknown": "Ukendt"
}

View File

@@ -19,7 +19,7 @@
"CompletedDownloadHandling": "Download-Handhabung abgeschlossen",
"Connect": "Verbinden",
"Connections": "Verbindungen",
"Crew": "Besatzung",
"Crew": "Besetzung",
"CustomFilters": "Benutzerdefinierte Filter",
"CustomFormats": "Eigene Formate",
"Date": "Datum",
@@ -149,7 +149,7 @@
"Language": "Sprache",
"MediaManagementSettingsSummary": "Einstellungen für Bennenung und Dateiverwaltung",
"Year": "Jahr",
"Wanted": " Gesucht",
"Wanted": "Gesucht",
"UpdateSelected": "Auswahl aktualisieren",
"UiSettingsSummary": "Einstellungen für Kalender, Datumsformat und Farbbeeinträchtigung",
"Timeleft": "Restzeit",
@@ -386,7 +386,6 @@
"ImportMovies": "Filme importieren",
"IncludeCustomFormatWhenRenaming": "Eigenes Format beim umbennen einfügen",
"IncludeCustomFormatWhenRenamingHelpText": "In {Custom Formats} umbennenungs Format",
"IncludeHealthWarningsHelpText": "Zustandswarnung",
"IncludeUnmonitored": "Nicht beobachtete einbeziehen",
"IndexerFlags": "Indexer-Flags",
"Interval": "Intervall",
@@ -549,7 +548,7 @@
"PreferIndexerFlags": "Bevorzugte Indexer Flags",
"CopyUsingHardlinksMovieHelpText": "Hardlinks erlauben es {appName}, Torrents zu importieren die derzeit geseeded werden, ohne dabei weiteren Speicherplatz zu belegen oder die Datei vollständig zu kopieren. Hardlinks funktionieren nur, wenn sich die Quelle und das Ziel auf dem selben Volume befinden",
"SearchForMovie": "Film suchen",
"AuthenticationMethodHelpText": "Für den Zugriff auf {appName} sind Benutzername und Passwort erforderlich.",
"AuthenticationMethodHelpText": "Für den Zugriff auf {appName} sind Benutzername und Passwort erforderlich",
"CopyUsingHardlinksHelpTextWarning": "Gelegentlich können Dateisperren das Umbenennen von Dateien verhindern, die geseedet werden. Sie können das Seeding vorübergehend deaktivieren und als Workaround die Umbenennungsfunktion von {appName} verwenden.",
"IndexerSettings": "Indexer Einstellungen",
"ProxyPasswordHelpText": "Sie müssen nur einen Benutzernamen und ein Passwort eingeben, wenn dies erforderlich ist. Andernfalls lassen Sie sie leer.",
@@ -624,16 +623,16 @@
"DownloadClientUnavailable": "Downloader ist nicht verfügbar",
"DeleteTagMessageText": "Sind Sie sicher, dass Sie das Tag „{label}“ löschen möchten?",
"DeleteRestrictionHelpText": "Beschränkung '{0}' wirklich löschen?",
"DeleteNotificationMessageText": "Sind Sie sicher, dass Sie die Benachrichtigung {name}“ löschen möchten?",
"DeleteIndexerMessageText": "Sind Sie sicher, dass Sie den Indexer {name}“ löschen möchten?",
"DeleteDownloadClientMessageText": "Sind Sie sicher, dass Sie den Download-Client {name} löschen möchten?",
"DeleteBackupMessageText": "Sind Sie sicher, dass Sie die Sicherung „{name}“ löschen möchten?",
"DeleteNotificationMessageText": "Bist du sicher, dass du die Benachrichtigung '{name}' wirklich löschen willst?",
"DeleteIndexerMessageText": "Bist du sicher, dass du den Indexer '{name}' wirklich löschen willst?",
"DeleteDownloadClientMessageText": "Bist du sicher, dass du den Download Client '{name}' wirklich löschen willst?",
"DeleteBackupMessageText": "Soll das Backup '{name}' wirklich gelöscht werden?",
"Cutoff": "Schwelle",
"ClickToChangeMovie": "Klicken um den Film zu bearbeiten",
"CheckDownloadClientForDetails": "Weitere Informationen finden Sie im Download-Client",
"CancelPendingTask": "Möchten Sie diese ausstehende Aufgabe wirklich abbrechen?",
"BranchUpdateMechanism": "Git-Branch für den externen Updateablauf",
"BranchUpdate": "Verwendeter Git-Branch für Aktualisierungen",
"BranchUpdate": "Branch, der verwendet werden soll, um {appName} zu updaten",
"BeforeUpdate": "Vor dem Update",
"AddingTag": "Tag hinzufügen",
"YouCanAlsoSearch": "Es kann auch nach der TMDb oder IMDb ID eines Filmes gesucht werden. Z.B.: 'tmdb:71663'",
@@ -707,7 +706,6 @@
"ImportCustomFormat": "Eigenes Format importieren",
"ExportCustomFormat": "Eigenes Format exportieren",
"CustomFormatUnknownConditionOption": "Unbekannte Option '{0}' für die Bedingung '{1}'",
"CustomFormatUnknownCondition": "Unbekannte eigene Format Bedingung '{0}'",
"DownloadPropersAndRepacksHelpTextCustomFormat": "Benutze 'Nicht bevorzugen' um den eigene Formate Score über Proper oder Repacks zu sortieren",
"DownloadPropersAndRepacksHelpTextWarning": "Benutze eigene Formate um automatisch auf Proper oder Repack releases zu upgraden",
"DownloadPropersAndRepacksHelpText": "Automatisch Proper oder Repacks zum upgraden eines Filmes zulassen",
@@ -927,10 +925,10 @@
"AllMoviesInPathHaveBeenImported": "Alle Filme in {path} wurden importiert",
"AllFiles": "Alle Dateien",
"AfterManualRefresh": "Nach manueller Aktualisierung",
"AddToDownloadQueue": "Zur Download-Warteschlange hinzufügen",
"AddToDownloadQueue": "Zur Download Warteschlange hinzufügen",
"AddRootFolder": "Stammverzeichnis hinzufügen",
"AddQualityProfile": "Qualitäts Profil hinzufügen",
"AddedToDownloadQueue": "Zur Download-Warteschlange hinzugefügt",
"AddedToDownloadQueue": "Zur Download Warteschlange hinzugefügt",
"AddDownloadClient": "Downloadmanager hinzufügen",
"AddCustomFormat": "Eigenes Format hinzufügen",
"AddDelayProfile": "Verzögerungsprofil hinzufügen",
@@ -1081,7 +1079,7 @@
"RemoveSelectedItemsQueueMessageText": "Bist du sicher, dass du {selectedCount} Einträge aus der Warteschlange entfernen willst?",
"ResetDefinitionTitlesHelpText": "Definitionstitel und -werte zurücksetzen",
"DeleteConditionMessageText": "Bist du sicher, dass du die Bedingung '{0}' löschen willst?",
"DeleteCustomFormatMessageText": "Bist du sicher, dass du das eigene Format '{name}' löschen willst?",
"DeleteCustomFormatMessageText": "Bist du sicher, dass du das benutzerdefinierte Format '{name}' wirklich löschen willst?",
"DeleteDelayProfileMessageText": "Sind Sie sicher, dass Sie dieses Verzögerungsprofil löschen möchten?",
"DeleteFormatMessageText": "Bist du sicher, dass du das Formatierungstag {0} löschen willst?",
"ResetAPIKeyMessageText": "Sind Sie sicher, dass Sie Ihren API-Schlüssel zurücksetzen möchten?",
@@ -1111,24 +1109,24 @@
"NoImportListsFound": "Keine Einspiel-Listen gefunden",
"NoIndexersFound": "Keine Indexer gefunden",
"CountIndexersSelected": "{count} Indexer ausgewählt",
"ApplyTagsHelpTextAdd": "Hinzufügen: Fügen Sie die Tags der vorhandenen Tag-Liste hinzu",
"ApplyTagsHelpTextRemove": "Entfernen: Die eingegebenen Tags entfernen",
"ApplyTagsHelpTextHowToApplyIndexers": "So wenden Sie Tags auf die ausgewählten Indexer an",
"ApplyTagsHelpTextReplace": "Ersetzen: Ersetzen Sie die Tags durch die eingegebenen Tags (geben Sie keine Tags ein, um alle Tags zu löschen).",
"ApplyTagsHelpTextAdd": "Hinzufügen: Füge Tags zu den bestehenden Tags hinzu",
"ApplyTagsHelpTextRemove": "Entfernen: Entferne die hinterlegten Tags",
"ApplyTagsHelpTextHowToApplyIndexers": "Wie Tags zu den selektierten Indexern hinzugefügt werden können",
"ApplyTagsHelpTextReplace": "Ersetzen: Ersetze die Tags mit den eingegebenen Tags (keine Tags eingeben um alle Tags zu löschen)",
"DeleteImportList": "Importliste löschen",
"DeleteImportListMessageText": "Sind Sie sicher, dass Sie die Liste {name}“ löschen möchten?",
"DeleteQualityProfileMessageText": "Sind Sie sicher, dass Sie das Qualitätsprofil {name}“ löschen möchten?",
"DeleteImportListMessageText": "Bist du sicher, dass du die Liste '{name}' wirklich löschen willst?",
"DeleteQualityProfileMessageText": "Bist du sicher, dass du das Qualitätsprofil '{name}' wirklich löschen willst?",
"RemoveQueueItemConfirmation": "Bist du sicher, dass du {0} Einträge aus der Warteschlange entfernen willst?",
"SkipRedownload": "Überspringe erneuten Download",
"MoveAutomatically": "Automatisch verschieben",
"AutoTaggingNegateHelpText": "Wenn aktiviert wird das eigene Format nicht angewendet solange diese {0} Bedingung zutrifft.",
"AutoTaggingRequiredHelpText": "Diese {0} Bedingungen müsen zutreffen damit das eigene Format zutrifft. Ansonsten reicht ein einzelner {1} Treffer.",
"AutoTaggingNegateHelpText": "Falls aktiviert wird die Auto Tagging Regel nicht angewendet, solange diese Bedingung {implementationName} zutrifft.",
"AutoTaggingRequiredHelpText": "Diese {0} Bedingungen müssen erfüllt sein, damit das eigene Format zutrifft. Ansonsten reicht ein einzelner {1} Treffer.",
"DeleteAutoTagHelpText": "Sind Sie sicher, dass Sie das automatische Tag „{name}“ löschen möchten?",
"DeleteSelectedDownloadClientsMessageText": "Sind Sie sicher, dass Sie {count} ausgewählte Download-Clients löschen möchten?",
"DeleteSelectedImportListsMessageText": "Sind Sie sicher, dass Sie {count} ausgewählte Importliste(n) löschen möchten?",
"DeleteSelectedIndexersMessageText": "Sind Sie sicher, dass Sie {count} ausgewählte(n) Indexer löschen möchten?",
"ApplyTagsHelpTextHowToApplyDownloadClients": "So wenden Sie Tags auf die ausgewählten Download-Clients an",
"ApplyTagsHelpTextHowToApplyImportLists": "So wenden Sie Tags auf die ausgewählten Importlisten an",
"ApplyTagsHelpTextHowToApplyDownloadClients": "Wie Tags zu den selektierten Downloadclients hinzugefügt werden können",
"ApplyTagsHelpTextHowToApplyImportLists": "Wie Tags den selektierten Importlisten hinzugefügt werden können",
"DeleteRootFolderMessageText": "Sind Sie sicher, dass Sie den Stammordner „{path}“ löschen möchten?",
"DeleteRootFolder": "Stammordner löschen",
"AddConnection": "Verbindung hinzufügen",
@@ -1170,13 +1168,13 @@
"ConnectionLostToBackend": "{appName} hat die Verbindung zum Backend verloren und muss neu geladen werden, um die Funktionalität wiederherzustellen.",
"CountImportListsSelected": "{count} Importliste(n) ausgewählt",
"CustomFormatJson": "Benutzerdefiniertes JSON-Format",
"DefaultNameCopiedProfile": "{Name} Kopieren",
"DelayingDownloadUntil": "Download wird bis zum {Datum} um {Uhrzeit} verzögert",
"DefaultNameCopiedProfile": "{name} Kopieren",
"DelayingDownloadUntil": "Download wird bis zum {date} um {time} verzögert",
"DeleteAutoTag": "Auto-Tag löschen",
"DeletedReasonManual": "Die Datei wurde über die Benutzeroberfläche gelöscht",
"DeletedReasonUpgrade": "Die Datei wurde gelöscht, um ein Upgrade zu importieren",
"RemoveTagsAutomatically": "Tags automatisch entfernen",
"RetryingDownloadOn": "Erneuter Downloadversuch am {Datum} um {Uhrzeit}",
"RetryingDownloadOn": "Erneuter Downloadversuch am {date} um {time}",
"SetReleaseGroupModalTitle": "{modalTitle} Release-Gruppe festlegen",
"UpdaterLogFiles": "Updater-Protokolldateien",
"Default": "Standard",
@@ -1195,11 +1193,32 @@
"True": "WAHR",
"AuthenticationMethod": "Authentifizierungsmethode",
"AutoRedownloadFailedFromInteractiveSearchHelpText": "Suchen Sie automatisch nach einer anderen Version und versuchen Sie, sie herunterzuladen, wenn eine fehlerhafte Version aus der interaktiven Suche ausgewählt wurde",
"DefaultNameCopiedSpecification": "{Name} Kopieren",
"DefaultNameCopiedSpecification": "{name} Kopieren",
"CountDownloadClientsSelected": "{count} Download-Client(s) ausgewählt",
"AddRootFolderError": "Stammverzeichnis kann nicht hinzugefügt werden",
"ClearBlocklist": "Sperrliste leeren",
"CloneAutoTag": "Automatische Tags kopieren",
"Retention": "Aufbewahrung ( Retention )",
"Unmonitored": "Nicht beobachtet"
"Unmonitored": "Nicht beobachtet",
"DownloadClientFreeboxUnableToReachFreebox": "Die Freebox-API kann nicht erreicht werden. Überprüfen Sie die Einstellungen „Host“, „Port“ oder „SSL verwenden“. (Fehler: {exceptionMessage})",
"DownloadClientDownloadStationValidationNoDefaultDestinationDetail": "Sie müssen sich bei Ihrer Diskstation als {username} anmelden und sie manuell in den DownloadStation-Einstellungen unter BT/HTTP/FTP/NZB -> Standort einrichten.",
"UsenetBlackhole": "Usenet Blackhole",
"BlocklistAndSearch": "Sperrliste und Suche",
"BlocklistAndSearchHint": "Starte Suche nach einer Alternative, falls es der Sperrliste hinzugefügt wurde",
"BlocklistMultipleOnlyHint": "Der Sperrliste hinzufügen, ohne nach Alternativen zu suchen",
"ChangeCategory": "Kategorie wechseln",
"BlackholeFolderHelpText": "Ordner, in dem {appName} die Datei {extension} speichert",
"BlackholeWatchFolderHelpText": "Der Ordner, aus dem {appName} fertige Downloads importieren soll",
"BlocklistAndSearchMultipleHint": "Starte Suchen nach einer Alternative, falls es der Sperrliste hinzugefügt wurde",
"BlocklistOnly": "Nur der Sperrliste hinzufügen",
"Category": "Kategorie",
"ClickToChangeIndexerFlags": "Klicken, um Indexer-Flags zu ändern",
"AddReleaseProfile": "Release Profil hinzufügen",
"BlocklistReleaseHelpText": "Dieses Release für erneuten Download durch {appName} via RSS oder automatische Suche sperren",
"AddListExclusion": "Listenausschluss hinzufügen",
"ChownGroup": "chown Gruppe",
"Clone": "Klonen",
"BlocklistOnlyHint": "Der Sperrliste hinzufügen, ohne nach Alternative zu suchen",
"AddAutoTagError": "Auto-Tag konnte nicht hinzugefügt werden. Bitte erneut versuchen.",
"AddDelayProfileError": "Verzögerungsprofil konnte nicht hinzugefügt werden. Bitte erneut versuchen."
}

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