mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-22 17:04:39 -04:00
Compare commits
99 Commits
v5.27.0.10
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1513ca39e | ||
|
|
7062b3a178 | ||
|
|
7885404c2c | ||
|
|
1ce3783566 | ||
|
|
1a2b90bf36 | ||
|
|
89110c2cc8 | ||
|
|
a126835028 | ||
|
|
4c00729183 | ||
|
|
b59ff0a3b1 | ||
|
|
b9c2563c9b | ||
|
|
949922b9a1 | ||
|
|
1b9662d588 | ||
|
|
005c870f69 | ||
|
|
90cd8df1ae | ||
|
|
7d8444c435 | ||
|
|
1883ae52ac | ||
|
|
47d4ebbeac | ||
|
|
ef9836d71d | ||
|
|
955ee2f29b | ||
|
|
abf3fc4557 | ||
|
|
1e72cc6b5a | ||
|
|
24639a7016 | ||
|
|
e52547fa37 | ||
|
|
ff6a69701f | ||
|
|
f6afbfa684 | ||
|
|
b1b33e0dbf | ||
|
|
cf465899b4 | ||
|
|
e63691935d | ||
|
|
1bae9499e4 | ||
|
|
c991a8927d | ||
|
|
3c75250c08 | ||
|
|
1e06fc5b43 | ||
|
|
52307038af | ||
|
|
0297dba7f9 | ||
|
|
554a54b009 | ||
|
|
64b2a10b3f | ||
|
|
97c226c23c | ||
|
|
9959c658be | ||
|
|
eaeb668eb5 | ||
|
|
bb6713f1d2 | ||
|
|
9906b95893 | ||
|
|
8c94581cb6 | ||
|
|
6bdbc9c600 | ||
|
|
f28691e48d | ||
|
|
e7bddaeedd | ||
|
|
94ced8cff9 | ||
|
|
3429fe0696 | ||
|
|
100e121afc | ||
|
|
24be516fdb | ||
|
|
f49c35563d | ||
|
|
6e23750705 | ||
|
|
30fc50e049 | ||
|
|
8000abc2be | ||
|
|
62a05e2765 | ||
|
|
f04bff8e91 | ||
|
|
84593502a3 | ||
|
|
d478b404df | ||
|
|
80a9fa68de | ||
|
|
8eb9fc71b8 | ||
|
|
6b1567ddae | ||
|
|
265e931451 | ||
|
|
2a886fb26a | ||
|
|
2235823af3 | ||
|
|
f99162b8ee | ||
|
|
a00ee08750 | ||
|
|
54cbbe05d9 | ||
|
|
57f602eb02 | ||
|
|
e841c9b764 | ||
|
|
81bbaf8946 | ||
|
|
8b4288fa18 | ||
|
|
9aa3061e8e | ||
|
|
308c58f729 | ||
|
|
d38492188a | ||
|
|
50e75e1362 | ||
|
|
f36845c251 | ||
|
|
110a338fb6 | ||
|
|
3fcbaf9259 | ||
|
|
576eff1890 | ||
|
|
b0284bda07 | ||
|
|
c78666009d | ||
|
|
b51d1beaaa | ||
|
|
4d22bf1ceb | ||
|
|
f9562b9b76 | ||
|
|
6851c26328 | ||
|
|
e29be26fc9 | ||
|
|
f6bd2f52d5 | ||
|
|
8bef9b4da7 | ||
|
|
787c387036 | ||
|
|
0525256115 | ||
|
|
5767e181b7 | ||
|
|
1cf3ef5dff | ||
|
|
b6bad2398c | ||
|
|
16308e4b1c | ||
|
|
bd7465fae4 | ||
|
|
c0d70485c3 | ||
|
|
c743383912 | ||
|
|
d93c1d7808 | ||
|
|
0e2e7e4259 | ||
|
|
e6b27512c9 |
@@ -2,7 +2,7 @@
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
|
||||
{
|
||||
"name": "Radarr",
|
||||
"image": "mcr.microsoft.com/devcontainers/dotnet:1-6.0",
|
||||
"image": "mcr.microsoft.com/devcontainers/dotnet:1-8.0",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"nodeGypDependencies": true,
|
||||
|
||||
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -10,7 +10,7 @@
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build dotnet",
|
||||
// If you have changed target frameworks, make sure to update the program path.
|
||||
"program": "${workspaceFolder}/_output/net6.0/Radarr",
|
||||
"program": "${workspaceFolder}/_output/net8.0/Radarr",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
|
||||
|
||||
183
CONTRIBUTING.md
183
CONTRIBUTING.md
@@ -1,13 +1,186 @@
|
||||
|
||||
# How to Contribute
|
||||
|
||||
We're always looking for people to help make Radarr even better, there are a number of ways to contribute.
|
||||
|
||||
This file has been moved to the wiki for the latest details please see the [contributing wiki page](https://wiki.servarr.com/radarr/contributing).
|
||||
# Documentation
|
||||
|
||||
## Documentation
|
||||
Setup guides, [FAQ](/radarr/faq), the more information we have on the [wiki](https://wiki.servarr.com/radarr) the better.
|
||||
|
||||
Setup guides, [FAQ](https://wiki.servarr.com/radarr/faq), the more information we have on the [wiki](https://wiki.servarr.com/radarr) the better.
|
||||
# Development
|
||||
|
||||
## Development
|
||||
Radarr is written in C# (backend) and JS (frontend). The backend is built on the .NET6 (and _soon_ .NET8) framework, while the frontend utilizes Reactjs.
|
||||
|
||||
See the [Wiki Page](https://wiki.servarr.com/radarr/contributing)
|
||||
## Tools required
|
||||
|
||||
- Visual Studio 2022 or higher is recommended (<https://www.visualstudio.com/vs/>). The community version is free and works (<https://www.visualstudio.com/downloads/>).
|
||||
|
||||
> VS 2022 V17.0 or higher is recommended as it includes the .NET6 SDK
|
||||
{.is-info}
|
||||
|
||||
- HTML/Javascript editor of choice (VS Code/Sublime Text/Webstorm/Atom/etc)
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
- The [Node.js](https://nodejs.org/) runtime is required. The following versions are supported:
|
||||
- **20** (any minor or patch version within this)
|
||||
{.grid-list}
|
||||
|
||||
> The Application will **NOT** run on older versions such as `18.x`, `16.x` or any version below 20.0! Due to a dependency issue, it will also not run on `21.x` and is untested on other verisons.
|
||||
{.is-warning}
|
||||
|
||||
- [Yarn](https://yarnpkg.com/getting-started/install) is required to build the frontend
|
||||
- Yarn is included with **Node 20**+ by default. Enable it with `corepack enable`
|
||||
- For other Node versions, install it with `npm i -g corepack`
|
||||
|
||||
## Getting started
|
||||
|
||||
1. Fork Radarr
|
||||
1. Clone the repository into your development machine. [*info*](https://docs.github.com/en/get-started/quickstart/fork-a-repo)
|
||||
|
||||
> Be sure to run lint `yarn lint --fix` on your code for any front end changes before committing.
|
||||
For css changes `yarn stylelint-windows --fix` {.is-info}
|
||||
|
||||
### Building the frontend
|
||||
|
||||
- Navigate to the cloned directory
|
||||
- Install the required Node Packages
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
```
|
||||
|
||||
- Start webpack to monitor your development environment for any changes that need post processing using:
|
||||
|
||||
```bash
|
||||
yarn start
|
||||
```
|
||||
|
||||
### Building the Backend
|
||||
|
||||
The backend solution is most easily built and ran in Visual Studio or Rider, however if the only priority is working on the frontend UI it can be built easily from command line as well when the correct SDK is installed.
|
||||
|
||||
#### Visual Studio
|
||||
|
||||
> Ensure startup project is set to `Radarr.Console` and framework to `net6.0`
|
||||
{.is-info}
|
||||
|
||||
1. First `Build` the solution in Visual Studio, this will ensure all projects are correctly built and dependencies restored
|
||||
1. Next `Debug/Run` the project in Visual Studio to start Radarr
|
||||
1. Open <http://localhost:7878>
|
||||
|
||||
#### Command line
|
||||
|
||||
1. Clean solution
|
||||
|
||||
```shell
|
||||
dotnet clean src/Radarr.sln -c Debug
|
||||
```
|
||||
|
||||
1. Restore and Build debug configuration for the correct platform (Posix or Windows)
|
||||
|
||||
```shell
|
||||
dotnet msbuild -restore src/Radarr.sln -p:Configuration=Debug -p:Platform=Posix -t:PublishAllRids
|
||||
```
|
||||
|
||||
1. Run the produced executable from `/_output`
|
||||
|
||||
## Contributing Code
|
||||
|
||||
- If you're adding a new, already requested feature, please comment on [GitHub Issues](https://github.com/Radarr/Radarr/issues) so work is not duplicated (If you want to add something not already on there, please talk to us first)
|
||||
- Rebase from Radarr's develop branch, do not merge
|
||||
- Make meaningful commits, or squash them
|
||||
- Feel free to make a pull request before work is complete, this will let us see where its at and make comments/suggest improvements
|
||||
- Reach out to us on the discord if you have any questions
|
||||
- Add tests (unit/integration)
|
||||
- Commit with \*nix line endings for consistency (We checkout Windows and commit \*nix)
|
||||
- One feature/bug fix per pull request to keep things clean and easy to understand
|
||||
- Use 4 spaces instead of tabs, this is the default for VS 2022 and WebStorm
|
||||
|
||||
## Pull Requesting
|
||||
|
||||
- Only make pull requests to `develop`, never `master`, if you make a PR to `master` we will comment on it and close it
|
||||
- You're probably going to get some comments or questions from us, they will be to ensure consistency and maintainability
|
||||
- We'll try to respond to pull requests as soon as possible, if its been a day or two, please reach out to us, we may have missed it
|
||||
- Each PR should come from its own [feature branch](http://martinfowler.com/bliki/FeatureBranch.html) not develop in your fork, it should have a meaningful branch name (what is being added/fixed)
|
||||
- `new-feature` (Good)
|
||||
- `fix-bug` (Good)
|
||||
- `patch` (Bad)
|
||||
- `develop` (Bad)
|
||||
- Commits should be wrote as `New:` or `Fixed:` for changes that would not be considered a `maintenance release`
|
||||
|
||||
## Unit Testing
|
||||
|
||||
Radarr utilizes nunit for its unit, integration, and automation test suite.
|
||||
|
||||
### Running Tests
|
||||
|
||||
Tests can be run easily from within VS using the included nunit3testadapter nuget package or from the command line using the included bash script `test.sh`.
|
||||
|
||||
From VS simply navigate to Test Explorer and run or debug the tests you'd like to examine.
|
||||
|
||||
Tests can be run all at once or one at a time in VS.
|
||||
|
||||
From command line the `test.sh` script accepts 3 parameters
|
||||
|
||||
```bash
|
||||
test.sh <PLATFORM> <TYPE> <COVERAGE>
|
||||
```
|
||||
|
||||
### Writing Tests
|
||||
|
||||
While not always fun, we encourage writing unit tests for any backend code changes. This will ensure the change is functioning as you intended and that future changes dont break the expected behavior.
|
||||
|
||||
> We currently require 80% coverage on new code when submitting a PR
|
||||
{.is-info}
|
||||
|
||||
If you have any questions about any of this, please let us know.
|
||||
|
||||
# Translation
|
||||
|
||||
Radarr uses a self hosted open access [Weblate](https://translate.servarr.com) instance to manage its json translation files. These files are stored in the repo at `src/NzbDrone.Core/Localization`
|
||||
|
||||
## Contributing to an Existing Translation
|
||||
|
||||
Weblate handles synchronization and translation of strings for all languages other than English. Editing of translated strings and translating existing strings for supported languages should be performed there for the Radarr project.
|
||||
|
||||
The English translation, `en.json`, serves as the source for all other translations and is managed on GitHub repo.
|
||||
|
||||
## Adding a Language
|
||||
|
||||
Adding translations to Radarr requires two steps
|
||||
|
||||
- Adding the Language to weblate
|
||||
- Adding the Language to Radarr codebase
|
||||
|
||||
## Adding Translation Strings in Code
|
||||
|
||||
The English translation, `src/NzbDrone.Core/Localization/en.json`, serves as the source for all other translations and is managed on GitHub repo. When adding a new string to either the UI or backend a key must also be added to `en.json` along with the default value in English. This key may then be consumed as follows:
|
||||
|
||||
> PRs for translation of log messages will not be accepted
|
||||
{.is-warning}
|
||||
|
||||
### Backend Strings
|
||||
|
||||
Backend strings may be added utilizing the Localization Service `GetLocalizedString` method
|
||||
|
||||
```dotnet
|
||||
private readonly ILocalizationService _localizationService;
|
||||
|
||||
public IndexerCheck(ILocalizationService localizationService)
|
||||
{
|
||||
_localizationService = localizationService;
|
||||
}
|
||||
|
||||
var translated = _localizationService.GetLocalizedString("IndexerHealthCheckNoIndexers")
|
||||
```
|
||||
|
||||
### Frontend Strings
|
||||
|
||||
New strings can be added to the frontend by importing the translate function and using a key specified from `en.json`
|
||||
|
||||
```js
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
<div>
|
||||
{translate('UnableToAddANewIndexerPleaseTryAgain')}
|
||||
</div>
|
||||
```
|
||||
|
||||
@@ -9,18 +9,18 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '5.27.0'
|
||||
majorVersion: '6.1.1'
|
||||
minorVersion: $[counter('minorVersion', 2000)]
|
||||
radarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||
sentryOrg: 'servarr'
|
||||
sentryUrl: 'https://sentry.servarr.com'
|
||||
dotnetVersion: '6.0.427'
|
||||
dotnetVersion: '8.0.405'
|
||||
nodeVersion: '20.X'
|
||||
innoVersion: '6.2.2'
|
||||
windowsImage: 'windows-2022'
|
||||
linuxImage: 'ubuntu-22.04'
|
||||
macImage: 'macOS-13'
|
||||
windowsImage: 'windows-2025'
|
||||
linuxImage: 'ubuntu-24.04'
|
||||
macImage: 'macOS-15'
|
||||
|
||||
trigger:
|
||||
branches:
|
||||
@@ -106,7 +106,7 @@ stages:
|
||||
echo "Extra platforms already enabled"
|
||||
else
|
||||
echo "Enabling extra platform support"
|
||||
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
|
||||
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64/' "$BUNDLEDVERSIONS"
|
||||
fi
|
||||
displayName: Enable Extra Platform Support
|
||||
- bash: ./build.sh --backend --enable-extra-platforms
|
||||
@@ -122,27 +122,23 @@ stages:
|
||||
artifact: '$(osName)Backend'
|
||||
displayName: Publish Backend
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net6.0/win-x64/publish'
|
||||
- publish: '$(testsFolder)/net8.0/win-x64/publish'
|
||||
artifact: win-x64-tests
|
||||
displayName: Publish win-x64 Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net6.0/linux-x64/publish'
|
||||
- publish: '$(testsFolder)/net8.0/linux-x64/publish'
|
||||
artifact: linux-x64-tests
|
||||
displayName: Publish linux-x64 Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net6.0/linux-x86/publish'
|
||||
artifact: linux-x86-tests
|
||||
displayName: Publish linux-x86 Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
|
||||
- publish: '$(testsFolder)/net8.0/linux-musl-x64/publish'
|
||||
artifact: linux-musl-x64-tests
|
||||
displayName: Publish linux-musl-x64 Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish'
|
||||
- publish: '$(testsFolder)/net8.0/freebsd-x64/publish'
|
||||
artifact: freebsd-x64-tests
|
||||
displayName: Publish freebsd-x64 Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net6.0/osx-x64/publish'
|
||||
- publish: '$(testsFolder)/net8.0/osx-x64/publish'
|
||||
artifact: osx-x64-tests
|
||||
displayName: Publish osx-x64 Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
@@ -189,7 +185,7 @@ stages:
|
||||
artifact: '$(osName)Frontend'
|
||||
displayName: Publish Frontend
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
|
||||
|
||||
- stage: Installer
|
||||
dependsOn:
|
||||
- Build_Backend
|
||||
@@ -260,21 +256,21 @@ stages:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x64.zip'
|
||||
archiveType: 'zip'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/win-x64/net6.0
|
||||
rootFolderOrFile: $(artifactsFolder)/win-x64/net8.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create win-x86 zip
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x86.zip'
|
||||
archiveType: 'zip'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/win-x86/net6.0
|
||||
rootFolderOrFile: $(artifactsFolder)/win-x86/net8.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create osx-x64 app
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-x64.zip'
|
||||
archiveType: 'zip'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/osx-x64-app/net6.0
|
||||
rootFolderOrFile: $(artifactsFolder)/osx-x64-app/net8.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create osx-x64 tar
|
||||
inputs:
|
||||
@@ -282,14 +278,14 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/osx-x64/net6.0
|
||||
rootFolderOrFile: $(artifactsFolder)/osx-x64/net8.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create osx-arm64 app
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-arm64.zip'
|
||||
archiveType: 'zip'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/osx-arm64-app/net6.0
|
||||
rootFolderOrFile: $(artifactsFolder)/osx-arm64-app/net8.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create osx-arm64 tar
|
||||
inputs:
|
||||
@@ -297,7 +293,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/osx-arm64/net6.0
|
||||
rootFolderOrFile: $(artifactsFolder)/osx-arm64/net8.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create linux-x64 tar
|
||||
inputs:
|
||||
@@ -305,7 +301,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-x64/net6.0
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-x64/net8.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create linux-musl-x64 tar
|
||||
inputs:
|
||||
@@ -313,15 +309,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create linux-x86 tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-core-x86.tar.gz'
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-x86/net6.0
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net8.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create linux-arm tar
|
||||
inputs:
|
||||
@@ -329,7 +317,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-arm/net6.0
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-arm/net8.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create linux-musl-arm tar
|
||||
inputs:
|
||||
@@ -337,7 +325,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm/net6.0
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm/net8.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create linux-arm64 tar
|
||||
inputs:
|
||||
@@ -345,7 +333,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net6.0
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net8.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create linux-musl-arm64 tar
|
||||
inputs:
|
||||
@@ -353,7 +341,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net6.0
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net8.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create freebsd-x64 tar
|
||||
inputs:
|
||||
@@ -361,7 +349,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/freebsd-x64/net6.0
|
||||
rootFolderOrFile: $(artifactsFolder)/freebsd-x64/net8.0
|
||||
- publish: $(Build.ArtifactStagingDirectory)
|
||||
artifact: 'Packages'
|
||||
displayName: Publish Packages
|
||||
@@ -392,7 +380,7 @@ stages:
|
||||
SENTRY_AUTH_TOKEN: $(sentryAuthTokenServarr)
|
||||
SENTRY_ORG: $(sentryOrg)
|
||||
SENTRY_URL: $(sentryUrl)
|
||||
|
||||
|
||||
- stage: Unit_Test
|
||||
displayName: Unit Tests
|
||||
dependsOn: Build_Backend
|
||||
@@ -493,29 +481,19 @@ stages:
|
||||
testName: 'Musl Net Core'
|
||||
artifactName: linux-musl-x64-tests
|
||||
containerImage: ghcr.io/servarr/testimages:alpine
|
||||
linux-x86:
|
||||
testName: 'linux-x86'
|
||||
artifactName: linux-x86-tests
|
||||
containerImage: ghcr.io/servarr/testimages:linux-x86
|
||||
|
||||
pool:
|
||||
vmImage: ${{ variables.linuxImage }}
|
||||
|
||||
|
||||
container: $[ variables['containerImage'] ]
|
||||
|
||||
timeoutInMinutes: 10
|
||||
|
||||
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Install .NET'
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
condition: and(succeeded(), ne(variables['testName'], 'linux-x86'))
|
||||
- bash: |
|
||||
SDKURL=$(curl -s https://api.github.com/repos/Servarr/dotnet-linux-x86/releases | jq -rc '.[].assets[].browser_download_url' | grep sdk-${DOTNETVERSION}.*gz$)
|
||||
curl -fsSL $SDKURL | tar xzf - -C /opt/dotnet
|
||||
displayName: 'Install .NET'
|
||||
condition: and(succeeded(), eq(variables['testName'], 'linux-x86'))
|
||||
- checkout: none
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Test Artifact
|
||||
@@ -559,7 +537,7 @@ stages:
|
||||
vmImage: ${{ variables.linuxImage }}
|
||||
|
||||
timeoutInMinutes: 10
|
||||
|
||||
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Install .net core'
|
||||
@@ -611,12 +589,12 @@ stages:
|
||||
Radarr__Postgres__Port: '5432'
|
||||
Radarr__Postgres__User: 'radarr'
|
||||
Radarr__Postgres__Password: 'radarr'
|
||||
|
||||
|
||||
pool:
|
||||
vmImage: ${{ variables.linuxImage }}
|
||||
|
||||
timeoutInMinutes: 10
|
||||
|
||||
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Install .net core'
|
||||
@@ -699,7 +677,7 @@ stages:
|
||||
|
||||
pool:
|
||||
vmImage: $(imageName)
|
||||
|
||||
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Install .net core'
|
||||
@@ -721,7 +699,7 @@ stages:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
- task: ExtractFiles@1
|
||||
inputs:
|
||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||
displayName: Extract Package
|
||||
- bash: |
|
||||
@@ -776,7 +754,7 @@ stages:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
- task: ExtractFiles@1
|
||||
inputs:
|
||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||
displayName: Extract Package
|
||||
- bash: |
|
||||
@@ -840,7 +818,7 @@ stages:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
- task: ExtractFiles@1
|
||||
inputs:
|
||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||
displayName: Extract Package
|
||||
- bash: |
|
||||
@@ -926,29 +904,18 @@ stages:
|
||||
artifactName: linux-musl-x64-tests
|
||||
containerImage: ghcr.io/servarr/testimages:alpine
|
||||
pattern: 'Radarr.*.linux-musl-core-x64.tar.gz'
|
||||
linux-x86:
|
||||
testName: 'linux-x86'
|
||||
artifactName: linux-x86-tests
|
||||
containerImage: ghcr.io/servarr/testimages:linux-x86
|
||||
pattern: 'Radarr.*.linux-core-x86.tar.gz'
|
||||
pool:
|
||||
vmImage: ${{ variables.linuxImage }}
|
||||
|
||||
container: $[ variables['containerImage'] ]
|
||||
|
||||
timeoutInMinutes: 15
|
||||
|
||||
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Install .NET'
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
condition: and(succeeded(), ne(variables['testName'], 'linux-x86'))
|
||||
- bash: |
|
||||
SDKURL=$(curl -s https://api.github.com/repos/Servarr/dotnet-linux-x86/releases | jq -rc '.[].assets[].browser_download_url' | grep sdk-${DOTNETVERSION}.*gz$)
|
||||
curl -fsSL $SDKURL | tar xzf - -C /opt/dotnet
|
||||
displayName: 'Install .NET'
|
||||
condition: and(succeeded(), eq(variables['testName'], 'linux-x86'))
|
||||
- checkout: none
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Test Artifact
|
||||
@@ -965,7 +932,7 @@ stages:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
- task: ExtractFiles@1
|
||||
inputs:
|
||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||
displayName: Extract Package
|
||||
- bash: |
|
||||
@@ -988,7 +955,7 @@ stages:
|
||||
- stage: Automation
|
||||
displayName: Automation
|
||||
dependsOn: Packages
|
||||
|
||||
|
||||
jobs:
|
||||
- job: Automation
|
||||
strategy:
|
||||
@@ -1014,7 +981,7 @@ stages:
|
||||
|
||||
pool:
|
||||
vmImage: $(imageName)
|
||||
|
||||
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Install .net core'
|
||||
@@ -1036,7 +1003,7 @@ stages:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
- task: ExtractFiles@1
|
||||
inputs:
|
||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||
displayName: Extract Package
|
||||
- bash: |
|
||||
@@ -1161,7 +1128,7 @@ stages:
|
||||
- checkout: self
|
||||
submodules: true
|
||||
persistCredentials: true
|
||||
fetchDepth: 1
|
||||
fetchDepth: 1
|
||||
- bash: ./docs.sh Windows
|
||||
displayName: Create openapi.json
|
||||
- bash: |
|
||||
@@ -1227,22 +1194,23 @@ stages:
|
||||
extraProperties: |
|
||||
sonar.exclusions=**/obj/**,**/*.dll,**/NzbDrone.Core.Test/Files/**/*,./frontend/**,**/ExternalModules/**,./src/Libraries/**
|
||||
sonar.coverage.exclusions=**/Radarr.Api.V3/**/*
|
||||
sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml
|
||||
sonar.cs.cobertura.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.cobertura.xml
|
||||
sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml
|
||||
- bash: |
|
||||
./build.sh --backend -f net6.0 -r win-x64
|
||||
TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage
|
||||
./build.sh --backend -f net8.0 -r win-x64
|
||||
TEST_DIR=_tests/net8.0/win-x64/publish/ ./test.sh Windows Unit Coverage
|
||||
displayName: Coverage Unit Tests
|
||||
- task: SonarCloudAnalyze@3
|
||||
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
||||
displayName: Publish SonarCloud Results
|
||||
- task: reportgenerator@5.3.11
|
||||
- task: reportgenerator@5
|
||||
displayName: Generate Coverage Report
|
||||
inputs:
|
||||
reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml'
|
||||
reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.cobertura.xml'
|
||||
targetdir: '$(Build.SourcesDirectory)/CoverageResults/combined'
|
||||
reporttypes: 'HtmlInline_AzurePipelines;Cobertura;Badges'
|
||||
publishCodeCoverageResults: true
|
||||
sourcedirs: src
|
||||
|
||||
- stage: Report_Out
|
||||
dependsOn:
|
||||
@@ -1274,4 +1242,3 @@ stages:
|
||||
DISCORDCHANNELID: $(discordChannelId)
|
||||
DISCORDWEBHOOKKEY: $(discordWebhookKey)
|
||||
DISCORDTHREADID: $(discordThreadId)
|
||||
|
||||
|
||||
52
build.sh
52
build.sh
@@ -33,14 +33,14 @@ EnableExtraPlatformsInSDK()
|
||||
echo "Extra platforms already enabled"
|
||||
else
|
||||
echo "Enabling extra platform support"
|
||||
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
|
||||
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64/' "$BUNDLEDVERSIONS"
|
||||
fi
|
||||
}
|
||||
|
||||
EnableExtraPlatforms()
|
||||
{
|
||||
if grep -qv freebsd-x64 src/Directory.Build.props; then
|
||||
sed -i'' -e "s^<RuntimeIdentifiers>\(.*\)</RuntimeIdentifiers>^<RuntimeIdentifiers>\1;freebsd-x64;linux-x86</RuntimeIdentifiers>^g" src/Directory.Build.props
|
||||
sed -i'' -e "s^<RuntimeIdentifiers>\(.*\)</RuntimeIdentifiers>^<RuntimeIdentifiers>\1;freebsd-x64</RuntimeIdentifiers>^g" src/Directory.Build.props
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -79,9 +79,9 @@ Build()
|
||||
|
||||
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
|
||||
then
|
||||
dotnet msbuild -restore $slnFile -p:Configuration=Release -p:Platform=$platform -t:PublishAllRids
|
||||
dotnet msbuild -restore $slnFile -p:SelfContained=True -p:Configuration=Release -p:Platform=$platform -t:PublishAllRids
|
||||
else
|
||||
dotnet msbuild -restore $slnFile -p:Configuration=Release -p:Platform=$platform -p:RuntimeIdentifiers=$RID -t:PublishAllRids
|
||||
dotnet msbuild -restore $slnFile -p:SelfContained=True -p:Configuration=Release -p:Platform=$platform -p:RuntimeIdentifiers=$RID -t:PublishAllRids
|
||||
fi
|
||||
|
||||
ProgressEnd 'Build'
|
||||
@@ -137,7 +137,7 @@ PackageLinux()
|
||||
|
||||
echo "Adding Radarr.Mono to UpdatePackage"
|
||||
cp $folder/Radarr.Mono.* $folder/Radarr.Update
|
||||
if [ "$framework" = "net6.0" ]; then
|
||||
if [ "$framework" = "net8.0" ]; then
|
||||
cp $folder/Mono.Posix.NETStandard.* $folder/Radarr.Update
|
||||
cp $folder/libMonoPosixHelper.* $folder/Radarr.Update
|
||||
fi
|
||||
@@ -165,7 +165,7 @@ PackageMacOS()
|
||||
|
||||
echo "Adding Radarr.Mono to UpdatePackage"
|
||||
cp $folder/Radarr.Mono.* $folder/Radarr.Update
|
||||
if [ "$framework" = "net6.0" ]; then
|
||||
if [ "$framework" = "net8.0" ]; then
|
||||
cp $folder/Mono.Posix.NETStandard.* $folder/Radarr.Update
|
||||
cp $folder/libMonoPosixHelper.* $folder/Radarr.Update
|
||||
fi
|
||||
@@ -377,15 +377,14 @@ then
|
||||
Build
|
||||
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
|
||||
then
|
||||
PackageTests "net6.0" "win-x64"
|
||||
PackageTests "net6.0" "win-x86"
|
||||
PackageTests "net6.0" "linux-x64"
|
||||
PackageTests "net6.0" "linux-musl-x64"
|
||||
PackageTests "net6.0" "osx-x64"
|
||||
PackageTests "net8.0" "win-x64"
|
||||
PackageTests "net8.0" "win-x86"
|
||||
PackageTests "net8.0" "linux-x64"
|
||||
PackageTests "net8.0" "linux-musl-x64"
|
||||
PackageTests "net8.0" "osx-x64"
|
||||
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
|
||||
then
|
||||
PackageTests "net6.0" "freebsd-x64"
|
||||
PackageTests "net6.0" "linux-x86"
|
||||
PackageTests "net8.0" "freebsd-x64"
|
||||
fi
|
||||
else
|
||||
PackageTests "$FRAMEWORK" "$RID"
|
||||
@@ -413,20 +412,19 @@ then
|
||||
|
||||
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
|
||||
then
|
||||
Package "net6.0" "win-x64"
|
||||
Package "net6.0" "win-x86"
|
||||
Package "net6.0" "linux-x64"
|
||||
Package "net6.0" "linux-musl-x64"
|
||||
Package "net6.0" "linux-arm64"
|
||||
Package "net6.0" "linux-musl-arm64"
|
||||
Package "net6.0" "linux-arm"
|
||||
Package "net6.0" "linux-musl-arm"
|
||||
Package "net6.0" "osx-x64"
|
||||
Package "net6.0" "osx-arm64"
|
||||
Package "net8.0" "win-x64"
|
||||
Package "net8.0" "win-x86"
|
||||
Package "net8.0" "linux-x64"
|
||||
Package "net8.0" "linux-musl-x64"
|
||||
Package "net8.0" "linux-arm64"
|
||||
Package "net8.0" "linux-musl-arm64"
|
||||
Package "net8.0" "linux-arm"
|
||||
Package "net8.0" "linux-musl-arm"
|
||||
Package "net8.0" "osx-x64"
|
||||
Package "net8.0" "osx-arm64"
|
||||
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
|
||||
then
|
||||
Package "net6.0" "freebsd-x64"
|
||||
Package "net6.0" "linux-x86"
|
||||
Package "net8.0" "freebsd-x64"
|
||||
fi
|
||||
else
|
||||
Package "$FRAMEWORK" "$RID"
|
||||
@@ -436,7 +434,7 @@ fi
|
||||
if [ "$INSTALLER" = "YES" ];
|
||||
then
|
||||
InstallInno
|
||||
BuildInstaller "net6.0" "win-x64"
|
||||
BuildInstaller "net6.0" "win-x86"
|
||||
BuildInstaller "net8.0" "win-x64"
|
||||
BuildInstaller "net8.0" "win-x86"
|
||||
RemoveInno
|
||||
fi
|
||||
|
||||
4
docs.sh
4
docs.sh
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
FRAMEWORK="net6.0"
|
||||
FRAMEWORK="net8.0"
|
||||
PLATFORM=$1
|
||||
ARCHITECTURE="${2:-x64}"
|
||||
|
||||
@@ -38,7 +38,7 @@ dotnet clean $slnFile -c Release
|
||||
dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p:RuntimeIdentifiers=$RUNTIME -t:PublishAllRids
|
||||
|
||||
dotnet new tool-manifest
|
||||
dotnet tool install --version 6.6.2 Swashbuckle.AspNetCore.Cli
|
||||
dotnet tool install --version 8.1.4 Swashbuckle.AspNetCore.Cli
|
||||
|
||||
dotnet tool run swagger tofile --output ./src/Radarr.Api.V3/openapi.json "$outputFolder/$FRAMEWORK/$RUNTIME/$application" v3 &
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ function BlocklistDetailsModal(props: BlocklistDetailsModalProps) {
|
||||
return (
|
||||
<Modal isOpen={isOpen} onModalClose={onModalClose}>
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>Details</ModalHeader>
|
||||
<ModalHeader>{translate('Details')}</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<DescriptionList>
|
||||
|
||||
@@ -304,7 +304,7 @@ function Queue() {
|
||||
<PageToolbar>
|
||||
<PageToolbarSection>
|
||||
<PageToolbarButton
|
||||
label="Refresh"
|
||||
label={translate('Refresh')}
|
||||
iconName={icons.REFRESH}
|
||||
isSpinning={isRefreshing}
|
||||
onPress={handleRefreshPress}
|
||||
|
||||
@@ -90,7 +90,7 @@ function QueueStatus(props: QueueStatusProps) {
|
||||
|
||||
if (trackedDownloadState === 'importing') {
|
||||
title += ` - ${translate('Importing')}`;
|
||||
iconKind = kinds.PURPLE;
|
||||
iconKind = kinds.PRIMARY;
|
||||
}
|
||||
|
||||
if (trackedDownloadState === 'failedPending') {
|
||||
|
||||
@@ -56,6 +56,8 @@ function CustomFiltersModalContent(props) {
|
||||
{translate('AddCustomFilter')}
|
||||
</Button>
|
||||
</div>
|
||||
<br />
|
||||
{translate('FilterMoviePropertiesOnlyNotFileWarning')}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
|
||||
@@ -26,6 +26,10 @@
|
||||
color: var(--warningColor);
|
||||
}
|
||||
|
||||
.primary {
|
||||
color: var(--primaryColor);
|
||||
}
|
||||
|
||||
.purple {
|
||||
color: var(--purple);
|
||||
}
|
||||
|
||||
1
frontend/src/Components/Icon.css.d.ts
vendored
1
frontend/src/Components/Icon.css.d.ts
vendored
@@ -6,6 +6,7 @@ interface CssExports {
|
||||
'disabled': string;
|
||||
'info': string;
|
||||
'pink': string;
|
||||
'primary': string;
|
||||
'purple': string;
|
||||
'success': string;
|
||||
'warning': string;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
.modal {
|
||||
position: relative;
|
||||
display: flex;
|
||||
max-width: 90%;
|
||||
max-height: 90%;
|
||||
border-radius: 6px;
|
||||
opacity: 1;
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -47,6 +47,15 @@ function DiscoverMovieSortMenu(props) {
|
||||
{translate('Studio')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="year"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('Year')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="inCinemas"
|
||||
sortKey={sortKey}
|
||||
|
||||
@@ -36,7 +36,8 @@
|
||||
.imdbRating,
|
||||
.rottenTomatoesRating,
|
||||
.traktRating,
|
||||
.runtime {
|
||||
.runtime,
|
||||
.year {
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
flex: 0 0 90px;
|
||||
|
||||
@@ -22,6 +22,7 @@ interface CssExports {
|
||||
'studio': string;
|
||||
'tmdbRating': string;
|
||||
'traktRating': string;
|
||||
'year': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
||||
|
||||
@@ -61,7 +61,8 @@
|
||||
.imdbRating,
|
||||
.rottenTomatoesRating,
|
||||
.traktRating,
|
||||
.runtime {
|
||||
.runtime,
|
||||
.year {
|
||||
composes: cell;
|
||||
|
||||
flex: 0 0 90px;
|
||||
|
||||
@@ -28,6 +28,7 @@ interface CssExports {
|
||||
'studio': string;
|
||||
'tmdbRating': string;
|
||||
'traktRating': string;
|
||||
'year': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
||||
|
||||
@@ -167,6 +167,14 @@ class DiscoverMovieRow extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'year') {
|
||||
return (
|
||||
<VirtualTableRowCell key={name} className={styles[name]}>
|
||||
{year}
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'collection') {
|
||||
return (
|
||||
<VirtualTableRowCell
|
||||
|
||||
@@ -54,7 +54,7 @@ export default function AuthenticationRequiredModalContent() {
|
||||
dispatch(fetchGeneralSettings());
|
||||
|
||||
return () => {
|
||||
dispatch(clearPendingChanges());
|
||||
dispatch(clearPendingChanges({ section: `settings.${SECTION}` }));
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
|
||||
56
frontend/src/Helpers/Hooks/useTheme.ts
Normal file
56
frontend/src/Helpers/Hooks/useTheme.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
import themes from 'Styles/Themes';
|
||||
|
||||
function createThemeSelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.settings.ui.item.theme || window.Radarr.theme,
|
||||
(theme) => theme
|
||||
);
|
||||
}
|
||||
|
||||
const useTheme = () => {
|
||||
const selectedTheme = useSelector(createThemeSelector());
|
||||
const [resolvedTheme, setResolvedTheme] = useState(selectedTheme);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedTheme !== 'auto') {
|
||||
setResolvedTheme(selectedTheme);
|
||||
return;
|
||||
}
|
||||
|
||||
const applySystemTheme = () => {
|
||||
setResolvedTheme(
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
? 'dark'
|
||||
: 'light'
|
||||
);
|
||||
};
|
||||
|
||||
applySystemTheme();
|
||||
|
||||
window
|
||||
.matchMedia('(prefers-color-scheme: dark)')
|
||||
.addEventListener('change', applySystemTheme);
|
||||
|
||||
return () => {
|
||||
window
|
||||
.matchMedia('(prefers-color-scheme: dark)')
|
||||
.removeEventListener('change', applySystemTheme);
|
||||
};
|
||||
}, [selectedTheme]);
|
||||
|
||||
return resolvedTheme;
|
||||
};
|
||||
|
||||
export default useTheme;
|
||||
|
||||
export const useThemeColor = (color: string) => {
|
||||
const theme = useTheme();
|
||||
const themeVariables = themes[theme];
|
||||
|
||||
// @ts-expect-error - themeVariables is a string indexable type
|
||||
return themeVariables[color];
|
||||
};
|
||||
@@ -284,7 +284,7 @@ const MovieIndex = withScrollPosition((props: MovieIndexProps) => {
|
||||
/>
|
||||
|
||||
<MovieIndexSelectAllButton
|
||||
label="SelectAll"
|
||||
label={translate('SelectAll')}
|
||||
isSelectMode={isSelectMode}
|
||||
overflowComponent={MovieIndexSelectAllMenuItem}
|
||||
/>
|
||||
|
||||
@@ -67,6 +67,7 @@ function MovieIndexOverview(props: MovieIndexOverviewProps) {
|
||||
monitored,
|
||||
status,
|
||||
path,
|
||||
titleSlug,
|
||||
overview,
|
||||
statistics = {} as Statistics,
|
||||
images,
|
||||
@@ -141,7 +142,9 @@ function MovieIndexOverview(props: MovieIndexOverviewProps) {
|
||||
<div className={styles.content}>
|
||||
<div className={styles.poster}>
|
||||
<div className={styles.posterContainer}>
|
||||
{isSelectMode ? <MovieIndexPosterSelect movieId={movieId} /> : null}
|
||||
{isSelectMode ? (
|
||||
<MovieIndexPosterSelect movieId={movieId} titleSlug={titleSlug} />
|
||||
) : null}
|
||||
|
||||
{status === 'deleted' ? (
|
||||
<div className={styles.deleted} title={translate('Deleted')} />
|
||||
|
||||
@@ -69,6 +69,7 @@ function MovieIndexPoster(props: MovieIndexPosterProps) {
|
||||
monitored,
|
||||
status,
|
||||
images,
|
||||
titleSlug,
|
||||
tmdbId,
|
||||
imdbId,
|
||||
youTubeTrailerId,
|
||||
@@ -141,7 +142,7 @@ function MovieIndexPoster(props: MovieIndexPosterProps) {
|
||||
setIsDeleteMovieModalOpen(false);
|
||||
}, [setIsDeleteMovieModalOpen]);
|
||||
|
||||
const link = `/movie/${tmdbId}`;
|
||||
const link = `/movie/${titleSlug}`;
|
||||
|
||||
const elementStyle = {
|
||||
width: `${posterWidth}px`,
|
||||
@@ -151,7 +152,9 @@ function MovieIndexPoster(props: MovieIndexPosterProps) {
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
<div className={styles.posterContainer} title={title}>
|
||||
{isSelectMode ? <MovieIndexPosterSelect movieId={movieId} /> : null}
|
||||
{isSelectMode ? (
|
||||
<MovieIndexPosterSelect movieId={movieId} titleSlug={titleSlug} />
|
||||
) : null}
|
||||
|
||||
<Label className={styles.controls}>
|
||||
<SpinnerIconButton
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 3;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.checkContainer {
|
||||
|
||||
@@ -7,15 +7,23 @@ import styles from './MovieIndexPosterSelect.css';
|
||||
|
||||
interface MovieIndexPosterSelectProps {
|
||||
movieId: number;
|
||||
titleSlug: string;
|
||||
}
|
||||
|
||||
function MovieIndexPosterSelect(props: MovieIndexPosterSelectProps) {
|
||||
const { movieId } = props;
|
||||
function MovieIndexPosterSelect({
|
||||
movieId,
|
||||
titleSlug,
|
||||
}: MovieIndexPosterSelectProps) {
|
||||
const [selectState, selectDispatch] = useSelect();
|
||||
const isSelected = selectState.selectedState[movieId];
|
||||
|
||||
const onSelectPress = useCallback(
|
||||
(event: SyntheticEvent<HTMLElement, PointerEvent>) => {
|
||||
if (event.nativeEvent.ctrlKey || event.nativeEvent.metaKey) {
|
||||
window.open(`${window.Radarr.urlBase}/movie/${titleSlug}`, '_blank');
|
||||
return;
|
||||
}
|
||||
|
||||
const shiftKey = event.nativeEvent.shiftKey;
|
||||
|
||||
selectDispatch({
|
||||
@@ -25,7 +33,7 @@ function MovieIndexPosterSelect(props: MovieIndexPosterSelectProps) {
|
||||
shiftKey,
|
||||
});
|
||||
},
|
||||
[movieId, isSelected, selectDispatch]
|
||||
[movieId, titleSlug, isSelected, selectDispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -161,7 +161,7 @@ class MovieFileEditorRow extends Component {
|
||||
>
|
||||
{indexerFlags ? (
|
||||
<Popover
|
||||
anchor={<Icon name={icons.FLAG} kind={kinds.PRIMARY} />}
|
||||
anchor={<Icon name={icons.FLAG} />}
|
||||
title={translate('IndexerFlags')}
|
||||
body={<IndexerFlags indexerFlags={indexerFlags} />}
|
||||
position={tooltipPositions.LEFT}
|
||||
|
||||
@@ -30,7 +30,9 @@ export const authenticationMethodOptions = [
|
||||
key: 'basic',
|
||||
get value() {
|
||||
return translate('AuthBasic');
|
||||
}
|
||||
},
|
||||
isDisabled: true,
|
||||
isHidden: true
|
||||
},
|
||||
{
|
||||
key: 'forms',
|
||||
|
||||
@@ -100,7 +100,7 @@ function ImportList({
|
||||
<TagList tags={tags} tagList={tagList} />
|
||||
|
||||
<div className={styles.enabled}>
|
||||
<Label kind={kinds.DEFAULT} title="List Refresh Interval">
|
||||
<Label kind={kinds.DEFAULT} title={translate('ListRefreshInterval')}>
|
||||
{`${translate('Refresh')}: ${formatShortTimeSpan(
|
||||
minRefreshInterval
|
||||
)}`}
|
||||
|
||||
@@ -114,6 +114,16 @@ const movieTokens = [
|
||||
example: 'The Movie Collection',
|
||||
footNotes: '1',
|
||||
},
|
||||
{
|
||||
token: '{Movie CollectionThe}',
|
||||
example: "Movie's Collection, The",
|
||||
footNotes: '1',
|
||||
},
|
||||
{
|
||||
token: '{Movie CleanCollectionThe}',
|
||||
example: 'Movies Collection, The',
|
||||
footNotes: '1',
|
||||
},
|
||||
{ token: '{Movie Certification}', example: 'R' },
|
||||
{ token: '{Release Year}', example: '2009' },
|
||||
];
|
||||
|
||||
@@ -54,11 +54,11 @@ function Tag({ id, label }: TagProps) {
|
||||
setIsDeleteTagModalOpen(true);
|
||||
}, []);
|
||||
|
||||
const handleConfirmDeleteTag = useCallback(() => {
|
||||
const handleDeleteTagModalClose = useCallback(() => {
|
||||
setIsDeleteTagModalOpen(false);
|
||||
}, []);
|
||||
|
||||
const handleDeleteTagModalClose = useCallback(() => {
|
||||
const handleConfirmDeleteTag = useCallback(() => {
|
||||
dispatch(deleteTag({ id }));
|
||||
}, [id, dispatch]);
|
||||
|
||||
|
||||
@@ -133,6 +133,12 @@ export const defaultState = {
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'year',
|
||||
label: () => translate('Year'),
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'inCinemas',
|
||||
label: () => translate('InCinemas'),
|
||||
|
||||
@@ -96,14 +96,22 @@ function merge(initialState, persistedState) {
|
||||
return computedState;
|
||||
}
|
||||
|
||||
const KEY = 'radarr';
|
||||
|
||||
const config = {
|
||||
slicer,
|
||||
serialize,
|
||||
merge,
|
||||
key: 'radarr'
|
||||
key: window.Radarr.instanceName.toLowerCase().replace(/ /g, '_') || KEY
|
||||
};
|
||||
|
||||
export default function createPersistState() {
|
||||
// Migrate existing local storage value to new key if it does not already exist.
|
||||
// Leave old value as-is in case there are multiple instances using the same key.
|
||||
if (config.key !== KEY && localStorage.getItem(KEY) && !localStorage.getItem(config.key)) {
|
||||
localStorage.setItem(config.key, localStorage.getItem(KEY));
|
||||
}
|
||||
|
||||
// Migrate existing local storage before proceeding
|
||||
const persistedState = JSON.parse(localStorage.getItem(config.key));
|
||||
migrate(persistedState);
|
||||
|
||||
@@ -23,14 +23,6 @@ function Donations() {
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={styles.logoContainer} title="Readarr">
|
||||
<Link to="https://readarr.com/donate">
|
||||
<img
|
||||
className={styles.logo}
|
||||
src={`${window.Radarr.urlBase}/Content/Images/Icons/logo-readarr.png`}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={styles.logoContainer} title="Prowlarr">
|
||||
<Link to="https://prowlarr.com/donate">
|
||||
<img
|
||||
|
||||
@@ -18,9 +18,19 @@ import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptions
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import usePaging from 'Components/Table/usePaging';
|
||||
import useCurrentPage from 'Helpers/Hooks/useCurrentPage';
|
||||
import usePrevious from 'Helpers/Hooks/usePrevious';
|
||||
import useSelectState from 'Helpers/Hooks/useSelectState';
|
||||
import { align, icons, kinds } from 'Helpers/Props';
|
||||
import Movie from 'Movie/Movie';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import {
|
||||
clearMovieFiles,
|
||||
fetchMovieFiles,
|
||||
} from 'Store/Actions/movieFileActions';
|
||||
import {
|
||||
clearQueueDetails,
|
||||
fetchQueueDetails,
|
||||
} from 'Store/Actions/queueActions';
|
||||
import {
|
||||
batchToggleCutoffUnmetMovies,
|
||||
clearCutoffUnmet,
|
||||
@@ -35,6 +45,8 @@ import { CheckInputChanged } from 'typings/inputs';
|
||||
import { SelectStateInputProps } from 'typings/props';
|
||||
import { TableOptionsChangePayload } from 'typings/Table';
|
||||
import getFilterValue from 'Utilities/Filter/getFilterValue';
|
||||
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
|
||||
import selectUniqueIds from 'Utilities/Object/selectUniqueIds';
|
||||
import {
|
||||
registerPagePopulator,
|
||||
unregisterPagePopulator,
|
||||
@@ -108,6 +120,8 @@ function CutoffUnmet() {
|
||||
const isSearchingForMovies =
|
||||
isSearchingForAllMovies || isSearchingForSelectedMovies;
|
||||
|
||||
const previousItems = usePrevious(items);
|
||||
|
||||
const handleSelectAllChange = useCallback(
|
||||
({ value }: CheckInputChanged) => {
|
||||
setSelectState({ type: value ? 'selectAll' : 'unselectAll', items });
|
||||
@@ -204,6 +218,8 @@ function CutoffUnmet() {
|
||||
|
||||
return () => {
|
||||
dispatch(clearCutoffUnmet());
|
||||
dispatch(clearQueueDetails());
|
||||
dispatch(clearMovieFiles());
|
||||
};
|
||||
}, [requestCurrentPage, dispatch]);
|
||||
|
||||
@@ -223,6 +239,21 @@ function CutoffUnmet() {
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!previousItems || hasDifferentItems(items, previousItems)) {
|
||||
const movieIds = selectUniqueIds<Movie, number>(items, 'id');
|
||||
const movieFileIds = selectUniqueIds<Movie, number>(items, 'movieFileId');
|
||||
|
||||
if (movieIds.length) {
|
||||
dispatch(fetchQueueDetails({ movieIds }));
|
||||
}
|
||||
|
||||
if (movieFileIds.length) {
|
||||
dispatch(fetchMovieFiles({ movieFileIds }));
|
||||
}
|
||||
}
|
||||
}, [items, previousItems, dispatch]);
|
||||
|
||||
return (
|
||||
<PageContent title={translate('CutoffUnmet')}>
|
||||
<PageToolbar>
|
||||
|
||||
@@ -18,10 +18,16 @@ import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptions
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import usePaging from 'Components/Table/usePaging';
|
||||
import useCurrentPage from 'Helpers/Hooks/useCurrentPage';
|
||||
import usePrevious from 'Helpers/Hooks/usePrevious';
|
||||
import useSelectState from 'Helpers/Hooks/useSelectState';
|
||||
import { align, icons, kinds } from 'Helpers/Props';
|
||||
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
||||
import Movie from 'Movie/Movie';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import {
|
||||
clearQueueDetails,
|
||||
fetchQueueDetails,
|
||||
} from 'Store/Actions/queueActions';
|
||||
import {
|
||||
batchToggleMissingMovies,
|
||||
clearMissing,
|
||||
@@ -36,6 +42,8 @@ import { CheckInputChanged } from 'typings/inputs';
|
||||
import { SelectStateInputProps } from 'typings/props';
|
||||
import { TableOptionsChangePayload } from 'typings/Table';
|
||||
import getFilterValue from 'Utilities/Filter/getFilterValue';
|
||||
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
|
||||
import selectUniqueIds from 'Utilities/Object/selectUniqueIds';
|
||||
import {
|
||||
registerPagePopulator,
|
||||
unregisterPagePopulator,
|
||||
@@ -112,6 +120,8 @@ function Missing() {
|
||||
const isSearchingForMovies =
|
||||
isSearchingForAllMovies || isSearchingForSelectedMovies;
|
||||
|
||||
const previousItems = usePrevious(items);
|
||||
|
||||
const handleSelectAllChange = useCallback(
|
||||
({ value }: CheckInputChanged) => {
|
||||
setSelectState({ type: value ? 'selectAll' : 'unselectAll', items });
|
||||
@@ -216,6 +226,7 @@ function Missing() {
|
||||
|
||||
return () => {
|
||||
dispatch(clearMissing());
|
||||
dispatch(clearQueueDetails());
|
||||
};
|
||||
}, [requestCurrentPage, dispatch]);
|
||||
|
||||
@@ -235,6 +246,16 @@ function Missing() {
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!previousItems || hasDifferentItems(items, previousItems)) {
|
||||
const movieIds = selectUniqueIds<Movie, number>(items, 'id');
|
||||
|
||||
if (movieIds.length) {
|
||||
dispatch(fetchQueueDetails({ movieIds }));
|
||||
}
|
||||
}
|
||||
}, [items, previousItems, dispatch]);
|
||||
|
||||
return (
|
||||
<PageContent title={translate('Missing')}>
|
||||
<PageToolbar>
|
||||
|
||||
5
global.json
Normal file
5
global.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "8.0.405"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@
|
||||
"@fortawesome/free-solid-svg-icons": "6.7.2",
|
||||
"@fortawesome/react-fontawesome": "0.2.2",
|
||||
"@juggle/resize-observer": "3.4.0",
|
||||
"@microsoft/signalr": "6.0.25",
|
||||
"@microsoft/signalr": "8.0.7",
|
||||
"@sentry/browser": "7.119.1",
|
||||
"@sentry/integrations": "7.119.1",
|
||||
"@tanstack/react-query": "5.74.3",
|
||||
@@ -131,7 +131,7 @@
|
||||
"html-webpack-plugin": "5.6.0",
|
||||
"loader-utils": "^3.2.1",
|
||||
"mini-css-extract-plugin": "2.9.1",
|
||||
"postcss": "8.4.47",
|
||||
"postcss": "8.5.6",
|
||||
"postcss-color-function": "4.1.0",
|
||||
"postcss-loader": "7.3.0",
|
||||
"postcss-mixins": "9.0.4",
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
|
||||
<Deterministic Condition="$(AssemblyVersion.EndsWith('*'))">False</Deterministic>
|
||||
|
||||
<PathMap>$(MSBuildProjectDirectory)=./$(MSBuildProjectName)/</PathMap>
|
||||
<PathMap>$(MSBuildThisFileDirectory)=./</PathMap>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Set the AssemblyConfiguration attribute for projects -->
|
||||
@@ -99,13 +99,6 @@
|
||||
<RootNamespace Condition="'$(RadarrProject)'=='true'">$(MSBuildProjectName.Replace('Radarr','NzbDrone'))</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TestProject)'!='true'">
|
||||
<!-- Annotates .NET assemblies with repository information including SHA -->
|
||||
<!-- Sentry uses this to link directly to GitHub at the exact version/file/line -->
|
||||
<!-- This is built-in on .NET 8 and can be removed once the project is updated -->
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Sentry specific configuration: Only in Release mode -->
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
||||
<!-- https://docs.sentry.io/platforms/dotnet/configuration/msbuild/ -->
|
||||
@@ -130,14 +123,11 @@
|
||||
|
||||
<!-- Standard testing packages -->
|
||||
<ItemGroup Condition="'$(TestProject)'=='true'">
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||
<PackageReference Include="NunitXml.TestLogger" Version="3.0.131" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TestProject)'=='true' and '$(TargetFramework)'=='net6.0'">
|
||||
<PackageReference Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="5.1.0" />
|
||||
<PackageReference Include="NunitXml.TestLogger" Version="3.1.20" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(RadarrProject)'=='true' and '$(EnableAnalyzers)'=='false'">
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||
<add key="dotnet-bsd-crossbuild" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/dotnet-bsd-crossbuild/nuget/v3/index.json" />
|
||||
<add key="Mono.Posix.NETStandard" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/Mono.Posix.NETStandard/nuget/v3/index.json" />
|
||||
<add key="SQLite" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/SQLite/nuget/v3/index.json" />
|
||||
<add key="coverlet-nightly" value="https://pkgs.dev.azure.com/Servarr/coverlet/_packaging/coverlet-nightly/nuget/v3/index.json" />
|
||||
<add key="FFMpegCore" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/FFMpegCore/nuget/v3/index.json" />
|
||||
<add key="FluentMigrator" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/FluentMigrator/nuget/v3/index.json" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NBuilder" Version="6.1.0" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Selenium.Support" Version="3.141.0" />
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace NzbDrone.Common.Test.EnvironmentInfo
|
||||
[Test]
|
||||
public void should_return_version()
|
||||
{
|
||||
BuildInfo.Version.Major.Should().BeOneOf(5, 10);
|
||||
BuildInfo.Version.Major.Should().BeOneOf(6, 10);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Host\Radarr.Host.csproj" />
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace NzbDrone.Common.Disk
|
||||
{
|
||||
@@ -24,10 +23,5 @@ namespace NzbDrone.Common.Disk
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected DestinationAlreadyExistsException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,17 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
{
|
||||
try
|
||||
{
|
||||
if (OsInfo.IsOsx)
|
||||
{
|
||||
var userAppDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile, Environment.SpecialFolderOption.DoNotVerify), ".config", "Radarr");
|
||||
|
||||
if (_diskProvider.FolderExists(userAppDataFolder) && !_diskProvider.FileExists(_appFolderInfo.GetConfigPath()))
|
||||
{
|
||||
_diskTransferService.MirrorFolder(userAppDataFolder, _appFolderInfo.AppDataFolder);
|
||||
_diskProvider.DeleteFolder(userAppDataFolder, true);
|
||||
}
|
||||
}
|
||||
|
||||
var oldDbFile = Path.Combine(_appFolderInfo.AppDataFolder, "nzbdrone.db");
|
||||
|
||||
if (_startupContext.Args.ContainsKey(StartupContext.APPDATA))
|
||||
@@ -111,7 +122,7 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Debug(ex, ex.Message);
|
||||
throw new RadarrStartupException("Unable to migrate DB from nzbdrone.db to {0}. Migrate manually", _appFolderInfo.GetDatabase());
|
||||
throw new RadarrStartupException(ex, "Unable to migrate DB from nzbdrone.db to {0}. Migrate manually", _appFolderInfo.GetDatabase());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +199,7 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
|
||||
private void RemovePidFile()
|
||||
{
|
||||
if (OsInfo.IsNotWindows)
|
||||
if (OsInfo.IsNotWindows && _diskProvider.FolderExists(_appFolderInfo.AppDataFolder))
|
||||
{
|
||||
_diskProvider.DeleteFile(Path.Combine(_appFolderInfo.AppDataFolder, "radarr.pid"));
|
||||
}
|
||||
|
||||
@@ -6,13 +6,14 @@ using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Common.Http
|
||||
{
|
||||
public class HttpUri : IEquatable<HttpUri>
|
||||
public partial class HttpUri : IEquatable<HttpUri>
|
||||
{
|
||||
private static readonly Regex RegexUri = new Regex(@"^(?:(?<scheme>[a-z]+):)?(?://(?<host>[-_A-Z0-9.]+|\[[[A-F0-9:]+\])(?::(?<port>[0-9]{1,5}))?)?(?<path>(?:(?:(?<=^)|/+)[^/?#\r\n]+)+/*|/+)?(?:\?(?<query>[^#\r\n]*))?(?:\#(?<fragment>.*))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private readonly string _uri;
|
||||
public string FullUri => _uri;
|
||||
|
||||
[GeneratedRegex(@"^(?:(?<scheme>[a-z]+):)?(?://(?<host>[-_A-Z0-9.]+|\[[[A-F0-9:]+\])(?::(?<port>[0-9]{1,5}))?)?(?<path>(?:(?:(?<=^)|/+)[^/?#\r\n]+)+/*|/+)?(?:\?(?<query>[^#\r\n]*))?(?:\#(?<fragment>.*))?$", RegexOptions.IgnoreCase | RegexOptions.Compiled)]
|
||||
private static partial Regex UriRegex();
|
||||
|
||||
public HttpUri(string uri)
|
||||
{
|
||||
_uri = uri ?? string.Empty;
|
||||
@@ -70,9 +71,9 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
private void Parse()
|
||||
{
|
||||
var parseSuccess = Uri.TryCreate(_uri, UriKind.RelativeOrAbsolute, out var uri);
|
||||
var parseSuccess = Uri.TryCreate(_uri, UriKind.RelativeOrAbsolute, out _);
|
||||
|
||||
var match = RegexUri.Match(_uri);
|
||||
var match = UriRegex().Match(_uri);
|
||||
|
||||
var scheme = match.Groups["scheme"];
|
||||
var host = match.Groups["host"];
|
||||
|
||||
@@ -202,6 +202,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
c.ForLogger("Microsoft.*").WriteToNil(LogLevel.Warn);
|
||||
c.ForLogger("Microsoft.Hosting.Lifetime*").WriteToNil(LogLevel.Info);
|
||||
c.ForLogger("Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware").WriteToNil(LogLevel.Fatal);
|
||||
c.ForLogger("Radarr.Http.Authentication.ApiKeyAuthenticationHandler").WriteToNil(LogLevel.Info);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0</TargetFrameworks>
|
||||
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DryIoc.dll" Version="5.4.3" />
|
||||
<PackageReference Include="IPAddressRange" Version="6.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NLog" Version="5.4.0" />
|
||||
<PackageReference Include="NLog.Layouts.ClefJsonLayout" Version="1.0.3" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.4.0" />
|
||||
<PackageReference Include="Npgsql" Version="7.0.10" />
|
||||
<PackageReference Include="Npgsql" Version="9.0.3" />
|
||||
<PackageReference Include="Sentry" Version="4.0.2" />
|
||||
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageReference Include="System.Text.Json" Version="6.0.10" />
|
||||
<PackageReference Include="SourceGear.sqlite3" Version="3.50.4.2" />
|
||||
<PackageReference Include="System.Data.SQLite" Version="2.0.2" />
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.5" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.6.1" />
|
||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.1" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.1" />
|
||||
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
|
||||
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="6.0.1" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="EnsureThat\Resources\ExceptionMessages.Designer.cs">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0</TargetFrameworks>
|
||||
|
||||
<ApplicationIcon>..\NzbDrone.Host\Radarr.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -9,7 +9,7 @@ using NzbDrone.Core.Test.Framework;
|
||||
namespace NzbDrone.Core.Test.Datastore.Migration
|
||||
{
|
||||
[TestFixture]
|
||||
public class collectionsFixture : MigrationTest<collections>
|
||||
public class collectionsFixture : MigrationTest<add_collections>
|
||||
{
|
||||
[Test]
|
||||
public void should_add_collection_from_movie_and_link_back_to_movie()
|
||||
|
||||
@@ -8,6 +8,7 @@ using NzbDrone.Test.Common;
|
||||
namespace NzbDrone.Core.Test.Http
|
||||
{
|
||||
[TestFixture]
|
||||
[Platform(Exclude = "MacOsX")]
|
||||
public class HttpProxySettingsProviderFixture : TestBase<HttpProxySettingsProvider>
|
||||
{
|
||||
private HttpProxySettings GetProxySettings()
|
||||
@@ -15,24 +16,24 @@ namespace NzbDrone.Core.Test.Http
|
||||
return new HttpProxySettings(ProxyType.Socks5, "localhost", 8080, "*.httpbin.org,google.com,172.16.0.0/12", true, null, null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_bypass_proxy()
|
||||
[TestCase("http://eu.httpbin.org/get")]
|
||||
[TestCase("http://google.com/get")]
|
||||
[TestCase("http://localhost:8654/get")]
|
||||
[TestCase("http://172.21.0.1:8989/api/v3/indexer/schema")]
|
||||
public void should_bypass_proxy(string url)
|
||||
{
|
||||
var settings = GetProxySettings();
|
||||
|
||||
Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://eu.httpbin.org/get")).Should().BeTrue();
|
||||
Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://google.com/get")).Should().BeTrue();
|
||||
Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://localhost:8654/get")).Should().BeTrue();
|
||||
Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://172.21.0.1:8989/api/v3/indexer/schema")).Should().BeTrue();
|
||||
Subject.ShouldProxyBeBypassed(settings, new HttpUri(url)).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_bypass_proxy()
|
||||
[TestCase("http://bing.com/get")]
|
||||
[TestCase("http://172.3.0.1:8989/api/v3/indexer/schema")]
|
||||
public void should_not_bypass_proxy(string url)
|
||||
{
|
||||
var settings = GetProxySettings();
|
||||
|
||||
Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://bing.com/get")).Should().BeFalse();
|
||||
Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://172.3.0.1:8989/api/v3/indexer/schema")).Should().BeFalse();
|
||||
Subject.ShouldProxyBeBypassed(settings, new HttpUri(url)).Should().BeFalse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ namespace NzbDrone.Core.Test.Languages
|
||||
new object[] { 53, Language.Tagalog },
|
||||
new object[] { 54, Language.Urdu },
|
||||
new object[] { 55, Language.Romansh },
|
||||
new object[] { 56, Language.Mongolian }
|
||||
new object[] { 56, Language.Mongolian },
|
||||
new object[] { 57, Language.Georgian }
|
||||
};
|
||||
|
||||
public static object[] ToIntCases =
|
||||
@@ -131,7 +132,8 @@ namespace NzbDrone.Core.Test.Languages
|
||||
new object[] { Language.Tagalog, 53 },
|
||||
new object[] { Language.Urdu, 54 },
|
||||
new object[] { Language.Romansh, 55 },
|
||||
new object[] { Language.Mongolian, 56 }
|
||||
new object[] { Language.Mongolian, 56 },
|
||||
new object[] { Language.Georgian, 57 }
|
||||
};
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
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 CleanCollectionTheFixture : CoreTest<FileNameBuilder>
|
||||
{
|
||||
private Movie _movie;
|
||||
private MovieFile _movieFile;
|
||||
private NamingConfig _namingConfig;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_movie = Builder<Movie>
|
||||
.CreateNew()
|
||||
.With(e => e.Title = "Movie Title")
|
||||
.Build();
|
||||
|
||||
_movieFile = new MovieFile { Quality = new QualityModel(Quality.HDTV720p), ReleaseGroup = "RadarrTest" };
|
||||
|
||||
_namingConfig = NamingConfig.Default;
|
||||
_namingConfig.RenameMovies = true;
|
||||
|
||||
Mocker.GetMock<INamingConfigService>()
|
||||
.Setup(c => c.GetConfig()).Returns(_namingConfig);
|
||||
|
||||
Mocker.GetMock<IQualityDefinitionService>()
|
||||
.Setup(v => v.Get(Moq.It.IsAny<Quality>()))
|
||||
.Returns<Quality>(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v));
|
||||
|
||||
Mocker.GetMock<ICustomFormatService>()
|
||||
.Setup(v => v.All())
|
||||
.Returns(new List<CustomFormat>());
|
||||
}
|
||||
|
||||
[TestCase("The Badger's Collection", "Badgers Collection, The")]
|
||||
[TestCase("@ The Movies Collection", "@ The Movies Collection")] // This doesn't seem right; see: FileNameBuilder.ScenifyRemoveChars, looks like it has the "at sign" in the regex
|
||||
[TestCase("A Stupid/Idiotic Collection", "Stupid Idiotic Collection, A")]
|
||||
[TestCase("An Astounding & Amazing Collection", "Astounding and Amazing Collection, An")]
|
||||
[TestCase("The Amazing Animal-Hero's Collection (2001)", "Amazing Animal-Heros Collection, The 2001")]
|
||||
[TestCase("A Different Movië (AU)", "Different Movie, A AU")]
|
||||
[TestCase("The Repairër (ZH) (2015)", "Repairer, The ZH 2015")]
|
||||
[TestCase("The Eighth Sensë 2 (Thai)", "Eighth Sense 2, The Thai")]
|
||||
[TestCase("The Astonishing Jæg (Latin America)", "Astonishing Jaeg, The Latin America")]
|
||||
[TestCase("The Hampster Pack (B&F)", "Hampster Pack, The BandF")]
|
||||
[TestCase("The Gásm: I (Almost) Got Away With It (1900)", "Gasm I Almost Got Away With It, The 1900")]
|
||||
[TestCase(null, "")]
|
||||
public void should_get_expected_title_back(string collection, string expected)
|
||||
{
|
||||
SetCollectionName(_movie, collection);
|
||||
_namingConfig.StandardMovieFormat = "{Movie CleanCollectionThe}";
|
||||
|
||||
Subject.BuildFileName(_movie, _movieFile)
|
||||
.Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("A")]
|
||||
[TestCase("Anne")]
|
||||
[TestCase("Theodore")]
|
||||
[TestCase("3%")]
|
||||
public void should_not_change_title(string collection)
|
||||
{
|
||||
SetCollectionName(_movie, collection);
|
||||
_namingConfig.StandardMovieFormat = "{Movie CleanCollectionThe}";
|
||||
|
||||
Subject.BuildFileName(_movie, _movieFile)
|
||||
.Should().Be(collection);
|
||||
}
|
||||
|
||||
private void SetCollectionName(Movie movie, string collectionName)
|
||||
{
|
||||
var metadata = new MovieMetadata()
|
||||
{
|
||||
CollectionTitle = collectionName,
|
||||
};
|
||||
movie.MovieMetadata = new Core.Datastore.LazyLoaded<MovieMetadata>(metadata);
|
||||
movie.MovieMetadata.Value.CollectionTitle = collectionName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
[TestCase("The Amazing Race (Latin America)", "Amazing Race, The Latin America")]
|
||||
[TestCase("The Rat Pack (A&E)", "Rat Pack, The AandE")]
|
||||
[TestCase("The Climax: I (Almost) Got Away With It (2016)", "Climax I Almost Got Away With It, The 2016")]
|
||||
[TestCase(null, "")]
|
||||
public void should_get_expected_title_back(string title, string expected)
|
||||
{
|
||||
_movie.Title = title;
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
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 CollectionTheFixture : CoreTest<FileNameBuilder>
|
||||
{
|
||||
private Movie _movie;
|
||||
private MovieFile _movieFile;
|
||||
private NamingConfig _namingConfig;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_movie = Builder<Movie>
|
||||
.CreateNew()
|
||||
.With(e => e.Title = "Movie Title")
|
||||
.Build();
|
||||
|
||||
_movieFile = new MovieFile { Quality = new QualityModel(Quality.HDTV720p), ReleaseGroup = "RadarrTest" };
|
||||
|
||||
_namingConfig = NamingConfig.Default;
|
||||
_namingConfig.RenameMovies = true;
|
||||
|
||||
Mocker.GetMock<INamingConfigService>()
|
||||
.Setup(c => c.GetConfig()).Returns(_namingConfig);
|
||||
|
||||
Mocker.GetMock<IQualityDefinitionService>()
|
||||
.Setup(v => v.Get(Moq.It.IsAny<Quality>()))
|
||||
.Returns<Quality>(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v));
|
||||
|
||||
Mocker.GetMock<ICustomFormatService>()
|
||||
.Setup(v => v.All())
|
||||
.Returns(new List<CustomFormat>());
|
||||
}
|
||||
|
||||
[TestCase("The Badger Collection", "Badger Collection, The")]
|
||||
[TestCase("The Mover Collection", "Mover Collection, The")]
|
||||
[TestCase("A Stupid Collection", "Stupid Collection, A")]
|
||||
[TestCase("An Astounding Collection", "Astounding Collection, An")]
|
||||
[TestCase("The Amazing Animal-Hero Collection (2001)", "Amazing Animal-Hero Collection, The (2001)")]
|
||||
[TestCase("A Different Movie (AU)", "Different Movie, A (AU)")]
|
||||
[TestCase("The Repairer (ZH) (2015)", "Repairer, The (ZH) (2015)")]
|
||||
[TestCase("The Eighth Sense 2 (Thai)", "Eighth Sense 2, The (Thai)")]
|
||||
[TestCase("The Astonishing Jog (Latin America)", "Astonishing Jog, The (Latin America)")]
|
||||
[TestCase("The Hampster Pack (B&F)", "Hampster Pack, The (B&F)")]
|
||||
[TestCase("The Gasm: I (Almost) Got Away With It (1900)", "Gasm - I (Almost) Got Away With It, The (1900)")]
|
||||
public void should_get_expected_title_back(string collection, string expected)
|
||||
{
|
||||
SetCollectionName(_movie, collection);
|
||||
_namingConfig.StandardMovieFormat = "{Movie CollectionThe}";
|
||||
|
||||
Subject.BuildFileName(_movie, _movieFile)
|
||||
.Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("A")]
|
||||
[TestCase("Anne")]
|
||||
[TestCase("Theodore")]
|
||||
[TestCase("3%")]
|
||||
public void should_not_change_title(string collection)
|
||||
{
|
||||
SetCollectionName(_movie, collection);
|
||||
_namingConfig.StandardMovieFormat = "{Movie CollectionThe}";
|
||||
|
||||
Subject.BuildFileName(_movie, _movieFile)
|
||||
.Should().Be(collection);
|
||||
}
|
||||
|
||||
private void SetCollectionName(Movie movie, string collectionName)
|
||||
{
|
||||
var metadata = new MovieMetadata()
|
||||
{
|
||||
CollectionTitle = collectionName,
|
||||
};
|
||||
movie.MovieMetadata = new Core.Datastore.LazyLoaded<MovieMetadata>(metadata);
|
||||
movie.MovieMetadata.Value.CollectionTitle = collectionName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -407,6 +407,8 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
[TestCase("nor", "NO")]
|
||||
[TestCase("khk", "MN")]
|
||||
[TestCase("mvf", "MN")]
|
||||
[TestCase("geo", "KA")]
|
||||
[TestCase("kat", "KA")]
|
||||
public void should_format_languagecodes_properly(string language, string code)
|
||||
{
|
||||
_namingConfig.StandardMovieFormat = "{Movie.Title}.{MEDIAINFO.FULL}";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.IO;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Movies;
|
||||
@@ -32,5 +33,30 @@ namespace NzbDrone.Core.Test.OrganizerTests
|
||||
|
||||
Subject.GetMovieFolder(movie).Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("The Y-Women Collection", "The Y-Women 14", 2005, "{Movie CollectionThe}/{Movie TitleThe} ({Release Year})", "Y-Women Collection, The", "Y-Women 14, The (2005)")]
|
||||
[TestCase("A Decade's Worth of Changes", "The First Year", 1980, "{Movie CleanCollectionThe}/{Movie TitleThe} ({Release Year})", "Decades Worth of Changes, A", "First Year, The (1980)")]
|
||||
[TestCase(null, "Just a Movie", 1999, "{Movie Title} ({Release Year})", null, "Just a Movie (1999)")]
|
||||
[TestCase(null, "Collectionless Slop", 1949, "{Movie CollectionThe}/{Movie TitleThe} ({Release Year})", null, "Collectionless Slop (1949)")]
|
||||
public void should_use_movieFolderFormat_and_CollectionFormat_to_build_folder_name(string collectionTitle, string movieTitle, int year, string format, string expectedCollection, string expectedTitle)
|
||||
{
|
||||
_namingConfig.MovieFolderFormat = format;
|
||||
|
||||
var movie = new Movie
|
||||
{
|
||||
MovieMetadata = new MovieMetadata
|
||||
{
|
||||
CollectionTitle = collectionTitle,
|
||||
Title = movieTitle,
|
||||
Year = year,
|
||||
},
|
||||
};
|
||||
|
||||
var result = Subject.GetMovieFolder(movie);
|
||||
var expected = !string.IsNullOrWhiteSpace(expectedCollection)
|
||||
? Path.Combine(expectedCollection, expectedTitle)
|
||||
: expectedTitle;
|
||||
result.Should().Be(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,5 +119,15 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
var result = IsoLanguages.Find(isoCode);
|
||||
result.Language.Should().Be(Language.Bengali);
|
||||
}
|
||||
|
||||
[TestCase("ka")]
|
||||
[TestCase("geo")]
|
||||
[TestCase("kat")]
|
||||
[TestCase("ka-GE")]
|
||||
public void should_return_georgian(string isoCode)
|
||||
{
|
||||
var result = IsoLanguages.Find(isoCode);
|
||||
result.Language.Should().Be(Language.Georgian);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,6 +521,16 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
result.Should().Contain(Language.Mongolian);
|
||||
}
|
||||
|
||||
[TestCase("Movie.Title.1994.Georgian.WEB-DL.h264")]
|
||||
[TestCase("Movie.Title.2016.Geo.WEB-DL.h264")]
|
||||
[TestCase("Movie.Title.2016.KA.WEB-DL.h264")]
|
||||
[TestCase("Movie.Title.2016.RU-KA.WEB-DL.h264")]
|
||||
public void should_parse_language_georgian(string postTitle)
|
||||
{
|
||||
var result = LanguageParser.ParseLanguages(postTitle);
|
||||
result.Should().Contain(Language.Georgian);
|
||||
}
|
||||
|
||||
[TestCase("Movie.Title.en.sub")]
|
||||
[TestCase("Movie Title.eng.sub")]
|
||||
[TestCase("Movie.Title.eng.forced.sub")]
|
||||
|
||||
@@ -294,6 +294,11 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Movie.Name.2016.German.DTS.DL.1080p.UHDBD.x265-TDO.mkv", false)]
|
||||
[TestCase("Movie.Name.2021.1080p.BDLight.x265-AVCDVD", false)]
|
||||
[TestCase("Movie.Title.2012.German.DL.1080p.UHD2BD.x264-QfG", false)]
|
||||
[TestCase("Movie.Title.2005.1080p.HDDVDRip.x264", false)]
|
||||
[TestCase("Movie.Title.2019.German.DL.1080p.HDR.UHDBDRip.AV1-GROUP", false)]
|
||||
[TestCase("Movie.Title.2014.German.OPUS.DL.1080p.UHDBDRiP.HDR.AV1-GROUP", false)]
|
||||
[TestCase("Movie.Title.1999.German.DL.1080p.HDR.UHDBDRip.AV1-GROUP", false)]
|
||||
[TestCase("Movie.Title.1993.Uncut.German.DL.1080p.HDR.UHDBDRip.h265-GROUP", false)]
|
||||
public void should_parse_bluray1080p_quality(string title, bool proper)
|
||||
{
|
||||
ParseAndVerifyQuality(title, QualitySource.BLURAY, proper, Resolution.R1080p);
|
||||
@@ -312,6 +317,8 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Movie.Name.2020.German.UHDBD.2160p.HDR10.HEVC.EAC3.DL-pmHD.mkv", false)]
|
||||
[TestCase("Movie.Title.2014.2160p.UHD.BluRay.X265-IAMABLE.mkv", false)]
|
||||
[TestCase("Movie.Title.2014.2160p.BDRip.AAC.7.1.HDR10.x265.10bit-Markll", false)]
|
||||
[TestCase("Movie.Title.1956.German.DL.2160p.HDR.UHDBDRip.h266-GROUP", false)]
|
||||
[TestCase("Movie.Title.2021.4K.HDR.2160P.UHDBDRip.HEVC-10bit.GROUP", false)]
|
||||
public void should_parse_bluray2160p_quality(string title, bool proper)
|
||||
{
|
||||
ParseAndVerifyQuality(title, QualitySource.BLURAY, proper, Resolution.R2160p);
|
||||
|
||||
@@ -55,14 +55,14 @@ 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")]
|
||||
[TestCase("Movie Name (2017) (Showtime) (1080p.BD.DD5.1.x265-TheSickle[TAoE])", "TheSickle")]
|
||||
[TestCase("Movie Name (2017) (Showtime) (1080p.BD.DD5.1.x265-TheSickle[TAoE])", "TAoE")]
|
||||
public void should_parse_release_group(string title, string expected)
|
||||
{
|
||||
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
|
||||
Parser.ReleaseGroupParser.ParseReleaseGroup(title).Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("Movie Name (2020) [2160p x265 10bit S82 Joy]", "Joy")]
|
||||
[TestCase("Movie Name (2003) (2160p BluRay X265 HEVC 10bit HDR AAC 7.1 Tigole) [QxR]", "Tigole")]
|
||||
[TestCase("Movie Name (2003) (2160p BluRay X265 HEVC 10bit HDR AAC 7.1 Tigole) [QxR]", "QxR")]
|
||||
[TestCase("Ode To Joy (2009) (2160p BluRay x265 10bit HDR Joy)", "Joy")]
|
||||
[TestCase("Movie Name (2001) 1080p NF WEB-DL DDP2.0 x264-E.N.D", "E.N.D")]
|
||||
[TestCase("Movie Name (2020) [1080p] [WEBRip] [5.1] [YTS.MX]", "YTS.MX")]
|
||||
@@ -108,8 +108,11 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("A Movie in the Name (1964) (1080p BluRay x265 r00t)", "r00t")]
|
||||
[TestCase("Movie Title (2022) (2160p ATV WEB-DL Hybrid H265 DV HDR DDP Atmos 5.1 English - HONE)", "HONE")]
|
||||
[TestCase("Movie Title (2009) (2160p PMTP WEB-DL Hybrid H265 DV HDR10+ DDP Atmos 5.1 English - HONE)", "HONE")]
|
||||
[TestCase("Movie Title (2022) (1080p PCOK WEB-DL H265 DV HDR DDP Atmos 5.1 English - GiLG)", "GiLG")]
|
||||
[TestCase("Movie Title (2022) Extended (2160p PCOK WEB-DL H265 DV HDR DDP Atmos 5.1 English - GiLG)", "GiLG")]
|
||||
[TestCase("Why.Cant.You.Use.Normal.Characters.2021.2160p.UHD.HDR10+.BluRay.TrueHD.Atmos.7.1.x265-ZØNEHD", "ZØNEHD")]
|
||||
[TestCase("Movie.Should.Not.Use.Dots.2022.1080p.BluRay.x265.10bit.Tigole", "Tigole")]
|
||||
[TestCase("Movie.Should.Not.Use.Dots.2022.1080p.BluRay.x265.10bit.Tigole)", "Tigole")]
|
||||
[TestCase("Movie.Should.Not.Use.Dots.2022.1080p.BluRay.x265.10bit.Tigole", null)]
|
||||
[TestCase("Movie.Title.2005.2160p.UHD.BluRay.TrueHD 7.1.Atmos.x265 - HQMUX", "HQMUX")]
|
||||
[TestCase("Movie.Name.2022.1080p.BluRay.x264-VARYG (Blue Lock, Multi-Subs)", "VARYG")]
|
||||
[TestCase("Movie Title (2023) (1080p BluRay x265 SDR AAC 2.0 English Vyndros)", "Vyndros")]
|
||||
@@ -117,8 +120,8 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Movie Title (2011) [BluRay] [1080p] [YTS.MX] [YIFY]", "YIFY")]
|
||||
[TestCase("Movie Title (2014) [BluRay] [1080p] [YIFY] [YTS]", "YTS")]
|
||||
[TestCase("Movie Title (2018) [BluRay] [1080p] [YIFY] [YTS.LT]", "YTS.LT")]
|
||||
[TestCase("Movie Title (2016) (1080p AMZN WEB-DL x265 HEVC 10bit EAC3 5 1 RZeroX) QxR", "RZeroX")]
|
||||
[TestCase("Movie Title (2016) (1080p AMZN WEB-DL x265 HEVC 10bit EAC3 5 1 Garshasp) QxR", "Garshasp")]
|
||||
[TestCase("Movie Title (2016) (1080p AMZN WEB-DL x265 HEVC 10bit EAC3 5 1 RZeroX) QxR", "QxR")]
|
||||
[TestCase("Movie Title (2016) (1080p AMZN WEB-DL x265 HEVC 10bit EAC3 5 1 Garshasp) QxR", "QxR")]
|
||||
[TestCase("Movie Title 2024 mUHD 10Bits DoVi HDR10 2160p BluRay DD 5 1 x265 - TMd", "TMd")]
|
||||
[TestCase("Movie Title 2024 mUHD 10Bits DoVi HDR10 2160p BluRay DD 5 1 x265 TMd", "TMd")]
|
||||
[TestCase("Movie Title (2024) 2160p WEB-DL ESP DD+ 5.1 ING DD+ 5.1 Atmos DV HDR H.265-Eml HDTeam", "Eml HDTeam")]
|
||||
@@ -126,15 +129,16 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Movie Title (2022) BDFull 1080p DTS-HD MA 5.1 AVC LMain", "LMain")]
|
||||
[TestCase("Movie Title (2024) (1080p BluRay x265 SDR DDP 5.1 English - DarQ)", "DarQ")]
|
||||
[TestCase("Movie Title (2024) (1080p BluRay x265 SDR DDP 5.1 English -BEN THE MEN", "BEN THE MEN")]
|
||||
[TestCase("Movie Title 2024 2160p WEB-DL DoVi HDR10+ H265 DDP 5.1 Atmos-126811", "126811")]
|
||||
public void should_parse_exception_release_group(string title, string expected)
|
||||
{
|
||||
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
|
||||
Parser.ReleaseGroupParser.ParseReleaseGroup(title).Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase(@"C:\Test\Doctor.Series.2005.s01e01.internal.bdrip.x264-archivist.mkv", "archivist")]
|
||||
public void should_not_include_extension_in_release_group(string title, string expected)
|
||||
{
|
||||
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
|
||||
Parser.ReleaseGroupParser.ParseReleaseGroup(title).Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("Some.Movie.S02E04.720p.WEBRip.x264-SKGTV English", "SKGTV")]
|
||||
@@ -143,7 +147,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
|
||||
public void should_not_include_language_in_release_group(string title, string expected)
|
||||
{
|
||||
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
|
||||
Parser.ReleaseGroupParser.ParseReleaseGroup(title).Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("Some.Movie.2019.1080p.BDRip.X264.AC3-EVO-RP", "EVO")]
|
||||
@@ -173,7 +177,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
|
||||
public void should_not_include_bad_suffix_in_release_group(string title, string expected)
|
||||
{
|
||||
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
|
||||
Parser.ReleaseGroupParser.ParseReleaseGroup(title).Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("[FFF] Invaders of the Movies!! - S01E11 - Someday, With Movies", "FFF")]
|
||||
@@ -184,13 +188,13 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
|
||||
public void should_parse_anime_release_groups(string title, string expected)
|
||||
{
|
||||
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
|
||||
Parser.ReleaseGroupParser.ParseReleaseGroup(title).Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("Terrible.Anime.Title.2020.DBOX.480p.x264-iKaos [v3] [6AFFEF6B]")]
|
||||
public void should_not_parse_anime_hash_as_release_group(string title)
|
||||
{
|
||||
Parser.Parser.ParseReleaseGroup(title).Should().BeNull();
|
||||
Parser.ReleaseGroupParser.ParseReleaseGroup(title).Should().BeNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Movie Title Future 2023 DVDRip XviD RUNNER[www.allstate.net]", null)]
|
||||
public void should_not_parse_url_in_group(string title, string expected)
|
||||
{
|
||||
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
|
||||
Parser.ReleaseGroupParser.ParseReleaseGroup(title).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="2.0.151" />
|
||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
||||
<PackageReference Include="NBuilder" Version="6.1.0" />
|
||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Test.Common\Radarr.Test.Common.csproj" />
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Core.Authentication
|
||||
{
|
||||
public enum AuthenticationType
|
||||
{
|
||||
None = 0,
|
||||
[Obsolete("Use Forms authentication instead")]
|
||||
Basic = 1,
|
||||
Forms = 2,
|
||||
External = 3
|
||||
|
||||
@@ -206,13 +206,24 @@ namespace NzbDrone.Core.Configuration
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
SetValue("AuthenticationMethod", AuthenticationType.Basic);
|
||||
return AuthenticationType.Basic;
|
||||
SetValue("AuthenticationMethod", AuthenticationType.Forms);
|
||||
return AuthenticationType.Forms;
|
||||
}
|
||||
|
||||
return Enum.TryParse<AuthenticationType>(_authOptions.Method, out var enumValue)
|
||||
var value = Enum.TryParse<AuthenticationType>(_authOptions.Method, out var enumValue)
|
||||
? enumValue
|
||||
: GetValueEnum("AuthenticationMethod", AuthenticationType.None);
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
if (value == AuthenticationType.Basic)
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
SetValue("AuthenticationMethod", AuthenticationType.Forms);
|
||||
|
||||
return AuthenticationType.Forms;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,6 +397,12 @@ namespace NzbDrone.Core.Configuration
|
||||
{
|
||||
SetValue("EnableSsl", false);
|
||||
}
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
if (AuthenticationMethod == AuthenticationType.Basic)
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
SetValue("AuthenticationMethod", AuthenticationType.Forms);
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteOldValues()
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.SQLite;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Dapper;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
using NzbDrone.Core.Datastore.Events;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using Polly;
|
||||
using Polly.Retry;
|
||||
|
||||
namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
@@ -40,12 +45,31 @@ namespace NzbDrone.Core.Datastore
|
||||
public class BasicRepository<TModel> : IBasicRepository<TModel>
|
||||
where TModel : ModelBase, new()
|
||||
{
|
||||
private static readonly ILogger Logger = NzbDroneLogger.GetLogger(typeof(BasicRepository<TModel>));
|
||||
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly PropertyInfo _keyProperty;
|
||||
private readonly List<PropertyInfo> _properties;
|
||||
private readonly string _updateSql;
|
||||
private readonly string _insertSql;
|
||||
|
||||
private static ResiliencePipeline RetryStrategy => new ResiliencePipelineBuilder()
|
||||
.AddRetry(new RetryStrategyOptions
|
||||
{
|
||||
ShouldHandle = new PredicateBuilder().Handle<SQLiteException>(ex => ex.ResultCode == SQLiteErrorCode.Busy),
|
||||
Delay = TimeSpan.FromMilliseconds(100),
|
||||
MaxRetryAttempts = 3,
|
||||
BackoffType = DelayBackoffType.Exponential,
|
||||
UseJitter = true,
|
||||
OnRetry = args =>
|
||||
{
|
||||
Logger.Warn(args.Outcome.Exception, "Failed writing to database. Retry #{0}", args.AttemptNumber);
|
||||
|
||||
return default;
|
||||
}
|
||||
})
|
||||
.Build();
|
||||
|
||||
protected readonly IDatabase _database;
|
||||
protected readonly string _table;
|
||||
|
||||
@@ -186,7 +210,9 @@ namespace NzbDrone.Core.Datastore
|
||||
private TModel Insert(IDbConnection connection, IDbTransaction transaction, TModel model)
|
||||
{
|
||||
SqlBuilderExtensions.LogQuery(_insertSql, model);
|
||||
var multi = connection.QueryMultiple(_insertSql, model, transaction);
|
||||
|
||||
var multi = RetryStrategy.Execute(static (state, _) => state.connection.QueryMultiple(state._insertSql, state.model, state.transaction), (connection, _insertSql, model, transaction));
|
||||
|
||||
var multiRead = multi.Read();
|
||||
var id = (int)(multiRead.First().id ?? multiRead.First().Id);
|
||||
_keyProperty.SetValue(model, id);
|
||||
@@ -381,7 +407,7 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
SqlBuilderExtensions.LogQuery(sql, model);
|
||||
|
||||
connection.Execute(sql, model, transaction: transaction);
|
||||
RetryStrategy.Execute(static (state, _) => state.connection.Execute(state.sql, state.model, transaction: state.transaction), (connection, sql, model, transaction));
|
||||
}
|
||||
|
||||
private void UpdateFields(IDbConnection connection, IDbTransaction transaction, IList<TModel> models, List<PropertyInfo> propertiesToUpdate)
|
||||
@@ -393,7 +419,7 @@ namespace NzbDrone.Core.Datastore
|
||||
SqlBuilderExtensions.LogQuery(sql, model);
|
||||
}
|
||||
|
||||
connection.Execute(sql, models, transaction: transaction);
|
||||
RetryStrategy.Execute(static (state, _) => state.connection.Execute(state.sql, state.models, transaction: state.transaction), (connection, sql, models, transaction));
|
||||
}
|
||||
|
||||
protected virtual SqlBuilder PagedBuilder() => Builder();
|
||||
|
||||
@@ -7,7 +7,7 @@ using NzbDrone.Common.Instrumentation;
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Maintenance(MigrationStage.BeforeAll, TransactionBehavior.None)]
|
||||
public class DatabaseEngineVersionCheck : FluentMigrator.Migration
|
||||
public class DatabaseEngineVersionCheck : ForwardOnlyMigration
|
||||
{
|
||||
protected readonly Logger _logger;
|
||||
|
||||
@@ -22,11 +22,6 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
IfDatabase("postgres").Execute.WithConnection(LogPostgresVersion);
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
|
||||
private void LogSqliteVersion(IDbConnection conn, IDbTransaction tran)
|
||||
{
|
||||
using (var versionCmd = conn.CreateCommand())
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
if (!Schema.Table("ImportExclusions").Exists())
|
||||
{
|
||||
Create.TableForModel("ImportExclusions")
|
||||
.WithColumn("TmdbId").AsInt64().NotNullable().Unique().PrimaryKey()
|
||||
.WithColumn("TmdbId").AsInt64().NotNullable().Unique()
|
||||
.WithColumn("MovieTitle").AsString().Nullable()
|
||||
.WithColumn("MovieYear").AsInt64().Nullable().WithDefaultValue(0);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
@@ -809,7 +810,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
|
||||
private static string GetSceneNameMatch(string sceneName, params string[] tokens)
|
||||
{
|
||||
sceneName = sceneName.IsNotNullOrWhiteSpace() ? Parser.Parser.RemoveFileExtension(sceneName) : string.Empty;
|
||||
sceneName = sceneName.IsNotNullOrWhiteSpace() ? FileExtensions.RemoveFileExtension(sceneName) : string.Empty;
|
||||
|
||||
foreach (var token in tokens)
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@ using NzbDrone.Core.Parser;
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(208)]
|
||||
public class collections : NzbDroneMigrationBase
|
||||
public class add_collections : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
|
||||
@@ -6,7 +6,6 @@ using FluentMigrator.Runner.Generators;
|
||||
using FluentMigrator.Runner.Initialization;
|
||||
using FluentMigrator.Runner.Processors;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NLog;
|
||||
using NLog.Extensions.Logging;
|
||||
|
||||
@@ -20,13 +19,10 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||
public class MigrationController : IMigrationController
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
private readonly ILoggerProvider _migrationLoggerProvider;
|
||||
|
||||
public MigrationController(Logger logger,
|
||||
ILoggerProvider migrationLoggerProvider)
|
||||
public MigrationController(Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_migrationLoggerProvider = migrationLoggerProvider;
|
||||
}
|
||||
|
||||
public void Migrate(string connectionString, MigrationContext migrationContext, DatabaseType databaseType)
|
||||
@@ -35,16 +31,13 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||
|
||||
_logger.Info("*** Migrating {0} ***", connectionString);
|
||||
|
||||
ServiceProvider serviceProvider;
|
||||
|
||||
var db = databaseType == DatabaseType.SQLite ? "sqlite" : "postgres";
|
||||
|
||||
serviceProvider = new ServiceCollection()
|
||||
var serviceProvider = new ServiceCollection()
|
||||
.AddLogging(b => b.AddNLog())
|
||||
.AddFluentMigratorCore()
|
||||
.Configure<RunnerOptions>(cfg => cfg.IncludeUntaggedMaintenances = true)
|
||||
.ConfigureRunner(
|
||||
builder => builder
|
||||
.ConfigureRunner(builder => builder
|
||||
.AddPostgres()
|
||||
.AddNzbDroneSQLite()
|
||||
.WithGlobalConnectionString(connectionString)
|
||||
|
||||
@@ -4,9 +4,14 @@ using FluentMigrator.Builders.Create;
|
||||
using FluentMigrator.Builders.Create.Table;
|
||||
using FluentMigrator.Runner;
|
||||
using FluentMigrator.Runner.BatchParser;
|
||||
using FluentMigrator.Runner.Generators;
|
||||
using FluentMigrator.Runner.Generators.SQLite;
|
||||
using FluentMigrator.Runner.Initialization;
|
||||
using FluentMigrator.Runner.Processors;
|
||||
using FluentMigrator.Runner.Processors.SQLite;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||
{
|
||||
@@ -26,23 +31,40 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||
return command;
|
||||
}
|
||||
|
||||
public static void AddParameter(this System.Data.IDbCommand command, object value)
|
||||
public static void AddParameter(this IDbCommand command, object value)
|
||||
{
|
||||
var parameter = command.CreateParameter();
|
||||
parameter.Value = value;
|
||||
command.Parameters.Add(parameter);
|
||||
}
|
||||
|
||||
public static IMigrationRunnerBuilder AddNzbDroneSQLite(this IMigrationRunnerBuilder builder)
|
||||
public static IMigrationRunnerBuilder AddNzbDroneSQLite(this IMigrationRunnerBuilder builder, bool binaryGuid = false, bool useStrictTables = false)
|
||||
{
|
||||
builder.Services
|
||||
.AddTransient<SQLiteBatchParser>()
|
||||
.AddScoped<SQLiteDbFactory>()
|
||||
.AddScoped<NzbDroneSQLiteProcessor>()
|
||||
.AddScoped<NzbDroneSQLiteProcessor>(sp =>
|
||||
{
|
||||
var factory = sp.GetService<SQLiteDbFactory>();
|
||||
var logger = sp.GetService<ILogger<NzbDroneSQLiteProcessor>>();
|
||||
var options = sp.GetService<IOptionsSnapshot<ProcessorOptions>>();
|
||||
var connectionStringAccessor = sp.GetService<IConnectionStringAccessor>();
|
||||
var sqliteQuoter = new SQLiteQuoter(false);
|
||||
return new NzbDroneSQLiteProcessor(factory, sp.GetService<SQLiteGenerator>(), logger, options, connectionStringAccessor, sp, sqliteQuoter);
|
||||
})
|
||||
.AddScoped<ISQLiteTypeMap>(_ => new NzbDroneSQLiteTypeMap(useStrictTables))
|
||||
.AddScoped<IMigrationProcessor>(sp => sp.GetRequiredService<NzbDroneSQLiteProcessor>())
|
||||
.AddScoped<SQLiteQuoter>()
|
||||
.AddScoped<SQLiteGenerator>()
|
||||
.AddScoped(
|
||||
sp =>
|
||||
{
|
||||
var typeMap = sp.GetRequiredService<ISQLiteTypeMap>();
|
||||
return new SQLiteGenerator(
|
||||
new SQLiteQuoter(binaryGuid),
|
||||
typeMap,
|
||||
new OptionsWrapper<GeneratorOptions>(new GeneratorOptions()));
|
||||
})
|
||||
.AddScoped<IMigrationGenerator>(sp => sp.GetRequiredService<SQLiteGenerator>());
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||
{
|
||||
public class NzbDroneSQLiteProcessor : SQLiteProcessor
|
||||
{
|
||||
private readonly SQLiteQuoter _quoter;
|
||||
|
||||
public NzbDroneSQLiteProcessor(SQLiteDbFactory factory,
|
||||
SQLiteGenerator generator,
|
||||
ILogger<NzbDroneSQLiteProcessor> logger,
|
||||
@@ -24,6 +26,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||
SQLiteQuoter quoter)
|
||||
: base(factory, generator, logger, options, connectionStringAccessor, serviceProvider, quoter)
|
||||
{
|
||||
_quoter = quoter;
|
||||
}
|
||||
|
||||
public override void Process(AlterColumnExpression expression)
|
||||
@@ -35,7 +38,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||
|
||||
if (columnIndex == -1)
|
||||
{
|
||||
throw new ApplicationException(string.Format("Column {0} does not exist on table {1}.", expression.Column.Name, expression.TableName));
|
||||
throw new ApplicationException($"Column {expression.Column.Name} does not exist on table {expression.TableName}.");
|
||||
}
|
||||
|
||||
columnDefinitions[columnIndex] = expression.Column;
|
||||
@@ -45,6 +48,28 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||
ProcessAlterTable(tableDefinition);
|
||||
}
|
||||
|
||||
public override void Process(AlterDefaultConstraintExpression expression)
|
||||
{
|
||||
var tableDefinition = GetTableSchema(expression.TableName);
|
||||
|
||||
var columnDefinitions = tableDefinition.Columns.ToList();
|
||||
var columnIndex = columnDefinitions.FindIndex(c => c.Name == expression.ColumnName);
|
||||
|
||||
if (columnIndex == -1)
|
||||
{
|
||||
throw new ApplicationException($"Column {expression.ColumnName} does not exist on table {expression.TableName}.");
|
||||
}
|
||||
|
||||
var changedColumn = columnDefinitions[columnIndex];
|
||||
changedColumn.DefaultValue = expression.DefaultValue;
|
||||
|
||||
columnDefinitions[columnIndex] = changedColumn;
|
||||
|
||||
tableDefinition.Columns = columnDefinitions;
|
||||
|
||||
ProcessAlterTable(tableDefinition);
|
||||
}
|
||||
|
||||
public override void Process(DeleteColumnExpression expression)
|
||||
{
|
||||
var tableDefinition = GetTableSchema(expression.TableName);
|
||||
@@ -62,7 +87,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||
|
||||
if (columnsToRemove.Any())
|
||||
{
|
||||
throw new ApplicationException(string.Format("Column {0} does not exist on table {1}.", columnsToRemove.First(), expression.TableName));
|
||||
throw new ApplicationException($"Column {columnsToRemove.First()} does not exist on table {expression.TableName}.");
|
||||
}
|
||||
|
||||
ProcessAlterTable(tableDefinition);
|
||||
@@ -78,12 +103,12 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||
|
||||
if (columnIndex == -1)
|
||||
{
|
||||
throw new ApplicationException(string.Format("Column {0} does not exist on table {1}.", expression.OldName, expression.TableName));
|
||||
throw new ApplicationException($"Column {expression.OldName} does not exist on table {expression.TableName}.");
|
||||
}
|
||||
|
||||
if (columnDefinitions.Any(c => c.Name == expression.NewName))
|
||||
{
|
||||
throw new ApplicationException(string.Format("Column {0} already exists on table {1}.", expression.NewName, expression.TableName));
|
||||
throw new ApplicationException($"Column {expression.NewName} already exists on table {expression.TableName}.");
|
||||
}
|
||||
|
||||
oldColumnDefinitions[columnIndex] = (ColumnDefinition)columnDefinitions[columnIndex].Clone();
|
||||
@@ -128,21 +153,20 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||
}
|
||||
|
||||
// What is the cleanest way to do this? Add function to Generator?
|
||||
var quoter = new SQLiteQuoter();
|
||||
var columnsToInsert = string.Join(", ", tableDefinition.Columns.Select(c => quoter.QuoteColumnName(c.Name)));
|
||||
var columnsToFetch = string.Join(", ", (oldColumnDefinitions ?? tableDefinition.Columns).Select(c => quoter.QuoteColumnName(c.Name)));
|
||||
var columnsToInsert = string.Join(", ", tableDefinition.Columns.Select(c => _quoter.QuoteColumnName(c.Name)));
|
||||
var columnsToFetch = string.Join(", ", (oldColumnDefinitions ?? tableDefinition.Columns).Select(c => _quoter.QuoteColumnName(c.Name)));
|
||||
|
||||
Process(new CreateTableExpression() { TableName = tempTableName, Columns = tableDefinition.Columns.ToList() });
|
||||
Process(new CreateTableExpression { TableName = tempTableName, Columns = tableDefinition.Columns.ToList() });
|
||||
|
||||
Process(string.Format("INSERT INTO {0} ({1}) SELECT {2} FROM {3}", quoter.QuoteTableName(tempTableName), columnsToInsert, columnsToFetch, quoter.QuoteTableName(tableName)));
|
||||
Process($"INSERT INTO {_quoter.QuoteTableName(tempTableName)} ({columnsToInsert}) SELECT {columnsToFetch} FROM {_quoter.QuoteTableName(tableName)}");
|
||||
|
||||
Process(new DeleteTableExpression() { TableName = tableName });
|
||||
Process(new DeleteTableExpression { TableName = tableName });
|
||||
|
||||
Process(new RenameTableExpression() { OldName = tempTableName, NewName = tableName });
|
||||
Process(new RenameTableExpression { OldName = tempTableName, NewName = tableName });
|
||||
|
||||
foreach (var index in tableDefinition.Indexes)
|
||||
{
|
||||
Process(new CreateIndexExpression() { Index = index });
|
||||
Process(new CreateIndexExpression { Index = index });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
using System.Data;
|
||||
using FluentMigrator.Runner.Generators.Base;
|
||||
using FluentMigrator.Runner.Generators.SQLite;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
// Based on https://github.com/fluentmigrator/fluentmigrator/blob/v6.2.0/src/FluentMigrator.Runner.SQLite/Generators/SQLite/SQLiteTypeMap.cs
|
||||
public sealed class NzbDroneSQLiteTypeMap : TypeMapBase, ISQLiteTypeMap
|
||||
{
|
||||
public bool UseStrictTables { get; }
|
||||
|
||||
public NzbDroneSQLiteTypeMap(bool useStrictTables = false)
|
||||
{
|
||||
UseStrictTables = useStrictTables;
|
||||
|
||||
SetupTypeMaps();
|
||||
}
|
||||
|
||||
// Must be kept in sync with upstream
|
||||
protected override void SetupTypeMaps()
|
||||
{
|
||||
SetTypeMap(DbType.Binary, "BLOB");
|
||||
SetTypeMap(DbType.Byte, "INTEGER");
|
||||
SetTypeMap(DbType.Int16, "INTEGER");
|
||||
SetTypeMap(DbType.Int32, "INTEGER");
|
||||
SetTypeMap(DbType.Int64, "INTEGER");
|
||||
SetTypeMap(DbType.SByte, "INTEGER");
|
||||
SetTypeMap(DbType.UInt16, "INTEGER");
|
||||
SetTypeMap(DbType.UInt32, "INTEGER");
|
||||
SetTypeMap(DbType.UInt64, "INTEGER");
|
||||
|
||||
if (!UseStrictTables)
|
||||
{
|
||||
SetTypeMap(DbType.Currency, "NUMERIC");
|
||||
SetTypeMap(DbType.Decimal, "NUMERIC");
|
||||
SetTypeMap(DbType.Double, "NUMERIC");
|
||||
SetTypeMap(DbType.Single, "NUMERIC");
|
||||
SetTypeMap(DbType.VarNumeric, "NUMERIC");
|
||||
SetTypeMap(DbType.Date, "DATETIME");
|
||||
SetTypeMap(DbType.DateTime, "DATETIME");
|
||||
SetTypeMap(DbType.DateTime2, "DATETIME");
|
||||
SetTypeMap(DbType.Time, "DATETIME");
|
||||
SetTypeMap(DbType.Guid, "UNIQUEIDENTIFIER");
|
||||
|
||||
// Custom so that we can use DateTimeOffset in Postgres for appropriate DB typing
|
||||
SetTypeMap(DbType.DateTimeOffset, "DATETIME");
|
||||
}
|
||||
else
|
||||
{
|
||||
SetTypeMap(DbType.Currency, "TEXT");
|
||||
SetTypeMap(DbType.Decimal, "TEXT");
|
||||
SetTypeMap(DbType.Double, "REAL");
|
||||
SetTypeMap(DbType.Single, "REAL");
|
||||
SetTypeMap(DbType.VarNumeric, "TEXT");
|
||||
SetTypeMap(DbType.Date, "TEXT");
|
||||
SetTypeMap(DbType.DateTime, "TEXT");
|
||||
SetTypeMap(DbType.DateTime2, "TEXT");
|
||||
SetTypeMap(DbType.Time, "TEXT");
|
||||
SetTypeMap(DbType.Guid, "TEXT");
|
||||
|
||||
// Custom so that we can use DateTimeOffset in Postgres for appropriate DB typing
|
||||
SetTypeMap(DbType.DateTimeOffset, "TEXT");
|
||||
}
|
||||
|
||||
SetTypeMap(DbType.AnsiString, "TEXT");
|
||||
SetTypeMap(DbType.String, "TEXT");
|
||||
SetTypeMap(DbType.AnsiStringFixedLength, "TEXT");
|
||||
SetTypeMap(DbType.StringFixedLength, "TEXT");
|
||||
SetTypeMap(DbType.Boolean, "INTEGER");
|
||||
}
|
||||
|
||||
public override string GetTypeMap(DbType type, int? size, int? precision)
|
||||
{
|
||||
return base.GetTypeMap(type, size: null, precision: null);
|
||||
}
|
||||
}
|
||||
@@ -85,7 +85,7 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
|
||||
if (remoteMovie.Movie == null)
|
||||
{
|
||||
decision = new DownloadDecision(remoteMovie, new DownloadRejection(DownloadRejectionReason.UnknownMovie, "Unknown Movie. Unable to identify correct movie using release name."));
|
||||
decision = new DownloadDecision(remoteMovie, new DownloadRejection(DownloadRejectionReason.UnknownMovie, pushedRelease ? "Unknown Movie. Unable to match to existing movie in Library using release title." : "Unknown Movie. Unable to match to correct movie using release title."));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -424,8 +424,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
_logger.Debug("qbitTorrent authentication failed.");
|
||||
if (ex.Response.StatusCode == HttpStatusCode.Forbidden)
|
||||
_logger.Debug(ex, "qbitTorrent authentication failed.");
|
||||
if (ex.Response.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden)
|
||||
{
|
||||
throw new DownloadClientAuthenticationException("Failed to authenticate with qBittorrent.", ex);
|
||||
}
|
||||
@@ -437,7 +437,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
throw new DownloadClientUnavailableException("Failed to connect to qBittorrent, please check your settings.", ex);
|
||||
}
|
||||
|
||||
if (response.Content != "Ok.")
|
||||
if (response.Content.IsNotNullOrWhiteSpace() && response.Content != "Ok.")
|
||||
{
|
||||
// returns "Fails." on bad login
|
||||
_logger.Debug("qbitTorrent authentication failed.");
|
||||
|
||||
@@ -221,7 +221,7 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
try
|
||||
{
|
||||
hash = MagnetLink.Parse(magnetUrl).InfoHash.ToHex();
|
||||
hash = MagnetLink.Parse(magnetUrl).InfoHashes.V1OrV2.ToHex();
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
|
||||
@@ -280,6 +280,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
|
||||
{
|
||||
var setElement = new XElement("set");
|
||||
|
||||
setElement.SetAttributeValue("tmdbcolid", movie.MovieMetadata.Value.CollectionTmdbId);
|
||||
setElement.Add(new XElement("name", movie.MovieMetadata.Value.CollectionTitle));
|
||||
setElement.Add(new XElement("overview"));
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
return new HealthCheck(
|
||||
GetType(),
|
||||
HealthCheckResult.Warning,
|
||||
HealthCheckResult.Error,
|
||||
_localizationService.GetLocalizedString(
|
||||
"NamingConfigMovieFolderFormatDeprecatedHealthCheckMessage", new Dictionary<string, object>
|
||||
{
|
||||
|
||||
@@ -30,6 +30,8 @@ namespace NzbDrone.Core.ImportLists
|
||||
public virtual int PageSize => 0;
|
||||
public virtual TimeSpan RateLimit => TimeSpan.FromSeconds(2);
|
||||
|
||||
protected virtual bool UsePreGeneratedPages => false;
|
||||
|
||||
public abstract IImportListRequestGenerator GetRequestGenerator();
|
||||
public abstract IParseImportListResponse GetParser();
|
||||
|
||||
@@ -79,7 +81,7 @@ namespace NzbDrone.Core.ImportLists
|
||||
break;
|
||||
}
|
||||
|
||||
if (!IsFullPage(page))
|
||||
if (!UsePreGeneratedPages && !IsFullPage(page))
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -210,7 +212,26 @@ namespace NzbDrone.Core.ImportLists
|
||||
{
|
||||
var parser = GetParser();
|
||||
var generator = GetRequestGenerator();
|
||||
var releases = FetchPage(generator.GetMovies().GetAllTiers().First().First(), parser);
|
||||
var pageableRequests = generator.GetMovies();
|
||||
|
||||
var allTiers = pageableRequests.GetAllTiers();
|
||||
if (!allTiers.Any())
|
||||
{
|
||||
return new NzbDroneValidationFailure(string.Empty,
|
||||
"No pages were returned from your import list, please check your settings and the log for details.")
|
||||
{ IsWarning = true };
|
||||
}
|
||||
|
||||
var firstTier = allTiers.First();
|
||||
if (!firstTier.Any())
|
||||
{
|
||||
return new NzbDroneValidationFailure(string.Empty,
|
||||
"No data could be retrieved from your import list, please check your settings.")
|
||||
{ IsWarning = true };
|
||||
}
|
||||
|
||||
var firstRequest = firstTier.First();
|
||||
var releases = FetchPage(firstRequest, parser);
|
||||
|
||||
if (releases.Empty())
|
||||
{
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace NzbDrone.Core.ImportLists.RadarrList2.IMDbList
|
||||
|
||||
public override IParseImportListResponse GetParser()
|
||||
{
|
||||
return new IMDbListParser(Settings);
|
||||
return new IMDbListParser(Settings, _logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.ImportLists.ImportListMovies;
|
||||
using NzbDrone.Core.MetadataSource.SkyHook.Resource;
|
||||
@@ -11,10 +12,12 @@ namespace NzbDrone.Core.ImportLists.RadarrList2.IMDbList
|
||||
public class IMDbListParser : RadarrList2Parser
|
||||
{
|
||||
private readonly IMDbListSettings _settings;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public IMDbListParser(IMDbListSettings settings)
|
||||
public IMDbListParser(IMDbListSettings settings, Logger logger)
|
||||
{
|
||||
_settings = settings;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override IList<ImportListMovie> ParseResponse(ImportListResponse importListResponse)
|
||||
@@ -25,6 +28,7 @@ namespace NzbDrone.Core.ImportLists.RadarrList2.IMDbList
|
||||
|
||||
if (!PreProcess(importResponse))
|
||||
{
|
||||
_logger.Debug("IMDb List {0}: Found {1} movies", _settings.ListId, movies.Count);
|
||||
return movies;
|
||||
}
|
||||
|
||||
@@ -34,20 +38,19 @@ namespace NzbDrone.Core.ImportLists.RadarrList2.IMDbList
|
||||
var rows = importResponse.Content.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
movies = rows.Skip(1).SelectList(m => m.Split(',')).Where(m => m.Length > 5).SelectList(i => new ImportListMovie { ImdbId = i[1], Title = i[5] });
|
||||
|
||||
return movies;
|
||||
}
|
||||
else
|
||||
{
|
||||
var jsonResponse = JsonConvert.DeserializeObject<List<MovieResource>>(importResponse.Content);
|
||||
|
||||
if (jsonResponse == null)
|
||||
if (jsonResponse != null)
|
||||
{
|
||||
return movies;
|
||||
movies = jsonResponse.SelectList(m => new ImportListMovie { TmdbId = m.TmdbId });
|
||||
}
|
||||
|
||||
return jsonResponse.SelectList(m => new ImportListMovie { TmdbId = m.TmdbId });
|
||||
}
|
||||
|
||||
_logger.Debug("IMDb List {0}: Found {1} movies", _settings.ListId, movies.Count);
|
||||
return movies;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,16 +9,22 @@ namespace NzbDrone.Core.ImportLists.RadarrList2.IMDbList
|
||||
|
||||
protected override HttpRequest GetHttpRequest()
|
||||
{
|
||||
Logger.Info("IMDb List {0}: Importing movies", Settings.ListId);
|
||||
|
||||
// Use IMDb list Export for user lists to bypass RadarrAPI caching
|
||||
if (Settings.ListId.StartsWith("ls", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new Exception("IMDb lists of the form 'ls12345678' are no longer supported. Feel free to remove this list after you review your Clean Library Level.");
|
||||
}
|
||||
|
||||
return RequestBuilder.Create()
|
||||
var request = RequestBuilder.Create()
|
||||
.SetSegment("route", $"list/imdb/{Settings.ListId}")
|
||||
.Accept(HttpAccept.Json)
|
||||
.Build();
|
||||
|
||||
Logger.Trace("IMDb List {0}: Request URL: {1}", Settings.ListId, request.Url);
|
||||
|
||||
return request;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.ImportLists.TMDb.List
|
||||
|
||||
private IEnumerable<ImportListRequest> GetMoviesRequest()
|
||||
{
|
||||
Logger.Info("Importing TMDb movies from list: {0}", Settings.ListId);
|
||||
Logger.Info("TMDb List {0}: Importing movies", Settings.ListId);
|
||||
|
||||
var requestBuilder = RequestBuilder.Create()
|
||||
.SetSegment("api", "4")
|
||||
@@ -32,19 +32,25 @@ namespace NzbDrone.Core.ImportLists.TMDb.List
|
||||
.SetSegment("id", Settings.ListId)
|
||||
.SetSegment("secondaryRoute", "");
|
||||
|
||||
Logger.Debug("Getting total pages that TMDb List: {0} consists of", Settings.ListId);
|
||||
Logger.Trace("TMDb List {0}: Getting total pages", Settings.ListId);
|
||||
|
||||
var jsonResponse = JsonConvert.DeserializeObject<MovieSearchResource>(HttpClient.Execute(requestBuilder.Build()).Content);
|
||||
|
||||
MaxPages = jsonResponse.TotalPages;
|
||||
|
||||
if (jsonResponse.TotalPages > 1)
|
||||
{
|
||||
Logger.Debug("TMDb List {0}: processing {1} pages", Settings.ListId, MaxPages);
|
||||
}
|
||||
|
||||
for (var pageNumber = 1; pageNumber <= MaxPages; pageNumber++)
|
||||
{
|
||||
requestBuilder.AddQueryParam("page", pageNumber, true);
|
||||
|
||||
var request = requestBuilder.Build();
|
||||
|
||||
Logger.Debug("Importing TMDb movies from: {0}", request.Url);
|
||||
Logger.Debug("TMDb List {0}: Processing page {1} of {2}", Settings.ListId, pageNumber, MaxPages);
|
||||
Logger.Trace("TMDb List {0}: Request URL: {1}", Settings.ListId, request.Url);
|
||||
|
||||
yield return new ImportListRequest(request);
|
||||
}
|
||||
|
||||
@@ -119,7 +119,8 @@ namespace NzbDrone.Core.ImportLists.TMDb.Popular
|
||||
|
||||
var request = requestBuilder.Build();
|
||||
|
||||
Logger.Debug("Importing TMDb movies from: {0}", request.Url);
|
||||
Logger.Debug("TMDb Popular: Processing page {0} of {1}", pageNumber, MaxPages);
|
||||
Logger.Trace("TMDb Popular: Request URL: {0}", request.Url);
|
||||
|
||||
yield return new ImportListRequest(request);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace NzbDrone.Core.ImportLists.TMDb
|
||||
public override ImportListType ListType => ImportListType.TMDB;
|
||||
public override TimeSpan MinRefreshInterval => TimeSpan.FromHours(12);
|
||||
public override int PageSize => 20;
|
||||
protected override bool UsePreGeneratedPages => true;
|
||||
|
||||
public readonly ISearchForNewMovie _skyhookProxy;
|
||||
public readonly IHttpRequestBuilderFactory _requestBuilder;
|
||||
|
||||
@@ -75,6 +75,8 @@ namespace NzbDrone.Core.ImportLists.TMDb
|
||||
[FieldOption(Hint = "Raeto-Romance")]
|
||||
rm,
|
||||
[FieldOption(Hint = "Mongolian")]
|
||||
mn
|
||||
mn,
|
||||
[FieldOption(Hint = "Georgian")]
|
||||
ka
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,17 +52,25 @@ namespace NzbDrone.Core.ImportLists.TMDb.User
|
||||
|
||||
requestBuilder.Method = HttpMethod.Get;
|
||||
|
||||
Logger.Trace("TMDb User {0}: Getting total pages", (TMDbUserListType)Settings.ListType);
|
||||
|
||||
var jsonResponse = JsonConvert.DeserializeObject<MovieSearchResource>(HttpClient.Execute(requestBuilder.Build()).Content);
|
||||
|
||||
MaxPages = jsonResponse.TotalPages;
|
||||
|
||||
if (jsonResponse.TotalPages > 1)
|
||||
{
|
||||
Logger.Debug("TMDb User {0}: processing {1} pages", (TMDbUserListType)Settings.ListType, MaxPages);
|
||||
}
|
||||
|
||||
for (var pageNumber = 1; pageNumber <= MaxPages; pageNumber++)
|
||||
{
|
||||
requestBuilder.AddQueryParam("page", pageNumber, true);
|
||||
|
||||
var request = requestBuilder.Build();
|
||||
|
||||
Logger.Debug("Importing TMDb movies from: {0}", request.Url);
|
||||
Logger.Debug("TMDb User {0}: Processing page {1} of {2}", (TMDbUserListType)Settings.ListType, pageNumber, MaxPages);
|
||||
Logger.Trace("TMDb User {0}: Request URL: {1}", (TMDbUserListType)Settings.ListType, request.Url);
|
||||
|
||||
yield return new ImportListRequest(request);
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
try
|
||||
{
|
||||
return MagnetLink.Parse(magnetUrl).InfoHash.ToHex();
|
||||
return MagnetLink.Parse(magnetUrl).InfoHashes.V1OrV2.ToHex();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -127,6 +127,7 @@ namespace NzbDrone.Core.Languages
|
||||
public static Language Urdu => new Language(54, "Urdu");
|
||||
public static Language Romansh => new Language(55, "Romansh");
|
||||
public static Language Mongolian => new Language(56, "Mongolian");
|
||||
public static Language Georgian => new Language(57, "Georgian");
|
||||
public static Language Any => new Language(-1, "Any");
|
||||
public static Language Original => new Language(-2, "Original");
|
||||
|
||||
@@ -193,6 +194,7 @@ namespace NzbDrone.Core.Languages
|
||||
Urdu,
|
||||
Romansh,
|
||||
Mongolian,
|
||||
Georgian,
|
||||
Any,
|
||||
Original
|
||||
};
|
||||
|
||||
@@ -1087,5 +1087,76 @@
|
||||
"ImportListsTraktSettingsCertification": "شهادة",
|
||||
"ImportListsTraktSettingsGenres": "الأنواع",
|
||||
"ImportListsTraktSettingsRating": "التقييمات",
|
||||
"IndexerHDBitsSettingsMediums": "متوسط"
|
||||
"IndexerHDBitsSettingsMediums": "متوسط",
|
||||
"AutoTaggingSpecificationTag": "العلامات",
|
||||
"ReleaseProfile": "تحرير ملف تعريف التأخير",
|
||||
"ShowPhysicalRelease": "تاريخ الإصدار المادي",
|
||||
"ManageFormats": "تنسيقات مخصصة",
|
||||
"NotificationsPushcutSettingsNotificationName": "شهادة",
|
||||
"AuthenticationMethod": "المصادقة",
|
||||
"OneMinute": "الدقائق",
|
||||
"PendingDownloadClientUnavailable": "عميل التنزيل غير متوفر",
|
||||
"RemoveQueueItemsRemovalMethodHelpTextWarning": "ستؤدي الإزالة إلى إزالة التنزيل والملف (الملفات) من عميل التنزيل.",
|
||||
"ResetDefinitions": "تعريفات الجودة",
|
||||
"ShowCinemaRelease": "عرض تاريخ الإصدار السينمائي",
|
||||
"Destination": "مسار الوجهة",
|
||||
"FormatRuntimeMinutes": "الدقائق",
|
||||
"SetIndexerFlags": "أعلام المفهرس",
|
||||
"ImportListsTraktSettingsYears": "عام",
|
||||
"ResetQualityDefinitions": "تعريفات الجودة",
|
||||
"AddCustomFilter": "مرشحات مخصصة",
|
||||
"DeleteMovieFolderCountConfirmation": "هل أنت متأكد أنك تريد حذف ملفات الأفلام المحددة؟",
|
||||
"DownloadClientFloodSettingsStartOnAdd": "ابحث في Add",
|
||||
"ShowDigitalRelease": "عرض تاريخ الإصدار السينمائي",
|
||||
"LanguagesLoadError": "تعذر تحميل العلامات",
|
||||
"ReleaseProfiles": "تحرير ملف تعريف التأخير",
|
||||
"NotificationsEmbySettingsSendNotifications": "شهادة",
|
||||
"ResetTitles": "عنوان الإصدار",
|
||||
"SelectIndexerFlags": "حذف المفهرس",
|
||||
"SkipRedownload": "إعادة التنزيل",
|
||||
"Trending": "قيد الانتظار",
|
||||
"ImportListMultipleMissingRoots": "عدة مجلدات جذر مفقودة: {0}",
|
||||
"AddConnection": "الإتصال",
|
||||
"AddImportList": "القوائم",
|
||||
"AutoTaggingSpecificationGenre": "الأنواع",
|
||||
"CustomFormatsSpecificationMinimumSize": "أكبر مقاس",
|
||||
"DeleteAutoTag": "حذف العلامة",
|
||||
"DeleteSelectedIndexersMessageText": "هل أنت متأكد أنك تريد حذف ملفات الأفلام المحددة؟",
|
||||
"Donate": "تاريخ",
|
||||
"DownloadClientSettingsOlderPriority": "أولوية المفهرس",
|
||||
"EditCollection": "مجموعة",
|
||||
"EditSelectedMovies": "حذف ملفات الأفلام المحددة",
|
||||
"MassSearchCancelWarning": "لا يمكن إلغاء هذا بمجرد البدء دون إعادة تشغيل {appName}.",
|
||||
"AutomaticAdd": "تلقائي",
|
||||
"RemoveSelectedItem": "ازل المحدد",
|
||||
"RemoveSelectedItems": "ازل المحدد",
|
||||
"RootFolderPath": "المجلد الرئيسي",
|
||||
"UnableToLoadCollections": "تعذر تحميل القيود",
|
||||
"EditAutoTag": "أضف كلمات دلالية تلقائيا",
|
||||
"RemoveQueueItemRemovalMethodHelpTextWarning": "ستؤدي الإزالة إلى إزالة التنزيل والملف (الملفات) من عميل التنزيل.",
|
||||
"SetReleaseGroup": "مجموعة الإصدار",
|
||||
"AutoTaggingSpecificationStudio": "ستوديو",
|
||||
"DeleteSelected": "حذف ملفات الأفلام المحددة",
|
||||
"DeleteSelectedCustomFormatsMessageText": "هل أنت متأكد أنك تريد حذف ملفات الأفلام المحددة؟",
|
||||
"DeleteSelectedImportListsMessageText": "هل أنت متأكد أنك تريد حذف ملفات الأفلام المحددة؟",
|
||||
"EditSelectedDownloadClients": "حذف Download Client",
|
||||
"ManageCustomFormats": "تنسيقات مخصصة",
|
||||
"ManageDownloadClients": "تحميل العملاء",
|
||||
"MinimumCustomFormatScoreIncrement": "الحد الأدنى من نقاط التنسيق المخصص",
|
||||
"NotificationsKodiSettingsCleanLibrary": "مستوى المكتبة النظيف",
|
||||
"EditSelectedIndexers": "حذف المفهرس",
|
||||
"AudioLanguages": "متعدد اللغات",
|
||||
"OnFileUpgrade": "عند الترقية",
|
||||
"ReleaseHash": "تواريخ الإصدار",
|
||||
"NotificationsKodiSettingsGuiNotification": "شهادة",
|
||||
"SearchForAllMissingMovies": "ابدأ البحث عن فيلم مفقود",
|
||||
"ClearBlocklistMessageText": "هل أنت متأكد أنك تريد إزالة العناصر المحددة من القائمة السوداء؟",
|
||||
"FavoriteFolderRemove": "قم بإزالة المجلد الجذر",
|
||||
"CustomFormatJson": "تنسيق مخصص",
|
||||
"DeleteMovieFolderCountWithFilesConfirmation": "هل أنت متأكد أنك تريد حذف ملفات الأفلام المحددة؟",
|
||||
"DeleteSelectedDownloadClientsMessageText": "هل أنت متأكد أنك تريد حذف ملفات الأفلام المحددة؟",
|
||||
"SelectReleaseGroup": "مجموعة الإصدار",
|
||||
"MovieCollectionFolderMultipleMissingRootsHealthCheckMessage": "عدة مجلدات جذر مفقودة: {0}",
|
||||
"ReleasePush": "تواريخ الإصدار",
|
||||
"CinemaRelease": "عرض تاريخ الإصدار السينمائي"
|
||||
}
|
||||
|
||||
@@ -1204,5 +1204,92 @@
|
||||
"IndexerHDBitsSettingsCategories": "Категории",
|
||||
"IndexerHDBitsSettingsMediums": "Среден",
|
||||
"IndexerSettingsCategories": "Категории",
|
||||
"ReleaseProfile": "Профил за издания"
|
||||
"ReleaseProfile": "Профил за издания",
|
||||
"CinemaRelease": "Пуснат по кината",
|
||||
"BlocklistedAt": "Блокиран на {date}",
|
||||
"Complete": "Завърши",
|
||||
"DeleteSelected": "Изтрийте избраните",
|
||||
"CollectionShowDetailsHelpText": "Покажи статуса и свойствата на колекцията",
|
||||
"AutoTaggingSpecificationStudio": "Студио(я)",
|
||||
"Completed": "Завършено",
|
||||
"DelayMinutes": "{delay} Минути",
|
||||
"Category": "Категория",
|
||||
"AutoTaggingSpecificationKeyword": "Ключова(и) дума(и)",
|
||||
"ChangeCategory": "Промени категорията",
|
||||
"DefaultNotFoundMessage": "Трябва да сте се изгубили, няма какво да видите тук.",
|
||||
"ClearBlocklist": "Изчисти списъка с блокирани",
|
||||
"CountMissingMoviesFromLibrary": "Липсващи филми от библеотеката : {count}",
|
||||
"EditSelectedMovies": "Изтриване на избрани филмови файлове",
|
||||
"EditAutoTag": "Добави автоматичен таг",
|
||||
"EditConnectionImplementation": "Добави условие - {implementationName}",
|
||||
"EditImportListImplementation": "Добави списък за импортиране - {implementationName}",
|
||||
"EditIndexerImplementation": "Добави индексатор - {implementationName}",
|
||||
"ReleaseProfiles": "Профил за издания",
|
||||
"SearchForAllMissingMovies": "Започнете търсене на липсващ филм",
|
||||
"ImportListMultipleMissingRoots": "Липсват множество коренни папки: {0}",
|
||||
"NotificationsKodiSettingsGuiNotification": "Сертифициране",
|
||||
"NotificationsPushcutSettingsNotificationName": "Сертифициране",
|
||||
"IndexerHDBitsSettingsCodecs": "Кодек",
|
||||
"ManageFormats": "Клониране на персонализиран формат",
|
||||
"MassSearchCancelWarning": "Това не може да бъде отменено след стартиране без рестартиране на {appName}.",
|
||||
"ResetQualityDefinitions": "Определения за качество",
|
||||
"SetIndexerFlags": "Индексиращи знамена",
|
||||
"ShowDigitalRelease": "Показване на датата на излизане на киното",
|
||||
"TodayAt": "{day} в {time}",
|
||||
"LanguagesLoadError": "Не може да се заредят маркери",
|
||||
"NotificationsCustomScriptValidationFileDoesNotExist": "Папката не съществува",
|
||||
"ClearBlocklistMessageText": "Наистина ли искате да премахнете избраните елементи от черния списък?",
|
||||
"DeleteSelectedImportListsMessageText": "Наистина ли искате да изтриете избраните филмови файлове?",
|
||||
"DownloadClientTransmissionSettingsDirectoryHelpText": "Незадължително локация за изтеглянията, оставете празно, за да използвате локацията по подразбиране на Aria2",
|
||||
"OnFileUpgrade": "При надстройка",
|
||||
"ShowCinemaRelease": "Показване на датата на излизане на киното",
|
||||
"ShowPhysicalRelease": "Дата на физическото издаване",
|
||||
"DownloadClientValidationAuthenticationFailure": "Изисква се удостоверяване",
|
||||
"DownloadClientSettingsOlderPriority": "Индексатор Приоритет",
|
||||
"SetReleaseGroup": "Група за освобождаване",
|
||||
"Trending": "В очакване",
|
||||
"UnableToLoadCollections": "Ограниченията не могат да се заредят",
|
||||
"NotificationsEmbySettingsSendNotifications": "Сертифициране",
|
||||
"NotificationsKodiSettingsCleanLibrary": "Чисто ниво на библиотеката",
|
||||
"RemoveQueueItemsRemovalMethodHelpTextWarning": "Премахването ще премахне изтеглянето и файловете от клиента за изтегляне.",
|
||||
"RootFolderPath": "Основна директория",
|
||||
"PendingDownloadClientUnavailable": "Клиентът за изтегляне не е наличен",
|
||||
"DeleteMovieFolderCountConfirmation": "Наистина ли искате да изтриете избраните филмови файлове?",
|
||||
"OneMinute": "Минути",
|
||||
"ResetTitles": "Заглавие на изданието",
|
||||
"RemoveSelectedItem": "Премахнете избраното",
|
||||
"RemoveSelectedItems": "Премахнете избраното",
|
||||
"EditDownloadClientImplementation": "Добави клиент за изтегляне - {implementationName}",
|
||||
"MinimumCustomFormatScoreIncrement": "Минимален резултат от персонализиран формат",
|
||||
"ReleaseHash": "Дати на издаване",
|
||||
"EditSelectedIndexers": "Изтрийте Indexer",
|
||||
"DeleteSelectedCustomFormatsMessageText": "Наистина ли искате да изтриете избраните филмови файлове?",
|
||||
"DeleteSelectedIndexersMessageText": "Наистина ли искате да изтриете избраните филмови файлове?",
|
||||
"DownloadClientQbittorrentValidationCategoryAddFailure": "Конфигурирането на етикета е неуспешно",
|
||||
"DownloadClientQbittorrentValidationCategoryAddFailureDetail": "{appName} не успя да добави етикета към {clientName}.",
|
||||
"EditConditionImplementation": "Добави условие - {implementationName}",
|
||||
"FavoriteFolderRemove": "Премахнете основната папка",
|
||||
"FormatRuntimeMinutes": "Минути",
|
||||
"FormatTimeSpanDays": "{day} в {time}",
|
||||
"ImportListsTraktSettingsYears": "Година",
|
||||
"ManageDownloadClients": "Изтеглете клиенти",
|
||||
"NotificationsDiscordSettingsAuthor": "Авто",
|
||||
"ResetDefinitions": "Определения за качество",
|
||||
"SelectIndexerFlags": "Изтрийте Indexer",
|
||||
"SkipRedownload": "Презареждане",
|
||||
"Mixed": "Фиксирана",
|
||||
"DeleteSelectedDownloadClientsMessageText": "Наистина ли искате да изтриете избраните филмови файлове?",
|
||||
"DownloadClientUTorrentTorrentStateError": "Deluge съобщава за грешка",
|
||||
"EditSelectedDownloadClients": "Изтриване на клиент за изтегляне",
|
||||
"DeleteAutoTag": "Изтриване на маркера",
|
||||
"EditCollection": "колекция",
|
||||
"ManageCustomFormats": "Клониране на персонализиран формат",
|
||||
"RemoveQueueItemRemovalMethodHelpTextWarning": "Премахването ще премахне изтеглянето и файловете от клиента за изтегляне.",
|
||||
"CustomFormatJson": "Персонализиран формат",
|
||||
"DeleteMovieFolderCountWithFilesConfirmation": "Наистина ли искате да изтриете избраните филмови файлове?",
|
||||
"DownloadClientRTorrentSettingsDirectoryHelpText": "Незадължително локация за изтеглянията, оставете празно, за да използвате локацията по подразбиране на Aria2",
|
||||
"SelectReleaseGroup": "Група за освобождаване",
|
||||
"DownloadClientQbittorrentTorrentStateError": "Deluge съобщава за грешка",
|
||||
"MovieCollectionFolderMultipleMissingRootsHealthCheckMessage": "Липсват множество коренни папки: {0}",
|
||||
"ReleasePush": "Дати на издаване"
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"Refresh": "Actualitza",
|
||||
"Reload": "Torna a carregar",
|
||||
"Blocklist": "Llista de bloquejats",
|
||||
"Blocklisted": "Bloquejats",
|
||||
"Blocklisted": "Bloquejat per llista",
|
||||
"BlocklistRelease": "Publicació de la llista de bloqueig",
|
||||
"History": "Història",
|
||||
"HomePage": "Pàgina d'inici",
|
||||
@@ -1438,7 +1438,7 @@
|
||||
"IndexerSettingsCategories": "Categories",
|
||||
"IndexerHDBitsSettingsCodecs": "Còdecs",
|
||||
"IndexerHDBitsSettingsMediums": "Mitjans",
|
||||
"BlocklistedAt": "Blocat el {date}",
|
||||
"BlocklistedAt": "Bloquejat per llista el {date}",
|
||||
"AutoTaggingSpecificationMaximumRuntime": "Temps d'execució màxim",
|
||||
"AutoTaggingSpecificationMinimumRuntime": "Temps d'execució mínim",
|
||||
"NotificationsTagsMovieHelpText": "Envia només notificacions per a pel·lícules amb almenys una etiqueta coincident",
|
||||
@@ -1629,7 +1629,7 @@
|
||||
"NotificationsPushBulletSettingsAccessToken": "Testimoni d'accés",
|
||||
"NotificationsPushBulletSettingsDeviceIds": "ID del dispositiu",
|
||||
"NotificationsPushBulletSettingsDeviceIdsHelpText": "Llista d'identificadors de dispositiu (deixeu-lo en blanc per enviar-lo a tots els dispositius)",
|
||||
"NotificationsPushoverSettingsExpireHelpText": "Temps màxim per tornar a provar les alertes d'emergència, màxim 86400 segons\"",
|
||||
"NotificationsPushoverSettingsExpireHelpText": "Temps màxim per tornar a provar les alertes d'emergència, màxim 86400 segons",
|
||||
"NotificationsSettingsWebhookMethodHelpText": "Quin mètode HTTP s'ha d'utilitzar per enviar al servei web",
|
||||
"ManageFormats": "Gestiona formats",
|
||||
"MassSearchCancelWarning": "Això no es pot cancel·lar un cop iniciat sense reiniciar {appName} o desactivar tots els vostres indexadors.",
|
||||
@@ -1987,7 +1987,7 @@
|
||||
"NotificationsSignalSettingsSenderNumber": "Número del remitent",
|
||||
"NotificationsMailgunSettingsApiKeyHelpText": "La clau API generada des de MailGun",
|
||||
"NotificationsPushcutSettingsApiKeyHelpText": "Les claus API es poden gestionar a la vista de compte de l'aplicació Pushcut",
|
||||
"DownloadClientUTorrentProviderMessage": "uTorrent té un historial d'inclusió de criptominers, programari maliciós i anuncis, us animem a triar un client diferent.",
|
||||
"DownloadClientUTorrentProviderMessage": "uTorrent té historial d'incloure criptominers, malware i anuncis, suggerim fortament que escolleixis un client diferent.",
|
||||
"DownloadClientValidationCategoryMissing": "La categoria no existeix",
|
||||
"DownloadClientValidationErrorVersion": "La versió de {clientName} hauria de ser com a mínim {requiredVersion}. La versió informada és {reportedVersion}",
|
||||
"DownloadClientValidationSslConnectFailureDetail": "{appName} no es pot connectar a {clientName} utilitzant SSL. Aquest problema podria estar relacionat amb l'ordinador. Si us plau, intenteu configurar {appName} i {clientName} per no utilitzar SSL.",
|
||||
@@ -2019,5 +2019,25 @@
|
||||
"NotificationsAppriseSettingsIncludePosterHelpText": "Inclou el pòster al missatge",
|
||||
"CloneImportList": "Clonar llista d'importació",
|
||||
"DefaultNameCopiedImportList": "{name} - Còpia",
|
||||
"ReleaseProfile": "Perfil de llançament"
|
||||
"ReleaseProfile": "Perfil de llançament",
|
||||
"CountMissingMoviesFromLibrary": "{count} pel·lícules que manquen de la biblioteca",
|
||||
"ShowPhysicalReleaseCalendarHelpText": "Mostra els llançaments físics en els esdeveniments del calendari",
|
||||
"CinemaRelease": "Llançament en cinemes",
|
||||
"ShowDigitalRelease": "Mostra la versió digital",
|
||||
"ICalReleaseTypesMoviesHelpText": "Inclou només pel·lícules amb tipus de llançament específics al canal iCal. Si no s'especifica, s'utilitzen totes les opcions.",
|
||||
"ShowDigitalReleaseCalendarHelpText": "Mostra els llançaments digitals en els esdeveniments del calendari",
|
||||
"RemoveRootFolderMoviesMessageText": "Esteu segur que voleu eliminar la carpeta arrel '{path}'? Els fitxers i carpetes no s'eliminaran del disc, i les pel·lícules d'aquesta carpeta arrel no s'eliminaran de {appName}.",
|
||||
"ICalReleaseTypes": "Tipus de llançament",
|
||||
"Keywords": "Paraules clau",
|
||||
"ShowPhysicalRelease": "Mostra la versió física",
|
||||
"ShowCinemaRelease": "Mostra el llançament del cinema",
|
||||
"ShowCinemaReleaseCalendarHelpText": "Mostra els llançaments de cinema en els esdeveniments del calendari",
|
||||
"AutoTaggingSpecificationKeyword": "Paraula(es) clau",
|
||||
"NotificationsPushcutSettingsIncludePoster": "Inclou el cartell",
|
||||
"NotificationsPushcutSettingsIncludePosterHelpText": "Inclou el cartell amb notificació",
|
||||
"NotificationsPushcutSettingsMetadataLinks": "Enllaços de metadades",
|
||||
"NotificationsPushcutSettingsMetadataLinksHelpText": "Afegeix un enllaç a les metadades de les sèries quan s'enviïn notificacions",
|
||||
"NotificationsPushoverSettingsTtl": "Temps de vida",
|
||||
"NotificationsPushoverSettingsTtlHelpText": "Temps en segons abans que el missatge caduqui. Establiu-lo a 0 per a una durada il·limitada",
|
||||
"FilterMoviePropertiesOnlyNotFileWarning": "Els filtres només estan disponibles per a les propietats d'una pel·lícula, no estan disponibles per a les propietats dels fitxers d'aquesta pel·lícula."
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"DeleteRestrictionHelpText": "Opravdu chcete toto omezení smazat?",
|
||||
"DeleteTagMessageText": "Opravdu chcete smazat značku „{0}“?",
|
||||
"DetailedProgressBar": "Podrobný ukazatel průběhu",
|
||||
"Discord": "Svár",
|
||||
"Discord": "Discord",
|
||||
"DownloadClients": "Stáhnout klienty",
|
||||
"DownloadClientsSettingsSummary": "Stahování klientů, zpracování stahování a mapování vzdálených cest",
|
||||
"DownloadPropersAndRepacks": "Sponzoři a přebalení",
|
||||
@@ -190,7 +190,7 @@
|
||||
"DeleteCustomFormat": "Odstranit vlastní formát",
|
||||
"DeletedMovieDescription": "Film byl odstraněn z TMDb",
|
||||
"DeleteMovieFolder": "Odstranit složku filmu",
|
||||
"DockerUpdater": "aktualizujte kontejner dockeru, abyste aktualizaci obdrželi",
|
||||
"DockerUpdater": "Pro získání aktualizace je třeba aktualizovat docker kontejner",
|
||||
"AddToDownloadQueue": "Přidat stahování do fronty",
|
||||
"AfterManualRefresh": "Po manuálním obnovení",
|
||||
"AllFiles": "Všechny soubory",
|
||||
@@ -347,12 +347,12 @@
|
||||
"Local": "Místní",
|
||||
"ManualImport": "Ruční import",
|
||||
"MarkAsFailed": "Označit jako neúspěšné",
|
||||
"MaximumSizeHelpText": "Maximální velikost uvolnění, která se má zachytit v MB. Nastavit na nulu nastavit na neomezený",
|
||||
"MaximumSizeHelpText": "Maximální velikost vydání, která se mají stahovat v MB. Nula znamená bez omezení",
|
||||
"Mechanism": "Mechanismus",
|
||||
"MediaInfo": "Informace o médiích",
|
||||
"MediaManagement": "Správa médií",
|
||||
"MediaManagementSettings": "Nastavení správy médií",
|
||||
"MediaManagementSettingsSummary": "Nastavení pojmenování a správy souborů",
|
||||
"MediaManagementSettingsSummary": "Nastavení jmenné konvence a správy souborů",
|
||||
"Hostname": "Název hostitele",
|
||||
"Missing": "Chybějící",
|
||||
"Month": "Měsíc",
|
||||
@@ -436,7 +436,7 @@
|
||||
"RestartRequiredHelpTextWarning": "Vyžaduje restart, aby se projevilo",
|
||||
"Restore": "Obnovit",
|
||||
"RestoreBackup": "Obnovit zálohu",
|
||||
"RootFolder": "Kořenový adresář",
|
||||
"RootFolder": "Kořenová složka",
|
||||
"RootFolderCheckMultipleMessage": "Chybí více kořenových složek: {rootFolderPaths}",
|
||||
"SendAnonymousUsageData": "Odesílejte anonymní údaje o používání",
|
||||
"FileBrowserPlaceholderText": "Začněte psát nebo vyberte cestu níže",
|
||||
@@ -495,7 +495,7 @@
|
||||
"CustomFormatsSettingsSummary": "Vlastní formáty a nastavení",
|
||||
"CustomFormatUnknownConditionOption": "Neznámá možnost „{key}“ pro podmínku „{implementation}“",
|
||||
"Cutoff": "Odříznout",
|
||||
"UpgradeUntilMovieHelpText": "Jakmile je této kvality dosaženo, {appName} již nebude stahovat filmy",
|
||||
"UpgradeUntilMovieHelpText": "Jakmile stažený film dosáhne nebo překročí nastavenou kvalitu, {appName} nebude dále stahovat další vydání",
|
||||
"CutoffUnmet": "Mezní hodnota nesplněna",
|
||||
"Days": "Dny",
|
||||
"Debug": "Ladit",
|
||||
@@ -503,7 +503,7 @@
|
||||
"DefaultDelayProfileMovie": "Toto je výchozí profil. Platí pro všechny filmy, které nemají explicitní profil.",
|
||||
"DelayProfile": "Zpožděný profil",
|
||||
"DelayProfiles": "Profily zpoždění",
|
||||
"Delete": "Vymazat",
|
||||
"Delete": "Smazat",
|
||||
"DeleteBackupMessageText": "Opravdu chcete odstranit zálohu '{name}'?",
|
||||
"Deleted": "Smazáno",
|
||||
"DeleteDelayProfile": "Smazat profil zpoždění",
|
||||
@@ -649,7 +649,7 @@
|
||||
"LinkHere": "tady",
|
||||
"Links": "Odkazy",
|
||||
"ImportLists": "Seznamy",
|
||||
"ImportListSettings": "Nastavení seznamu",
|
||||
"ImportListSettings": "Nastavení seznamu pro import",
|
||||
"ListSyncLevelHelpText": "Filmy v knihovně budou zpracovány na základě vašeho výběru, pokud vypadnou nebo se neobjeví na vašich seznamech",
|
||||
"LogFiles": "Záznam souborů",
|
||||
"Logging": "Protokolování",
|
||||
@@ -802,13 +802,13 @@
|
||||
"SetPermissionsLinuxHelpText": "Měl by se chmod spustit při importu / přejmenování souborů?",
|
||||
"SetTags": "Nastavit značky",
|
||||
"Settings": "Nastavení",
|
||||
"LongDateFormat": "Long Date Format",
|
||||
"LongDateFormat": "Dlouhý formát data",
|
||||
"RemotePathMappingLocalPathHelpText": "Cesta, kterou by {appName} měl použít pro místní přístup ke vzdálené cestě",
|
||||
"RemotePathMappingRemotePathHelpText": "Kořenová cesta k adresáři, do kterého stahovací klient přistupuje",
|
||||
"RuntimeFormat": "Runtime Format",
|
||||
"ShortDateFormat": "Formát krátkého data",
|
||||
"ShowRelativeDates": "Zobrazit relativní data",
|
||||
"ShowRelativeDatesHelpText": "Zobrazit relativní (dnes / včera / atd.) Nebo absolutní data",
|
||||
"ShowRelativeDates": "Zobrazit aktuální datum",
|
||||
"ShowRelativeDatesHelpText": "Zobrazit relevantní",
|
||||
"WeekColumnHeaderHelpText": "Zobrazuje se nad každým sloupcem, když je aktivní zobrazení týden",
|
||||
"ListMonitorMovieHelpText": "Pokud je povoleno, budou přidány a sledovány filmy přidané tímto seznamem",
|
||||
"ICalShowAsAllDayEvents": "Zobrazit jako celodenní události",
|
||||
@@ -836,7 +836,7 @@
|
||||
"SourcePath": "Cesta zdroje",
|
||||
"SourceRelativePath": "Cesta relativního zdroje",
|
||||
"SourceTitle": "Název zdroje",
|
||||
"SslCertPassword": "Heslo SSL Cert",
|
||||
"SslCertPassword": "Heslo SSL Certifikátu",
|
||||
"SslCertPasswordHelpText": "Heslo pro soubor pfx",
|
||||
"SslCertPath": "Cesta certifikátu SSL",
|
||||
"SslCertPathHelpText": "Cesta k souboru pfx",
|
||||
@@ -979,7 +979,7 @@
|
||||
"DeleteDelayProfileMessageText": "Opravdu chcete smazat tento profil zpoždění?",
|
||||
"DeleteFormatMessageText": "Opravdu chcete smazat značku formátu {0}?",
|
||||
"RemoveSelectedItemQueueMessageText": "Opravdu chcete odebrat {0} položku {1} z fronty?",
|
||||
"RemoveSelectedItemsQueueMessageText": "Opravdu chcete odebrat {selectedCount} položky z fronty?",
|
||||
"RemoveSelectedItemsQueueMessageText": "Opravdu chcete odebrat {selectedCount} položek z fronty?",
|
||||
"ApplyTagsHelpTextAdd": "Přidat: Přidat štítky do existujícího seznamu štítků",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "Jak použít štítky na vybrané indexery",
|
||||
"ApplyTagsHelpTextRemove": "Odebrat: Odebrat zadané štítky",
|
||||
@@ -1037,7 +1037,7 @@
|
||||
"CollectionShowPostersHelpText": "Zobrazit plakáty položek v kolekci",
|
||||
"ConnectionLostReconnect": "{appName} se pokusí připojit automaticky, nebo můžete kliknout na tlačítko znovunačtení níže.",
|
||||
"ConnectionLostToBackend": "{appName} ztratil spojení s backendem a pro obnovení funkčnosti bude potřeba ho znovu načíst.",
|
||||
"CountDownloadClientsSelected": "{count} vybraných klientů pro stahování",
|
||||
"CountDownloadClientsSelected": "{count} klientů ke stahování vybráno",
|
||||
"DefaultNameCopiedProfile": "{name} - Kopírovat",
|
||||
"DefaultNameCopiedSpecification": "{name} - Kopírovat",
|
||||
"DelayingDownloadUntil": "Odložení stahování do {date} v {time}",
|
||||
@@ -1078,7 +1078,7 @@
|
||||
"CountImportListsSelected": "{count} vybraných seznamů pro import",
|
||||
"CollectionShowDetailsHelpText": "Zobrazit stav a vlastnosti kolekce",
|
||||
"AutoTaggingNegateHelpText": "Pokud je zaškrtnuto, pravidlo automatického označování se nepoužije, pokud odpovídá této podmínce {implementationName}.",
|
||||
"DownloadClientSortingCheckMessage": "Klient pro stahování {downloadClientName} má nastaveno třídění {sortingMode} pro kategorii {appName}. Ve svém klientovi pro stahování byste měli třídění zakázat, abyste se vyhnuli problémům s importem.",
|
||||
"DownloadClientSortingCheckMessage": "Klient pro stahování {downloadClientName} má nastaveno třídění {sortingMode} pro kategorie {appName}. Je doporučeno toto třídění vypnout, abyste se vyhli případným problémům s importem.",
|
||||
"EditSelectedImportLists": "Upravit vybrané seznamy k importu",
|
||||
"EditSelectedIndexers": "Upravit vybrané indexery",
|
||||
"DisabledForLocalAddresses": "Zakázáno pro místní adresy",
|
||||
@@ -1278,7 +1278,7 @@
|
||||
"CutoffUnmetLoadError": "Chybné načítání nesplněných položek",
|
||||
"CutoffUnmetNoItems": "Žádné neodpovídající nesplněné položky",
|
||||
"ClickToChangeIndexerFlags": "Kliknutím změníte značky indexeru",
|
||||
"RecycleBinUnableToWriteHealthCheck": "Nelze zapisovat do nakonfigurované složky koše: {path}. Ujistěte se, že tato cesta existuje a že do ní může zapisovat uživatel se spuštěnou {appName}",
|
||||
"RecycleBinUnableToWriteHealthCheck": "Nelze zapisovat do nakonfigurované složky koše: {path}. Ujistěte se, že tato cesta existuje a že do ní může zapisovat uživatel, pod kterým běží {appName}",
|
||||
"AutoTaggingSpecificationMaximumYear": "Maximální Rok",
|
||||
"AutoTaggingSpecificationMinimumYear": "Minimální Rok",
|
||||
"ChangeCategoryHint": "Změní stahování do kategorie „Post-Import“ z aplikace Download Client",
|
||||
@@ -1366,5 +1366,213 @@
|
||||
"DownloadClientItemErrorMessage": "{clientName} hlásí chybu: {message}",
|
||||
"CloneImportList": "Klonovat seznam Importu",
|
||||
"DefaultNameCopiedImportList": "{name} - Kopírovat",
|
||||
"ReleaseProfile": "profil vydání"
|
||||
"ReleaseProfile": "profil vydání",
|
||||
"NotificationsPushcutSettingsIncludePoster": "Zahrnout plakát",
|
||||
"RemotePathMappingCheckFilesLocalWrongOSPath": "Lokální klient pro stahování {downloadClientName} hlásí soubory v {path}, ale to není validní cesta pro {osName}. Zkontrolujte nastavení klienta pro stahování.",
|
||||
"RemotePathMappingCheckLocalWrongOSPath": "Lokální klient pro stahování {downloadClientName} ukládá stažené soubory do {path}, ale vypadá to, že taková složka v {osName} neexistuje. Ověřte nastavení klienta pro stahování.",
|
||||
"TaskUserAgentTooltip": "User-Agent je poskytován aplikací, která volá API",
|
||||
"RemotePathMappingCheckFilesGenericPermissions": "Klient pro stahování {downloadClientName} hlásí soubory v {path}, ale {appName} tento adresář nevidí. Možná je nutné upravit práva této složky.",
|
||||
"RemotePathMappingCheckGenericPermissions": "Klient pro stahování {downloadClientName} ukládá soubory do {path}, ale {appName} tento adresář nevidí. Možná je nutné upravit práva této složky.",
|
||||
"RemotePathMappingCheckFileRemoved": "Soubor {path} byl smazán v průběhu zpracování.",
|
||||
"RemotePathMappingCheckWrongOSPath": "Vzdálený klient pro stahování {downloadClientName}ukládá stažené soubory do {path}, ale toto není validní cesta pro {osName}. Ověřte nastavení mapování vzdálených cest a klientů pro stahování.",
|
||||
"Loading": "Načítání",
|
||||
"NotificationsEmbySettingsSendNotificationsHelpText": "Nechat Emby poslat notifikace nastaveným poskytovatelům. Nefunguje s Jellyfin.",
|
||||
"RemotePathMappingCheckImportFailed": "{appName} nemohl importovat film. Detaily naleznete v logu.",
|
||||
"RemotePathMappingCheckBadDockerPath": "Používáte docker; klient pro stahování {downloadClientName} ukládá stažené soubory do {path}, ale to není validní cesta pro {osName}. Ověřte nastavení mapování vzdálených cest a klientů pro stahování.",
|
||||
"RemotePathMappingCheckFolderPermissions": "{appName} vidí, ale nemůže přistupovat do adresáře stahování {path}. Pravděpodobně jde o chybně nastavené oprávnění.{appName}.",
|
||||
"RemotePathMappingCheckLocalFolderMissing": "Vzdálený klient pro stahování {downloadClientName} ukládá stažené soubory do {path}, ale vypadá to, že taková složka neexistuje. Ověřte nastavení mapování vzdálených cest a klientů pro stahování.",
|
||||
"IndexerJackettAll": "Indexery, které používají nepodporovaný Jackett endpoint 'all': {indexerNames}",
|
||||
"RemotePathMappingCheckFilesWrongOSPath": "Vzdálený klient pro stahování {downloadClientName} hlásí soubory v {path}, ale to není validní cesta pro {osName}. Zkontrolujte mapování vzdálených cest a nastavení klienta pro stahování.",
|
||||
"RemotePathMappingCheckFilesBadDockerPath": "Používáte docker; klient pro stahování {downloadClientName} hlásí soubory v {path}, ale to není validní cesta pro {osName}. Ověřte nastavení mapování vzdálených cest a klientů pro stahování.",
|
||||
"RemotePathMappingCheckDockerFolderMissing": "Používáte docker; klient pro stahování {downloadClientName} ukládá stažené soubory do {path}, ale vypadá to, že taková složka v tomto konejneru neexistuje. Ověřte nastavení mapování vzdálených cest a klientů pro stahování.",
|
||||
"RemotePathMappingsInfo": "Mapování vzdálených cest je potřeba pouze ve výjimečných případech. Pokud {appName} a klient pro stahování je na stejném systému, je lepší cesty tak, aby byly všude stejné. Více informací naleznete na [wiki]({wikiLink}).",
|
||||
"RemotePathMappingCheckRemoteDownloadClient": "Vzdálený klient pro stahování {downloadClientName} hlásí soubory v {path}, ale vypadá to, že taková složka neexistuje. Pravděpodobně chybí nastavení mapování vzdálených cest.",
|
||||
"SmartReplaceHint": "Pomlčka nebo mezera pomlčka, podle jména",
|
||||
"EnableProfileHelpText": "Zaškrnutím zapnete profil vydání",
|
||||
"NotificationsEmbySettingsUpdateLibraryHelpText": "Aktualizovat knihovnu při importu, přejmenování nebo smazání?",
|
||||
"NotificationsKodiSettingsDisplayTime": "Zobrazit čas",
|
||||
"LogSizeLimit": "Limit velikosti logových souborů",
|
||||
"LogSizeLimitHelpText": "Maximální velikost souboru s logy v MB před jeho archivací. Výchozí je 1MB.",
|
||||
"Logout": "Odhlásit",
|
||||
"NoCustomFormatsFound": "Nenalezeny žádné vlastní formáty",
|
||||
"NotificationsKodiSettingsCleanLibrary": "Vyčistit knihovnu",
|
||||
"RemoveQueueItem": "Odebrat - {sourceTitle}",
|
||||
"RemoveSelectedItem": "Odebrat vybranou položku",
|
||||
"Started": "Běží",
|
||||
"UseSsl": "Použít SSL",
|
||||
"LastSearched": "Poslední hledání",
|
||||
"OnHealthRestored": "Při obnovení zdraví",
|
||||
"InstanceName": "Jméno instance",
|
||||
"InfoUrl": "Info URL",
|
||||
"DownloadClientSettingsPostImportCategoryHelpText": "Kategorie, kterou {appName} nastaví po importu staženého souboru. {appName} nebude odebírat torrenty této kategorie i když už nebudou dále seedovány. Nechte prázdné pro ponechání stejné kategorie.",
|
||||
"IgnoreDownloadHint": "Zabrání {appName} v dalším zpracování tohoto stahování",
|
||||
"IndexerSettingsSeedRatio": "Poměr sdílení",
|
||||
"NotificationsKodiSettingAlwaysUpdateHelpText": "Aktualizovat knihovnu i když se přehrává video?",
|
||||
"NotificationsPlexSettingsAuthenticateWithPlexTv": "Autentizovat s Plex.tv",
|
||||
"ParseModalHelpTextDetails": "{appName} se pokusí zpracovat název a ukáže vám výsledek",
|
||||
"PendingDownloadClientUnavailable": "Čeká - klient pro stahování není dostupný",
|
||||
"Period": "Období",
|
||||
"ResetQualityDefinitions": "Obnovit definice kvality",
|
||||
"SelectReleaseGroup": "Vybrat skupinu vydání",
|
||||
"IgnoreDownload": "Ignorovat stažení",
|
||||
"IndexerSettingsApiUrlHelpText": "Neměňte tohle, pokud nevíte, co děláte. Váš API klíč bude odeslán na toho hosta.",
|
||||
"IndexerSettingsRejectBlocklistedTorrentHashes": "Odmítnout blacklistované hashe torrentů při stahování",
|
||||
"MassSearchCancelWarning": "Akce nemůže být po spuštění zastavena jinak, než restartem {appName} nebo vypnutím všech indexerů.",
|
||||
"MonitorSelected": "Monitorovat vybrané",
|
||||
"NotificationsKodiSettingAlwaysUpdate": "Vždy aktualizovat",
|
||||
"NotificationsKodiSettingsUpdateLibraryHelpText": "Aktualizovat knihovnu při importu a přejmenování?",
|
||||
"NotificationsSettingsUseSslHelpText": "Připojovat se k {serviceName} pomocí HTTPS místo HTTP",
|
||||
"Parse": "Zpracování",
|
||||
"ParseModalHelpText": "Zadejte název vydání do pole výše",
|
||||
"RemoveCompletedDownloads": "Odebrat dokončená stahování",
|
||||
"SkipRedownloadHelpText": "Zabraňuje {appName} zkoušet stahovat alternativní vydání pro odebrané položky",
|
||||
"SmartReplace": "Chytré nahrazení",
|
||||
"NotificationsPlexSettingsAuthToken": "Autorizační token",
|
||||
"NotificationsTelegramSettingsIncludeAppName": "Vložit {appName} do titulku",
|
||||
"NotificationsTelegramSettingsIncludeAppNameHelpText": "Přidat {appName} před titulek zprávy pro odlišení notifikací z jiných aplikací",
|
||||
"PostImportCategory": "Kategorie po importu",
|
||||
"PreferProtocol": "Preferovat {preferredProtocol}",
|
||||
"PreviouslyInstalled": "Minulá instalace",
|
||||
"ListRefreshInterval": "Interval obnovení seznamu",
|
||||
"NotificationsSettingsUpdateLibrary": "Aktualizovat knihovnu",
|
||||
"NotificationsSettingsUpdateMapPathsTo": "Mapovat cestu na",
|
||||
"RemoveCompleted": "Odebrat dokončené",
|
||||
"NotificationsEmbySettingsSendNotifications": "Poslat notifikace",
|
||||
"NotificationsKodiSettingsGuiNotification": "GUI notifikace",
|
||||
"QueueFilterHasNoItems": "Vybraný filtr fronty nemá žádné položky",
|
||||
"TestParsing": "Test parsování",
|
||||
"MissingNoItems": "Žádné chybějící položky",
|
||||
"SelectIndexerFlags": "Vybrat příznaky indexeru",
|
||||
"RegularExpressionsTutorialLink": "Více detailů o regulárních výrazech naleznete [zde]({url}).",
|
||||
"RemoveFailed": "Odebrání selhalo",
|
||||
"RemoveQueueItemRemovalMethod": "Metoda odebrání",
|
||||
"UnmonitorSelected": "Nemonitorovat vybrané",
|
||||
"SizeLimit": "Limit velikosti",
|
||||
"Space": "Mezera",
|
||||
"False": "Nepravda",
|
||||
"IgnoreDownloads": "Ignorovat stahování",
|
||||
"NotificationsKodiSettingsCleanLibraryHelpText": "Vyčistit knihovnu po aktualizaci",
|
||||
"DownloadClientSettingsOlderPriority": "Starší priorita",
|
||||
"FormatShortTimeSpanHours": "{hours} hodin(a/y)",
|
||||
"FormatRuntimeMinutes": "{minutes}m",
|
||||
"FormatShortTimeSpanMinutes": "{minutes} minut(a/y)",
|
||||
"OnApplicationUpdate": "Při aktualizaci aplikace",
|
||||
"Rejections": "Odmítnutí",
|
||||
"RemoveMultipleFromDownloadClientHint": "Odebrat stahování a soubory z klienta pro stahování",
|
||||
"RemoveTagsAutomatically": "Automaticky odebrat tagy",
|
||||
"RemoveTagsAutomaticallyHelpText": "Automaticky odebrat tagy, pokud podmínky nejsou splněny",
|
||||
"Repack": "Repack",
|
||||
"RootFolderPath": "Cesta kořenového adresáře",
|
||||
"ManageClients": "Spravovat klienty",
|
||||
"ErrorLoadingContent": "Nastala chyba při načítání obsahu",
|
||||
"ErrorLoadingItem": "Nastala chyba při načítání této položky",
|
||||
"ErrorLoadingPage": "Nastala chyba při načítání této stránky",
|
||||
"IndexerSettingsSeedRatioHelpText": "Poměr, kterého by torrent měl dosáhnout před zastavením sdílení, prázdná hodnota použije výchozí hodnotu klienta stahování. Poměr sdílení by měl být alespoň 1.0 a měl by plnit pravidla indexeru",
|
||||
"IndexerSettingsSeedTimeHelpText": "Doba, po kterou bude torrent sdílen před zastavením, prázdná hodnota použije výchozí hodnotu klienta stahování",
|
||||
"Install": "Instalovat",
|
||||
"InteractiveSearchModalHeaderTitle": "Interaktivní hledání - {title}",
|
||||
"InvalidUILanguage": "Vaše UI má nastaveno neplatný jazyk, opravte jej a uložte nastavení",
|
||||
"LabelIsRequired": "Název je vyžadován",
|
||||
"LogFilesLocation": "Soubory s logy jsou uloževy v {location}",
|
||||
"ManageCustomFormats": "Spravovat vlastní formáty",
|
||||
"ManageDownloadClients": "Spravovat klienty stahování",
|
||||
"ManageFormats": "Spravovat formáty",
|
||||
"ManageImportLists": "Spravovat importní seznamy",
|
||||
"ManageIndexers": "Spravovat indexery",
|
||||
"ManageLists": "Spravovat seznamy",
|
||||
"Menu": "Menu",
|
||||
"NoDownloadClientsFound": "Nenalezen žádný klient stahování",
|
||||
"NoImportListsFound": "Nenalezen žádný importní seznam",
|
||||
"NoIndexersFound": "Nenalezen žádný indexer",
|
||||
"NotificationsSettingsWebhookHeaders": "Hlavičky",
|
||||
"RemoveFromDownloadClientHint": "Odebrat stahování a soubor(y) z klienta pro stahování",
|
||||
"RemoveQueueItemRemovalMethodHelpTextWarning": "'Odebrat z klienta pro stahování' odebere stahování soubor(y) z klienta pro stahování.",
|
||||
"RemoveSelectedItems": "Odebrat vybrané položky",
|
||||
"ResetQualityDefinitionsMessageText": "Opravdu chcete obnovit definice kvality?",
|
||||
"ResetTitles": "Obnovit názvy",
|
||||
"ResetDefinitions": "Obnovit definice",
|
||||
"UnableToImportAutomatically": "Automatický import se nezdařil",
|
||||
"SkipRedownload": "Přeskočit opětovné stažení",
|
||||
"FormatShortTimeSpanSeconds": "{seconds} vteřin(a/y)",
|
||||
"Never": "Nikdy",
|
||||
"True": "Pravda",
|
||||
"EnableRssHelpText": "Bude použito, když {appName} pravidelně vyhledává vydání pomocí RSS",
|
||||
"DownloadClientPriorityHelpText": "Priorita klienta pro stahování od 1 (nejvyšší) do 50 (nejnižší). Výchozí: 1. Pro klienty se stejnou prioritou se používá funkce Round-Robin.",
|
||||
"IgnoreDownloadsHint": "Zabrání {appName} v dalším zpracování těchto stahování",
|
||||
"ParseModalErrorParsing": "Chyba zpracování, zkuste to prosím znovu.",
|
||||
"ParseModalUnableToParse": "Nepodařilo se zpracovat zadaný název, zkuste to prosím znovu.",
|
||||
"PasswordConfirmation": "Potvrzení hesla",
|
||||
"RemoveDownloadsAlert": "Natavení \"Odebrání\" byla přesunuta do jednotlivých klientů pro stahování v tabulce výše.",
|
||||
"RemoveFailedDownloads": "Odebrat neúspěšná stahování",
|
||||
"RemoveQueueItemsRemovalMethodHelpTextWarning": "'Odebrat z klienta pro stahování' odebere stahování soubory z klienta pro stahování.",
|
||||
"ResetDefinitionTitlesHelpText": "Obnovit názvy definice včetně jejich hodnot",
|
||||
"ListRootFolderHelpText": "Kořenová složka, do které budou přidány položky seznamu",
|
||||
"ThemeHelpText": "Změnit motiv UI, možnost „Auto“ kopíruje nastavení OS pro výběr tmavého nebo světlého režimu. Inspirováno theme.park",
|
||||
"UpdateAvailableHealthCheckMessage": "Nová verze je k dispozici: {version}",
|
||||
"SkipFreeSpaceCheckHelpText": "Použijte v případě, kdy {appName} správně nedetekuje volné místo vaší kořenové složky",
|
||||
"UpdateFiltered": "Aktualizace filtrována",
|
||||
"EditSelectedCustomFormats": "Upravit vybrané vlastní formáty",
|
||||
"FormatDateTimeRelative": "{relativeDay}, {formattedDate} {formattedTime}",
|
||||
"FormatRuntimeHours": "{hours}h",
|
||||
"FormatTimeSpanDays": "{days}d {time}",
|
||||
"NotificationsKodiSettingsDisplayTimeHelpText": "Jak dlouho má zůstat notifikace zobrazena (s)",
|
||||
"IndexerSettingsSeedTime": "Doba sdílení",
|
||||
"InstallMajorVersionUpdate": "Instalovat aktualizaci",
|
||||
"InstallMajorVersionUpdateMessage": "Tato aktualizace nainstaluje novou major verzi, která nemusí být kompatibilní s vaším systémem. Pokračovat v instalaci?",
|
||||
"InstallMajorVersionUpdateMessageLink": "Pro více informací prosím navštivte [{domain}]({url}).",
|
||||
"InstanceNameHelpText": "Jméno instance v záložce a v syslogu",
|
||||
"SetIndexerFlags": "Nastavit příznaky indexeru",
|
||||
"ReleaseProfileIndexerHelpText": "Výběr, jakých indexerů se profil týká",
|
||||
"DownloadClientQbittorrentValidationCategoryRecommended": "Kategorie je doporučená",
|
||||
"DownloadClientQbittorrentValidationCategoryUnsupported": "Kategorie není podporována",
|
||||
"DownloadClientNzbVortexMultipleFilesMessage": "Stažení obsahuje více souborů a nenachází se ve složce úlohy: {outputPath}",
|
||||
"DownloadClientNzbgetValidationKeepHistoryOverMax": "Nastavení KeepHistory pro NzbGet by mělo být menší než 25000",
|
||||
"DownloadClientNzbgetValidationKeepHistoryZero": "Nastavení NzbGet pro KeepHistory by mělo být větší než 0",
|
||||
"SearchForAllMissingMovies": "Vyhledat všechna chybějící alba",
|
||||
"DownloadClientSettingsOlderPriorityMovieHelpText": "Priorita, která se použije při stahování alb vydaných před více, než 14 dny",
|
||||
"ImportListsTraktSettingsListType": "Typ umělce",
|
||||
"DownloadClientQbittorrentValidationCategoryAddFailureDetail": "{appName} nemohl(a) přidat etiketu k {clientName}.",
|
||||
"IndexerSettingsMinimumSeeders": "Minimální počet seederů aplikací",
|
||||
"NotificationsGotifySettingsServerHelpText": "URL serveru Apprise, včetně http(s):// a portu, pokud je potřeba",
|
||||
"ReleaseHash": "Datum vydání",
|
||||
"SearchForCutoffUnmetMovies": "Vyhledat všechna alba, která nedosáhla nastavené hranice kvality",
|
||||
"ShowCinemaRelease": "Zobrazit datum vydání kina",
|
||||
"TodayAt": "{day} v {time}",
|
||||
"NotificationsPushcutSettingsNotificationName": "Oznámení",
|
||||
"NotificationsTagsMovieHelpText": "Posílat notifikace jen pro umělce, co mají alespoň jeden shodný tag",
|
||||
"RemoveRootFolderMoviesMessageText": "OPravdu chcete odebrat kořenovou složku '{name}'? Soubory ani složky nebudou smazány a umělci v této složce nebudou odebrány z {appName}.",
|
||||
"SearchForAllMissingMoviesConfirmationCount": "Opravdu chcete vyhledat všechna ({totalRecords}) chybějící alba?",
|
||||
"ShowDigitalRelease": "Zobrazit datum vydání kina",
|
||||
"ShowPhysicalRelease": "Datum fyzického vydání",
|
||||
"Trending": "čekající",
|
||||
"NotificationsGotifySettingIncludeMoviePoster": "Zahrnout plakát",
|
||||
"NotificationsGotifySettingIncludeMoviePosterHelpText": "Zahrnout plakát do zprávy",
|
||||
"NotificationsSettingsUpdateMapPathsFrom": "Mapovat cestu na",
|
||||
"MinimumCustomFormatScoreIncrement": "Minimální skóre vlastního formátu",
|
||||
"MovieCollectionRootFolderMissingRootHealthCheckMessage": "Chybí kořenový adresář pro import seznamu: {rootFolderInfo}",
|
||||
"SetReleaseGroup": "Vybrat skupinu vydání",
|
||||
"DownloadClientSettingsCategorySubFolderHelpText": "Výchozí záložní kategorie, pokud pro vydání neexistuje žádná namapovaná kategorie. Přidáním kategorie specifické pro {appName} se zabrání konfliktům s nesouvisejícími stahováními, která nejsou {appName}. Použití kategorie je nepovinné, ale důrazně se doporučuje. Vytvoří podadresář [kategorie] ve výstupním adresáři.",
|
||||
"DownloadClientValidationAuthenticationFailure": "Vyžadováno ověření",
|
||||
"InvalidMovieInfoLanguageLanguage": "Vaše UI má nastaveno neplatný jazyk, opravte jej a uložte nastavení",
|
||||
"LanguagesLoadError": "Značky nelze načíst",
|
||||
"OnFileUpgrade": "Při upgradu",
|
||||
"OneMinute": "Minut",
|
||||
"ReleaseProfileTagMovieHelpText": "Profily vydání se aplikují na umělce s alespoň jedním shodným tagem. Nechte prázdné pro aplikaci na všechny umělce",
|
||||
"RemotePathMappingCheckDownloadPermissions": "{appName} sice vidí, ale nemůže přistupovat ke stažené hudbě {0}. Pravděpodobně chybně nastavené v oprávnění.",
|
||||
"DownloadClientQbittorrentValidationCategoryAddFailure": "Konfigurace etikety selhala",
|
||||
"DownloadClientUTorrentTorrentStateError": "Deluge hlásí chybu",
|
||||
"DownloadIgnoredMovieTooltip": "Stahování ignorováno",
|
||||
"ImportListsTraktSettingsAdditionalParametersHelpText": "Dodatečné parametry",
|
||||
"ImportListsTraktSettingsYears": "Rok",
|
||||
"ListQualityProfileHelpText": "Profil kvality se kterým by měly být přidány položky seznamu stahování",
|
||||
"MovieEditRootFolderHelpText": "Přesun umělců do stejné kořenové složky může být použito k přejmenování složek umělců, takže budou odpovídat jmenné konvenci",
|
||||
"DownloadClientSettingsCategoryHelpText": "Výchozí záložní kategorie, pokud pro vydání neexistuje žádná namapovaná kategorie. Přidáním kategorie specifické pro {appName} se zabrání konfliktům s nesouvisejícími stahováními, která nejsou {appName}. Použití kategorie je nepovinné, ale důrazně se doporučuje.",
|
||||
"DownloadClientSettingsRecentPriorityMovieHelpText": "Priorita, která se použije při stahování alb vydaných v posledních 14 dnech",
|
||||
"FavoriteFolderRemove": "Odeberte kořenovou složku",
|
||||
"MovieMatchType": "Hledat typy",
|
||||
"SearchForCutoffUnmetMoviesConfirmationCount": "Opravdu chcete vyhledat všechna alba ({totalRecords}), která nedosáhla nastavené hranice kvality?",
|
||||
"DownloadClientQbittorrentTorrentStateError": "Deluge hlásí chybu",
|
||||
"MovieDownloaded": "Stahování ignorováno",
|
||||
"ReleasePush": "Datum vydání",
|
||||
"CinemaRelease": "Zobrazit datum vydání kina"
|
||||
}
|
||||
|
||||
@@ -1116,5 +1116,74 @@
|
||||
"IndexerHDBitsSettingsCodecs": "codec",
|
||||
"IndexerHDBitsSettingsMediums": "Medium",
|
||||
"IndexerSettingsCategories": "Kategorier",
|
||||
"ReleaseProfile": "udgivelsesprofil"
|
||||
"ReleaseProfile": "udgivelsesprofil",
|
||||
"ResetDefinitions": "Kvalitetsdefinitioner",
|
||||
"UnableToLoadCollections": "Kunne ikke indlæse begrænsninger",
|
||||
"EditAutoTag": "Tilføj automatisk etiket",
|
||||
"Trending": "Verserende",
|
||||
"OneMinute": "Protokoller",
|
||||
"ShowCinemaRelease": "Vis biografens udgivelsesdato",
|
||||
"ShowDigitalRelease": "Vis biografens udgivelsesdato",
|
||||
"RemoveQueueItemRemovalMethodHelpTextWarning": "Fjernelse fjerner download og fil (er) fra download-klienten.",
|
||||
"ManageCustomFormats": "Bruger Tilpasset Formater",
|
||||
"ManageDownloadClients": "Download Klienter",
|
||||
"MinimumCustomFormatScoreIncrement": "Minimum tilpasset format score",
|
||||
"NotificationsPushcutSettingsNotificationName": "Notifikationer",
|
||||
"RemoveSelectedItems": "Fjern valgte",
|
||||
"CustomFormatJson": "Bruger Tilpasset Formater",
|
||||
"CustomFormatsSpecificationMinimumSize": "Maksimal størrelse",
|
||||
"DownloadClientFloodSettingsStartOnAdd": "Søg på Tilføj",
|
||||
"EditSelectedDownloadClients": "Slet Download Client",
|
||||
"FavoriteFolderRemove": "Fjern rodmappen",
|
||||
"FormatRuntimeMinutes": "Protokoller",
|
||||
"NotificationsEmbySettingsSendNotifications": "Notifikationer",
|
||||
"Category": "Tilføj Kategori",
|
||||
"ClearBlocklistMessageText": "Er du sikker på, at du vil fjerne de valgte emner fra sortlisten?",
|
||||
"PendingDownloadClientUnavailable": "Downloadklienten er ikke tilgængelig",
|
||||
"ReleaseHash": "Slip datoer",
|
||||
"RootFolderPath": "Rodmappe",
|
||||
"SetIndexerFlags": "Indexer Flag",
|
||||
"SetReleaseGroup": "Slip gruppe",
|
||||
"ShowPhysicalRelease": "Fysisk udgivelsesdato",
|
||||
"SelectIndexerFlags": "Slet Indexer",
|
||||
"DeleteSelectedIndexersMessageText": "Er du sikker på, at du vil slette de valgte filmfiler?",
|
||||
"DownloadClientSettingsOlderPriority": "Indekseringsprioritet",
|
||||
"ApplicationURL": "Applikationer",
|
||||
"AutomaticAdd": "Automatisk",
|
||||
"EditSelectedIndexers": "Slet Indexer",
|
||||
"EditSelectedMovies": "Slet valgte filmfiler",
|
||||
"AudioLanguages": "Multi-sprog",
|
||||
"AuthenticationMethod": "Godkendelse",
|
||||
"ResetQualityDefinitions": "Kvalitetsdefinitioner",
|
||||
"AutoTaggingRequiredHelpText": "Denne {implementationName}-betingelse skal matche for at det tilpassede format kan anvendes. Ellers er et enkelt {implementationName}-match tilstrækkeligt.",
|
||||
"AutoTaggingSpecificationStudio": "Studio",
|
||||
"AutoTaggingSpecificationGenre": "Genrer",
|
||||
"DeleteSelected": "Slet valgte film",
|
||||
"DeleteSelectedCustomFormatsMessageText": "Er du sikker på, at du vil slette de valgte filmfiler?",
|
||||
"EditCollection": "Samling",
|
||||
"CountIndexersSelected": "{count} film er valgt",
|
||||
"DeleteAutoTag": "Slet tag",
|
||||
"DeleteMovieFolderCountConfirmation": "Er du sikker på, at du vil slette de valgte filmfiler?",
|
||||
"DeleteMovieFolderCountWithFilesConfirmation": "Er du sikker på, at du vil slette de valgte filmfiler?",
|
||||
"DeleteSelectedDownloadClientsMessageText": "Er du sikker på, at du vil slette de valgte filmfiler?",
|
||||
"DeleteSelectedImportListsMessageText": "Er du sikker på, at du vil slette de valgte filmfiler?",
|
||||
"ImportListMultipleMissingRoots": "Der mangler flere rodmapper: {0}",
|
||||
"LanguagesLoadError": "Kan ikke indlæse tags",
|
||||
"ManageFormats": "Bruger Tilpasset Formater",
|
||||
"MassSearchCancelWarning": "Dette kan ikke annulleres når først det er startet uden at du deaktiverer alle dine indeksører.",
|
||||
"OnFileUpgrade": "Ved opgradering",
|
||||
"RemoveQueueItemsRemovalMethodHelpTextWarning": "Fjernelse fjerner download og fil (er) fra download-klienten.",
|
||||
"ResetTitles": "Udgiv titel",
|
||||
"Destination": "Destinationssti",
|
||||
"NotificationsKodiSettingsGuiNotification": "Notifikationer",
|
||||
"SearchForAllMissingMovies": "Start søgning efter manglende film",
|
||||
"NotificationsKodiSettingsCleanLibrary": "Rens biblioteksniveau",
|
||||
"ImportListsTraktSettingsYears": "År",
|
||||
"SkipRedownload": "Genindlæs",
|
||||
"RemoveSelectedItem": "Fjern valgte",
|
||||
"Donate": "Dato",
|
||||
"SelectReleaseGroup": "Slip gruppe",
|
||||
"MovieCollectionFolderMultipleMissingRootsHealthCheckMessage": "Der mangler flere rodmapper: {0}",
|
||||
"ReleasePush": "Slip datoer",
|
||||
"CinemaRelease": "Vis biografens udgivelsesdato"
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"Discover": "Entdecken",
|
||||
"DiskSpace": "Festplattenplatz",
|
||||
"DownloadClientCheckNoneAvailableMessage": "Kein Download Client verfügbar",
|
||||
"DownloadClientCheckUnableToCommunicateMessage": "Kommunikation mit {downloadClientName} nicht möglich.",
|
||||
"DownloadClientCheckUnableToCommunicateMessage": "Kommunikation mit {downloadClientName} nicht möglich. {errorMessage}",
|
||||
"DownloadClientStatusCheckAllClientMessage": "Alle Download Clients sind aufgrund von Fehlern nicht verfügbar",
|
||||
"DownloadClientStatusCheckSingleClientMessage": "Download Clients aufgrund von Fehlern nicht verfügbar: {downloadClientNames}",
|
||||
"DownloadClients": "Download Clients",
|
||||
@@ -155,7 +155,7 @@
|
||||
"Timeleft": "Restzeit",
|
||||
"TagsSettingsSummary": "Sehen Sie sich alle Tags und deren Verwendung an. Nicht verwendete Tags können entfernt werden",
|
||||
"SourceTitle": "Quellentitel",
|
||||
"SizeOnDisk": "Größe",
|
||||
"SizeOnDisk": "Größe auf dem Laufwerk",
|
||||
"Size": "Größe",
|
||||
"Runtime": "Laufzeit",
|
||||
"Renamed": "Umbenannt",
|
||||
@@ -563,7 +563,7 @@
|
||||
"RecyclingBinCleanupHelpTextWarning": "Datien im Papierkorb die älter sind als der gewählte Wert, werden endgültig gelöscht",
|
||||
"RemotePathMappingsLoadError": "Kann Remote-Pfadzuordnungen nicht laden",
|
||||
"DeleteEmptyMovieFoldersHelpText": "Lösche leere Filmeordner während des Scans oder wenn Filmdateien gelöscht werden",
|
||||
"MaximumSizeHelpText": "Maximale Größe für einen Release, der heruntergeladen wird, in MB. Setze auf Null, um es auf unbegrenzt zu setzen.",
|
||||
"MaximumSizeHelpText": "Maximale Größe für einen Release, der heruntergeladen wird, in MB. Setze auf Null, um es auf unbegrenzt zu setzen",
|
||||
"ReleaseDates": "VÖ Termine",
|
||||
"CertificationCountryHelpText": "Wähle ein Land für die Film Zertifizierungen",
|
||||
"DeleteNotification": "Benachrichtigung löschen",
|
||||
@@ -615,7 +615,7 @@
|
||||
"ExcludeMovie": "Film ausschließen",
|
||||
"SearchIsNotSupportedWithThisIndexer": "Suche wird von diesem Indexer nicht unterstützt",
|
||||
"EnableInteractiveSearchHelpText": "Wird verwendet, wenn die interaktive Suche verwendet wird",
|
||||
"EnableAutomaticSearchHelpText": "Wird verwendet, wenn die automatische Suche über die Benutzeroberfläche oder durch {appName} durchgeführt wird.",
|
||||
"EnableAutomaticSearchHelpText": "Wird verwendet, wenn die automatische Suche über die Benutzeroberfläche oder durch {appName} durchgeführt wird",
|
||||
"DownloadWarning": "Download Warnung: {warningMessage}",
|
||||
"Downloading": "wird runtergeladen",
|
||||
"DownloadFailed": "Download fehlgeschlagen",
|
||||
@@ -877,7 +877,7 @@
|
||||
"Hours": "Stunden",
|
||||
"HomePage": "Hauptseite",
|
||||
"MoveMovieFoldersRenameFolderWarning": "Dies wird auch den Filmordner nach dem Filmordnerformat aus den Einstellungen umbenennen.",
|
||||
"FeatureRequests": "Feature Anfragen",
|
||||
"FeatureRequests": "Funktion anfragen",
|
||||
"ExternalUpdater": "{appName} ist so konfiguriert, dass es einen externen Aktualisierungsmechanismus verwendet",
|
||||
"ExcludeTitle": "{0} ausschließen? Dies wird {appName} daran hindern, es automatisch beim Listen synchronisieren hinzuzufügen.",
|
||||
"ErrorRestoringBackup": "Fehler beim Wiederherstellen der Sicherung",
|
||||
@@ -1232,7 +1232,7 @@
|
||||
"DownloadClientDownloadStationValidationSharedFolderMissing": "Der freigegebene Ordner existiert nicht",
|
||||
"DownloadClientFloodSettingsPostImportTags": "Post-Import-Tags",
|
||||
"DownloadClientFreeboxSettingsApiUrl": "API-URL",
|
||||
"DownloadClientFreeboxSettingsApiUrlHelpText": "Definiere die Freebox-API-Basis-URL mit der API-Version, z. B. '{url}', standardmäßig '{defaultApiUrl}'.",
|
||||
"DownloadClientFreeboxSettingsApiUrlHelpText": "Definiere die Freebox-API-Basis-URL mit der API-Version, z. B. '{url}', standardmäßig '{defaultApiUrl}'",
|
||||
"DownloadClientNzbVortexMultipleFilesMessage": "Der Download enthält mehrere Dateien und befindet sich nicht in einem Jobordner: {outputPath}",
|
||||
"DownloadClientQbittorrentSettingsFirstAndLastFirst": "Erster und Letzter Erster",
|
||||
"DownloadClientQbittorrentSettingsFirstAndLastFirstHelpText": "Laden Sie zuerst das erste und das letzte Stück herunter (qBittorrent 4.1.0+)",
|
||||
@@ -1304,7 +1304,7 @@
|
||||
"DownloadClientQbittorrentValidationQueueingNotEnabled": "Warteschlangen nicht aktiviert",
|
||||
"DownloadClientQbittorrentValidationQueueingNotEnabledDetail": "Torrent Warteschlange ist in Ihren qBittorrent-Einstellungen nicht aktiviert. Aktivieren Sie es in qBittorrent oder wählen Sie „Letzte“ als Priorität.",
|
||||
"DownloadClientQbittorrentValidationRemovesAtRatioLimit": "qBittorrent ist so konfiguriert, dass Torrents entfernt werden, wenn sie ihr Share-Ratio-Limit erreichen",
|
||||
"DownloadClientQbittorrentValidationRemovesAtRatioLimitDetail": "{appName} kann das Handling abgeschlossener Downloads wie konfiguriert nicht durchführen. Du kannst das in qBittorrent beheben ('Werkzeuge -> Optionen...' im Menü), indem du 'Optionen -> BitTorrent -> Share Ratio Limiting' von 'Entfernen' auf 'Pausieren' änderst.",
|
||||
"DownloadClientQbittorrentValidationRemovesAtRatioLimitDetail": "{appName} kann das Handling abgeschlossener Downloads wie konfiguriert nicht durchführen. Du kannst das in qBittorrent beheben ('Werkzeuge -> Optionen...' im Menü), indem du 'Optionen -> BitTorrent -> Share Ratio Limiting' von 'Entfernen' auf 'Pausieren' änderst",
|
||||
"DownloadClientRTorrentSettingsAddStopped": "Hinzufügen gestoppt",
|
||||
"DownloadClientRTorrentSettingsAddStoppedHelpText": "Durch die Aktivierung werden Torrents und Magnete im gestoppten Zustand zu rTorrent hinzugefügt. Dadurch können Magnetdateien beschädigt werden.",
|
||||
"DownloadClientRTorrentSettingsUrlPathHelpText": "Pfad zum XMLRPC-Endpunkt, siehe {url}. Dies ist normalerweise RPC2 oder [Pfad zu ruTorrent]{url2}, wenn ruTorrent verwendet wird.",
|
||||
@@ -1975,6 +1975,59 @@
|
||||
"ImportListsRadarrSettingsTagsHelpText": "Tags von der Quellinstanz zum Importieren",
|
||||
"EditMovieCollectionModalHeader": "Bearbeiten - {title}",
|
||||
"DownloadClientUTorrentProviderMessage": "uTorrent ist dafür bekannt, dass es Kryptominer, Malware und Werbung enthält. Wir empfehlen dringend einen anderen Client zu wählen.",
|
||||
"DefaultNameCopiedImportList": "{name} – Kopieren",
|
||||
"ReleaseProfile": "Release-Profil"
|
||||
"DefaultNameCopiedImportList": "{name} - Kopieren",
|
||||
"ReleaseProfile": "Release-Profil",
|
||||
"ImportListsTraktSettingsCertificationMovieHelpText": "Filtriere Filme nach einem Zertifikat(NR,G,PG,PG-13,R,NC-17)(Beistrich getrennt)",
|
||||
"ImportListsTraktSettingsRatingMovieHelpText": "Sortiere Filme nach Bewertung (0-100)",
|
||||
"ImportListsTraktSettingsPopularListTypeRecommendedMoviesByWeek": "Empfohlene Filme nach Woche",
|
||||
"ImportListsTraktSettingsPopularListTypeRecommendedMoviesByYear": "Empfohlene Filme nach Jahr",
|
||||
"ImportListsTraktSettingsPopularListTypeTopWatchedMoviesByMonth": "Am meisten geschaute Filme nach Monat",
|
||||
"IndexerSettingsBaseUrl": "Basis-Url",
|
||||
"ImportListsTraktSettingsPopularListTypeTopWatchedMoviesByWeek": "Am meisten geschaute Filme nach Woche",
|
||||
"ImportListsTraktSettingsYearsMovieHelpText": "Filme nach Jahr oder Jahresspanne filtern",
|
||||
"IndexerNewznabSettingsCategoriesHelpText": "Dropdown-Liste, mindestens eine Kategorie muss ausgewählt werden.",
|
||||
"GrabbedAt": "Geholt am {date}",
|
||||
"CinemaRelease": "Kinostart",
|
||||
"FailedAt": "Fehlgeschlagen am {date}",
|
||||
"ImportListsTraktSettingsLimitMovieHelpText": "Die Anzahl der abzurufenden Filme begrenzen",
|
||||
"ImportListsTraktSettingsPopularListTypePopularMovies": "Beliebte Filme",
|
||||
"ImportListsTraktSettingsPopularListTypeRecommendedMoviesByMonth": "Empfohlene Filme nach Monat",
|
||||
"ImportListsTraktSettingsPopularListTypeRecommendedMoviesOfAllTime": "Empfohlene Filme aller Zeiten",
|
||||
"ImportListsTraktSettingsPopularListTypeTopWatchedMoviesByYear": "Am meisten geschaute Filme nach Jahr",
|
||||
"ImportListsTraktSettingsPopularListTypeTopWatchedMoviesOfAllTime": "Am meisten geschaute Filme aller Zeiten",
|
||||
"ImportListsTraktSettingsPopularListTypeTrendingMovies": "Filme im Trend",
|
||||
"IndexerSettingsRemoveYear": "Entferne Jahr vom Suchfilter",
|
||||
"AutoTaggingSpecificationStudio": "Filmstudio(s)",
|
||||
"ImportListsRadarrSettingsApiKeyHelpText": "API-KEY von der {appName} Instanz für den Import von (Radarr 3.0 oder älter)",
|
||||
"ImportListsRadarrSettingsFullUrlHelpText": "URL, mit Port von der {appName} Instanz für den Import von (Radarr 3.0 oder älter)",
|
||||
"AutoTaggingSpecificationKeyword": "Schlüsselwort(e)",
|
||||
"BlocklistedAt": "Zur Sperrliste hinzugefügt am {date}",
|
||||
"CloneImportList": "Import Liste duplizieren",
|
||||
"FileSize": "Dateigröße",
|
||||
"NotificationsPushcutSettingsMetadataLinks": "Metadaten-Links",
|
||||
"NotificationsPushcutSettingsMetadataLinksHelpText": "Füge Links zu den Serienmetadaten hinzu, wenn Benachrichtigungen gesendet werden",
|
||||
"DownloadClientItemErrorMessage": "{clientName} meldet einen Fehler: {message}",
|
||||
"ImportListsTraktSettingsPopularListTypeTopBoxOfficeMovies": "Top-Kinokassenfilme",
|
||||
"ImportListsTraktSettingsPopularListTypeTopAnticipatedMovies": "Meist erwartete Filme",
|
||||
"CountMissingMoviesFromLibrary": "{count} fehlende(r) Film(e) in der Bibliothek",
|
||||
"ICalReleaseTypes": "Veröffentlichungstypen",
|
||||
"ICalReleaseTypesMoviesHelpText": "Nur Filme mit bestimmten Veröffentlichungstypen im iCal-Feed einbeziehen. Wenn nicht angegeben, werden alle Optionen verwendet.",
|
||||
"IndexerFileListSettingsCategoriesHelpText": "Kategorien zur Verwendung in Suche und Feeds. Wenn nicht angegeben, werden alle Optionen verwendet.",
|
||||
"ImportListsTraktSettingsGenresMovieHelpText": "Filme nach Trakt-Genre-Slug filtern (durch Kommas getrennt) – nur für beliebte Listen",
|
||||
"SelectMovieModalTitle": "{modalTitle} – Ordner auswählen",
|
||||
"MovieEditRootFolderHelpText": "Durch das Verschieben von Serien in denselben Stammordner können Serienordner umbenannt werden, um sie an den aktualisierten Titel oder das Benennungsformat anzupassen",
|
||||
"UpdateMoviePath": "Update-Serie-Pfad",
|
||||
"NotificationsAppriseSettingsIncludePoster": "Film-Poster einbeziehen",
|
||||
"NotificationsAppriseSettingsIncludePosterHelpText": "Film-Poster in Nachricht einbeziehen",
|
||||
"Keywords": "Schlüsselwort(e)",
|
||||
"ShowCinemaRelease": "Erscheinungsdatum des Kinos anzeigen",
|
||||
"ShowDigitalRelease": "Erscheinungsdatum des Kinos anzeigen",
|
||||
"ShowPhysicalRelease": "Disc Veröffentlichungsdatum",
|
||||
"RemoveRootFolderMoviesMessageText": "Sind sie sicher dass Sie den Stammordner '{name}' löschen möchten? Dateien und Ordner werden nicht gelöscht. Künstler in diesem Stammordner werden nicht von {appName} entfernt.",
|
||||
"NotificationsPushcutSettingsIncludePoster": "Film-Poster einbeziehen",
|
||||
"NotificationsPushoverSettingsTtl": "Lebenszeit",
|
||||
"NotificationsPushoverSettingsTtlHelpText": "Zeit in Sekunden bevor die Nachricht abläuft. Auf 0 setzen für unendliche Dauer",
|
||||
"FilterMoviePropertiesOnlyNotFileWarning": "Filter sind für die Eigenschaften eines Filmes verfügbar, sie sind nicht für die Eigenschaften von Dateien vorgesehen.",
|
||||
"IndexerSettingsRemoveYearHelpText": "Soll {appName} das Jahr nach dem Filter entfernen wenn dieser Indexer durchsucht wird?",
|
||||
"IndexerSettingsRequiredFlags": "Benötigte Markierungen"
|
||||
}
|
||||
|
||||
@@ -1250,5 +1250,72 @@
|
||||
"IndexerHDBitsSettingsCategories": "Κατηγορίες",
|
||||
"IndexerHDBitsSettingsMediums": "Μεσαίο",
|
||||
"IndexerSettingsMinimumSeeders": "Ελάχιστοι σπαρτήρες",
|
||||
"ReleaseProfile": "Προφίλ έκδοσης"
|
||||
"ReleaseProfile": "Προφίλ έκδοσης",
|
||||
"AutoTaggingSpecificationStudio": "Στούντιο",
|
||||
"NotificationsKodiSettingsGuiNotification": "Ειδοποιήσ",
|
||||
"RemoveQueueItemRemovalMethodHelpTextWarning": "Η κατάργηση θα καταργήσει τη λήψη και τα αρχεία από τον πελάτη λήψης.",
|
||||
"SetIndexerFlags": "Σημαίες ευρετηρίου",
|
||||
"ImportListsTraktSettingsListType": "Τύπος καλλιτέχνη",
|
||||
"PendingDownloadClientUnavailable": "Ο πελάτης λήψης δεν είναι διαθέσιμος",
|
||||
"ImportListsTraktSettingsYears": "Ετος",
|
||||
"ManageCustomFormats": "Προσαρμοσμένη μορφή κλώνου",
|
||||
"ManageDownloadClients": "Προγράμματα Λήψης",
|
||||
"NotificationsPushcutSettingsNotificationName": "Ειδοποιήσ",
|
||||
"OneMinute": "Λεπτά",
|
||||
"ShowImdbRating": "Αξιολόγηση στο IMDb",
|
||||
"AddCustomFilter": "Custom Φιλτρα",
|
||||
"CustomFormatsSpecificationMinimumSize": "Μέγιστο μέγεθος",
|
||||
"DeleteMovieFolderCountWithFilesConfirmation": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τα επιλεγμένα αρχεία ταινιών;",
|
||||
"DeleteSelectedIndexersMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τα επιλεγμένα αρχεία ταινιών;",
|
||||
"DownloadClientValidationAuthenticationFailure": "Απαιτείται πιστοποίηση",
|
||||
"DownloadIgnoredMovieTooltip": "Λήψη Εισήχθη",
|
||||
"EditDownloadClientImplementation": "Προσθήκη",
|
||||
"EditImportListImplementation": "Προσθήκη",
|
||||
"EditSelectedIndexers": "Αναζήτηση ευρετηρίων",
|
||||
"FavoriteFolderRemove": "Κατάργηση ριζικού φακέλου",
|
||||
"ShowDigitalRelease": "Εμφάνιση ημερομηνίας κυκλοφορίας κινηματογράφου",
|
||||
"Donate": "Ημερομηνία",
|
||||
"ShowTmdbRating": "Αξιολόγηση TMDb",
|
||||
"SkipRedownloadHelpText": "Αποτρέπει το {appName} από το να δοκιμάσει τη λήψη εναλλακτικών εκδόσεων για τα αφαιρεμένα στοιχεία",
|
||||
"Trending": "εκκρεμής",
|
||||
"LanguagesLoadError": "Δεν είναι δυνατή η φόρτωση ετικετών",
|
||||
"CustomFormatJson": "Προσαρμοσμένη μορφή",
|
||||
"OnFileUpgrade": "Κατά την αναβάθμιση",
|
||||
"ReleaseHash": "Ημερομηνίες κυκλοφορίας",
|
||||
"ShowCinemaRelease": "Εμφάνιση ημερομηνίας κυκλοφορίας κινηματογράφου",
|
||||
"TraktRating": "Βαθμολογία ντομάτας",
|
||||
"NotificationsEmbySettingsSendNotifications": "Ειδοποιήσεις",
|
||||
"MinimumCustomFormatScoreIncrement": "Ελάχιστη βαθμολογία προσαρμοσμένης μορφής",
|
||||
"AddDownloadClientImplementation": "Προσθήκη - {implementationName}",
|
||||
"AddImportList": "Τόπος αγώνων",
|
||||
"AddImportListImplementation": "Προσθήκη - {implementationName}",
|
||||
"AddCondition": "Προσθήκη Σύνδεσης",
|
||||
"LogSizeLimit": "Όριο μεγέθους",
|
||||
"ClearBlocklistMessageText": "Είστε σίγουροι πως θέλετε να διαγράψετε τα επιλεγμένα αντικείμενα από τη λίστα αποκλεισμού;",
|
||||
"ManageFormats": "Προσαρμοσμένη μορφή κλώνου",
|
||||
"DeleteSelectedCustomFormatsMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τα επιλεγμένα αρχεία ταινιών;",
|
||||
"DeleteSelectedDownloadClientsMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τα επιλεγμένα αρχεία ταινιών;",
|
||||
"AudioLanguages": "Πολλαπλών γλωσσών",
|
||||
"DeleteSelectedImportListsMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τα επιλεγμένα αρχεία ταινιών;",
|
||||
"AuthenticationMethod": "Αυθεντικοποίηση",
|
||||
"AutoTaggingSpecificationGenre": "Είδη",
|
||||
"DeleteAutoTag": "Διαγραφή ετικέτας",
|
||||
"DeleteMovieFolderCountConfirmation": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τα επιλεγμένα αρχεία ταινιών;",
|
||||
"Destination": "Διαδρομή προορισμού",
|
||||
"DownloadClientSettingsOlderPriority": "Προτεραιότητα ευρετηρίου",
|
||||
"RemoveQueueItemsRemovalMethodHelpTextWarning": "Η κατάργηση θα καταργήσει τη λήψη και τα αρχεία από τον πελάτη λήψης.",
|
||||
"SelectIndexerFlags": "Αναζήτηση ευρετηρίων",
|
||||
"DownloadClientFloodSettingsStartOnAdd": "Αναζήτηση στο Προσθήκη",
|
||||
"EditSelectedDownloadClients": "Διαγραφή προγράμματος-πελάτη λήψης",
|
||||
"EditSelectedImportLists": "Διαγραφή επιλεγμένου καλλιτέχνη",
|
||||
"NotificationsKodiSettingsCleanLibrary": "Καθαρό επίπεδο βιβλιοθήκης",
|
||||
"CloneImportList": "Διαγραφή λίστας εισαγωγής",
|
||||
"FormatRuntimeMinutes": "Λεπτά",
|
||||
"MassSearchCancelWarning": "Αυτό δεν μπορεί να ακυρωθεί μόλις ξεκινήσει η απενεργοποίηση όλων των ευρετηριωτών σας.",
|
||||
"ShowPhysicalRelease": "Ημερομηνία φυσικής κυκλοφορίας",
|
||||
"ShowRottenTomatoesRating": "Βαθμολογία ντομάτας",
|
||||
"MovieDownloaded": "Λήψη Εισήχθη",
|
||||
"ShowTraktRating": "Βαθμολογία ντομάτας",
|
||||
"ReleasePush": "Ημερομηνίες κυκλοφορίας",
|
||||
"CinemaRelease": "Εμφάνιση ημερομηνίας κυκλοφορίας κινηματογράφου"
|
||||
}
|
||||
|
||||
@@ -698,6 +698,7 @@
|
||||
"FilterNotInNext": "not in the next",
|
||||
"FilterStartsWith": "starts with",
|
||||
"Filters": "Filters",
|
||||
"FilterMoviePropertiesOnlyNotFileWarning": "Filters are available only for the properties of a movie, they are not available for properties of the file(s) you may have for that movie.",
|
||||
"FirstDayOfWeek": "First Day of Week",
|
||||
"Fixed": "Fixed",
|
||||
"Folder": "Folder",
|
||||
@@ -1328,6 +1329,10 @@
|
||||
"NotificationsPushBulletSettingsDeviceIds": "Device IDs",
|
||||
"NotificationsPushBulletSettingsDeviceIdsHelpText": "List of device IDs (leave blank to send to all devices)",
|
||||
"NotificationsPushcutSettingsApiKeyHelpText": "API Keys can be managed in the Account view of the Pushcut app",
|
||||
"NotificationsPushcutSettingsIncludePoster": "Include Poster",
|
||||
"NotificationsPushcutSettingsIncludePosterHelpText": "Include poster with notification",
|
||||
"NotificationsPushcutSettingsMetadataLinks": "Metadata Links",
|
||||
"NotificationsPushcutSettingsMetadataLinksHelpText": "Add a links to series metadata when sending notifications",
|
||||
"NotificationsPushcutSettingsNotificationName": "Notification Name",
|
||||
"NotificationsPushcutSettingsNotificationNameHelpText": "Notification name from Notifications tab of the Pushcut app",
|
||||
"NotificationsPushcutSettingsTimeSensitive": "Time Sensitive",
|
||||
@@ -1335,11 +1340,13 @@
|
||||
"NotificationsPushoverSettingsDevices": "Devices",
|
||||
"NotificationsPushoverSettingsDevicesHelpText": "List of device names (leave blank to send to all devices)",
|
||||
"NotificationsPushoverSettingsExpire": "Expire",
|
||||
"NotificationsPushoverSettingsExpireHelpText": "Maximum time to retry Emergency alerts, maximum 86400 seconds\"",
|
||||
"NotificationsPushoverSettingsExpireHelpText": "Maximum time to retry Emergency alerts, maximum 86400 seconds",
|
||||
"NotificationsPushoverSettingsRetry": "Retry",
|
||||
"NotificationsPushoverSettingsRetryHelpText": "Interval to retry Emergency alerts, minimum 30 seconds",
|
||||
"NotificationsPushoverSettingsSound": "Sound",
|
||||
"NotificationsPushoverSettingsSoundHelpText": "Notification sound, leave blank to use the default",
|
||||
"NotificationsPushoverSettingsTtl": "Time To Live",
|
||||
"NotificationsPushoverSettingsTtlHelpText": "Time in seconds before the message expires. Set to 0 for unlimited duration",
|
||||
"NotificationsPushoverSettingsUserKey": "User Key",
|
||||
"NotificationsSendGridSettingsApiKeyHelpText": "The API Key generated by SendGrid",
|
||||
"NotificationsSettingsUpdateLibrary": "Update Library",
|
||||
@@ -2033,4 +2040,4 @@
|
||||
"Yesterday": "Yesterday",
|
||||
"YesterdayAt": "Yesterday at {time}",
|
||||
"YouCanAlsoSearch": "You can also search using TMDb ID or IMDb ID of a movie. e.g. `tmdb:71663`"
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user