mirror of
https://github.com/Radarr/Radarr
synced 2025-12-06 16:32:36 +01:00
Compare commits
94 commits
v5.27.5.10
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
dae5e86b2c | ||
|
|
71f032d175 | ||
|
|
5a6db29dbd |
120 changed files with 3297 additions and 703 deletions
|
|
@ -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.5'
|
||||
majorVersion: '6.1.0'
|
||||
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 &
|
||||
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ function QueueStatus(props: QueueStatusProps) {
|
|||
|
||||
if (trackedDownloadState === 'importing') {
|
||||
title += ` - ${translate('Importing')}`;
|
||||
iconKind = kinds.PURPLE;
|
||||
iconKind = kinds.PRIMARY;
|
||||
}
|
||||
|
||||
if (trackedDownloadState === 'failedPending') {
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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];
|
||||
};
|
||||
|
|
@ -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')} />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import React, { SyntheticEvent, useCallback, useState } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useSelect } from 'App/SelectContext';
|
||||
import { MOVIE_SEARCH, REFRESH_MOVIE } from 'Commands/commandNames';
|
||||
import Icon from 'Components/Icon';
|
||||
import ImdbRating from 'Components/ImdbRating';
|
||||
|
|
@ -70,6 +69,7 @@ function MovieIndexPoster(props: MovieIndexPosterProps) {
|
|||
monitored,
|
||||
status,
|
||||
images,
|
||||
titleSlug,
|
||||
tmdbId,
|
||||
imdbId,
|
||||
youTubeTrailerId,
|
||||
|
|
@ -142,30 +142,7 @@ function MovieIndexPoster(props: MovieIndexPosterProps) {
|
|||
setIsDeleteMovieModalOpen(false);
|
||||
}, [setIsDeleteMovieModalOpen]);
|
||||
|
||||
const [selectState, selectDispatch] = useSelect();
|
||||
|
||||
const onSelectPress = useCallback(
|
||||
(event: SyntheticEvent<HTMLElement, MouseEvent>) => {
|
||||
if (event.nativeEvent.ctrlKey || event.nativeEvent.metaKey) {
|
||||
window.open(`/movie/${tmdbId}`, '_blank');
|
||||
return;
|
||||
}
|
||||
|
||||
const shiftKey = event.nativeEvent.shiftKey;
|
||||
|
||||
selectDispatch({
|
||||
type: 'toggleSelected',
|
||||
id: movieId,
|
||||
isSelected: !selectState.selectedState[movieId],
|
||||
shiftKey,
|
||||
});
|
||||
},
|
||||
[movieId, selectState.selectedState, selectDispatch, tmdbId]
|
||||
);
|
||||
|
||||
const link = `/movie/${tmdbId}`;
|
||||
|
||||
const linkProps = isSelectMode ? { onPress: onSelectPress } : { to: link };
|
||||
const link = `/movie/${titleSlug}`;
|
||||
|
||||
const elementStyle = {
|
||||
width: `${posterWidth}px`,
|
||||
|
|
@ -175,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
|
||||
|
|
@ -220,7 +199,7 @@ function MovieIndexPoster(props: MovieIndexPosterProps) {
|
|||
<div className={styles.deleted} title={translate('Deleted')} />
|
||||
) : null}
|
||||
|
||||
<Link className={styles.link} style={elementStyle} {...linkProps}>
|
||||
<Link className={styles.link} style={elementStyle} to={link}>
|
||||
<MoviePoster
|
||||
style={elementStyle}
|
||||
images={images}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -114,6 +114,11 @@ const movieTokens = [
|
|||
example: 'The Movie Collection',
|
||||
footNotes: '1',
|
||||
},
|
||||
{
|
||||
token: '{Movie CollectionThe}',
|
||||
example: 'Movie Collection, The',
|
||||
footNotes: '1',
|
||||
},
|
||||
{ token: '{Movie Certification}', example: 'R' },
|
||||
{ token: '{Release Year}', example: '2009' },
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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 @@ public class BuildInfoFixture
|
|||
[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 @@ public DestinationAlreadyExistsException(string message, Exception innerExceptio
|
|||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected DestinationAlreadyExistsException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,6 +75,17 @@ private void MigrateAppDataFolder()
|
|||
{
|
||||
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 @@ private void MigrateAppDataFolder()
|
|||
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 @@ private void MoveSqliteDatabase(string source, string destination)
|
|||
|
||||
private void RemovePidFile()
|
||||
{
|
||||
if (OsInfo.IsNotWindows)
|
||||
if (OsInfo.IsNotWindows && _diskProvider.FolderExists(_appFolderInfo.AppDataFolder))
|
||||
{
|
||||
_diskProvider.DeleteFile(Path.Combine(_appFolderInfo.AppDataFolder, "radarr.pid"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 @@
|
|||
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 @@
|
|||
namespace NzbDrone.Core.Test.Http
|
||||
{
|
||||
[TestFixture]
|
||||
[Platform(Exclude = "MacOsX")]
|
||||
public class HttpProxySettingsProviderFixture : TestBase<HttpProxySettingsProvider>
|
||||
{
|
||||
private HttpProxySettings GetProxySettings()
|
||||
|
|
@ -15,24 +16,24 @@ private HttpProxySettings GetProxySettings()
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -55,14 +55,14 @@ public class ReleaseGroupParserFixture : CoreTest
|
|||
[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")]
|
||||
|
|
@ -109,7 +109,8 @@ public void should_parse_release_group(string title, string expected)
|
|||
[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("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 +118,8 @@ public void should_parse_release_group(string title, string expected)
|
|||
[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 +127,16 @@ public void should_parse_release_group(string title, string expected)
|
|||
[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 +145,7 @@ public void should_not_include_extension_in_release_group(string title, string e
|
|||
|
||||
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 +175,7 @@ public void should_not_include_language_in_release_group(string title, string ex
|
|||
|
||||
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 +186,13 @@ public void should_not_include_bad_suffix_in_release_group(string title, string
|
|||
|
||||
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 @@ public void should_not_parse_url_in_name(string postTitle, string title)
|
|||
[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 @@ public AuthenticationType AuthenticationMethod
|
|||
|
||||
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 @@ public void MigrateConfigFile()
|
|||
{
|
||||
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 @@ public interface IBasicRepository<TModel>
|
|||
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 @@ private string GetInsertSql()
|
|||
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 @@ private void UpdateFields(IDbConnection connection, IDbTransaction transaction,
|
|||
|
||||
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 @@ private void UpdateFields(IDbConnection connection, IDbTransaction transaction,
|
|||
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 @@
|
|||
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 @@ public override void Up()
|
|||
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 @@ protected override void MainDbUpgrade()
|
|||
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.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 @@ private string MigrateTransferCharacteristics(string transferCharacteristics)
|
|||
|
||||
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 @@
|
|||
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.Initialization;
|
||||
using FluentMigrator.Runner.Processors;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NLog;
|
||||
using NLog.Extensions.Logging;
|
||||
|
||||
|
|
@ -20,13 +19,10 @@ public interface IMigrationController
|
|||
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 @@ public void Migrate(string connectionString, MigrationContext migrationContext,
|
|||
|
||||
_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.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 @@ public static IDbCommand CreateCommand(this IDbConnection conn, IDbTransaction t
|
|||
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 @@ public NzbDroneSQLiteProcessor(SQLiteDbFactory factory,
|
|||
SQLiteQuoter quoter)
|
||||
: base(factory, generator, logger, options, connectionStringAccessor, serviceProvider, quoter)
|
||||
{
|
||||
_quoter = quoter;
|
||||
}
|
||||
|
||||
public override void Process(AlterColumnExpression expression)
|
||||
|
|
@ -35,7 +38,7 @@ public override void Process(AlterColumnExpression expression)
|
|||
|
||||
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 @@ public override void Process(AlterColumnExpression expression)
|
|||
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 @@ public override void Process(DeleteColumnExpression expression)
|
|||
|
||||
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 @@ public override void Process(RenameColumnExpression expression)
|
|||
|
||||
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 @@ protected virtual void ProcessAlterTable(TableDefinition tableDefinition, List<C
|
|||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -424,8 +424,8 @@ private void AuthenticateClient(HttpRequestBuilder requestBuilder, QBittorrentSe
|
|||
}
|
||||
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 @@ private void AuthenticateClient(HttpRequestBuilder requestBuilder, QBittorrentSe
|
|||
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 @@ private string DownloadFromMagnetUrl(RemoteMovie remoteMovie, IIndexer indexer,
|
|||
|
||||
try
|
||||
{
|
||||
hash = MagnetLink.Parse(magnetUrl).InfoHash.ToHex();
|
||||
hash = MagnetLink.Parse(magnetUrl).InfoHashes.V1OrV2.ToHex();
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -280,6 +280,7 @@ public override MetadataFileResult MovieMetadata(Movie movie, MovieFile movieFil
|
|||
{
|
||||
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 @@ public override HealthCheck Check()
|
|||
{
|
||||
return new HealthCheck(
|
||||
GetType(),
|
||||
HealthCheckResult.Warning,
|
||||
HealthCheckResult.Error,
|
||||
_localizationService.GetLocalizedString(
|
||||
"NamingConfigMovieFolderFormatDeprecatedHealthCheckMessage", new Dictionary<string, object>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ public override IImportListRequestGenerator GetRequestGenerator()
|
|||
|
||||
public override IParseImportListResponse GetParser()
|
||||
{
|
||||
return new IMDbListParser(Settings);
|
||||
return new IMDbListParser(Settings, _logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
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 @@ public override IList<ImportListMovie> ParseResponse(ImportListResponse importLi
|
|||
|
||||
if (!PreProcess(importResponse))
|
||||
{
|
||||
_logger.Debug("IMDb List {0}: Found {1} movies", _settings.ListId, movies.Count);
|
||||
return movies;
|
||||
}
|
||||
|
||||
|
|
@ -34,20 +38,19 @@ public override IList<ImportListMovie> ParseResponse(ImportListResponse importLi
|
|||
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 @@ public class IMDbListRequestGenerator : RadarrList2RequestGeneratorBase
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ protected virtual string GetInfoHash(XElement item)
|
|||
{
|
||||
try
|
||||
{
|
||||
return MagnetLink.Parse(magnetUrl).InfoHash.ToHex();
|
||||
return MagnetLink.Parse(magnetUrl).InfoHashes.V1OrV2.ToHex();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1386,5 +1386,141 @@
|
|||
"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."
|
||||
"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á"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1340,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",
|
||||
|
|
|
|||
|
|
@ -2036,5 +2036,6 @@
|
|||
"NotificationsPushcutSettingsIncludePoster": "Incluir póster",
|
||||
"NotificationsPushcutSettingsIncludePosterHelpText": "Incluir póster con notificación",
|
||||
"NotificationsPushcutSettingsMetadataLinks": "Enlaces de metadatos",
|
||||
"NotificationsPushcutSettingsMetadataLinksHelpText": "Añade un enlace a los metadatos de las series cuando se envían notificaciones"
|
||||
"NotificationsPushcutSettingsMetadataLinksHelpText": "Añade un enlace a los metadatos de las series cuando se envían notificaciones",
|
||||
"FilterMoviePropertiesOnlyNotFileWarning": "Los filtros están disponibles solo para las propiedades de una película, no para las propiedades del archivo(s) que puedas tener para esa película."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2036,5 +2036,6 @@
|
|||
"NotificationsPushcutSettingsMetadataLinks": "Lien de métadonnées",
|
||||
"NotificationsPushcutSettingsMetadataLinksHelpText": "Ajouter un lien vers les métadonnées de la série lors de l'envoi de notifications",
|
||||
"ImportListsTraktSettingsGenresMovieHelpText": "Filtrer les films par genre Trakt (séparés par des virgules) Uniquement pour les listes populaires",
|
||||
"ReleasePush": "Poussée de version"
|
||||
"ReleasePush": "Poussée de version",
|
||||
"FilterMoviePropertiesOnlyNotFileWarning": "Les filtres sont seulement disponibles pour les propriétés d'un film, ils ne sont pas disponibles pour les propriétés du ou des fichiers que vous pouvez avoir pour ce film."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1265,5 +1265,95 @@
|
|||
"IndexerHDBitsSettingsCodecs": "Codec",
|
||||
"IndexerHDBitsSettingsMediums": "Gemiddeld",
|
||||
"IndexerSettingsCategories": "Categorieën",
|
||||
"ReleaseProfile": "releaseprofiel"
|
||||
"ReleaseProfile": "releaseprofiel",
|
||||
"DownloadClientFloodSettingsAddPaused": "Toevoegen gepauzeerd",
|
||||
"ImportListsTraktSettingsAdditionalParameters": "Aanvullende parameters",
|
||||
"IndexerHDBitsSettingsCategoriesHelpText": "Als niet gespecificeerd, worden alle opties gebruikt.",
|
||||
"IndexerSettingsRejectBlocklistedTorrentHashesHelpText": "Als een torrent wordt geblokkeerd op basis van de hash, kan het zijn dat deze niet correct wordt geweigerd tijdens RSS/Search bij sommige indexers. Als je deze optie inschakelt, wordt de torrent alsnog geweigerd nadat hij is opgehaald, maar voordat hij naar de client wordt gestuurd.",
|
||||
"NotificationsDiscordSettingsAuthor": "Auteur",
|
||||
"NotificationsGotifySettingsAppToken": "App Token",
|
||||
"UsenetBlackholeNzbFolder": "Nzb map",
|
||||
"IndexerSettingsApiUrl": "API URL",
|
||||
"DefaultNameCopiedSpecification": "{name} - Kopie",
|
||||
"DefaultNameCopiedImportList": "{name} - Kopie",
|
||||
"DeleteSelectedDownloadClientsMessageText": "Bent u zeker dat u de indexeerder '{count}' wilt verwijderen?",
|
||||
"DownloadClientFreeboxSettingsAppIdHelpText": "App ID die wordt gegeven bij het aanmaken van toegang tot de Freebox API (bijv. 'app_id')",
|
||||
"DownloadClientFreeboxSettingsAppToken": "App Token",
|
||||
"DownloadClientFreeboxSettingsAppTokenHelpText": "App token dat wordt verkregen bij het aanmaken van toegang tot de Freebox API (bijv. 'app_token')",
|
||||
"DownloadClientPneumaticSettingsNzbFolderHelpText": "Deze map moet bereikbaar zijn vanuit XBMC",
|
||||
"DownloadClientRTorrentSettingsAddStoppedHelpText": "Als je dit inschakelt, worden torrents en magnet-links in rTorrent in een gestopte staat toegevoegd. Dit kan magnet-bestanden laten mislukken.",
|
||||
"DownloadClientSettingsUseSslHelpText": "Gebruik een beveiligde verbinding bij het verbinden met {clientName}",
|
||||
"DownloadClientTransmissionSettingsUrlBaseHelpText": "Voegt een voorvoegsel toe aan de {clientName} RPC-URL, bijvoorbeeld {url}, standaard is '{defaultUrl}'",
|
||||
"DownloadClientValidationSslConnectFailureDetail": "{appName} kan niet verbinden met {clientName} via SSL. Dit probleem kan computergerelateerd zijn. Probeer alstublieft om {appName} en {clientName} te configureren om geen SSL te gebruiken.",
|
||||
"DownloadClientValidationTestTorrents": "Kon de lijst van torrents niet verkrijgen: {exceptionMessage}",
|
||||
"External": "Extern",
|
||||
"Implementation": "Implementatie",
|
||||
"IndexerSettingsApiUser": "API Gebruiker",
|
||||
"DownloadClientValidationCategoryMissingDetail": "De ingevoerde categorie bestaat niet in {clientName}. Voeg deze eerst toe in {clientName}.",
|
||||
"FailedToFetchUpdates": "Updates ophalen mislukt",
|
||||
"IndexerSettingsAdditionalParameters": "Aanvullende parameters",
|
||||
"DownloadClientDelugeSettingsUrlBaseHelpText": "Voegt een voorvoegsel toe aan de Deluge JSON-URL, zie {url}",
|
||||
"DownloadClientPneumaticSettingsStrmFolder": "Strm Map",
|
||||
"DownloadClientQbittorrentSettingsContentLayoutHelpText": "Of de ingestelde inhoudsindeling van qBittorrent gebruikt moet worden, de originele indeling van de torrent, of altijd een submap moet worden aangemaakt (qBittorrent 4.3.2+)",
|
||||
"DownloadClientQbittorrentSettingsFirstAndLastFirst": "Eerste en laatste eerst",
|
||||
"DownloadClientQbittorrentSettingsFirstAndLastFirstHelpText": "Download eerste en laatste stukjes eerst (qBittorrent 4.1.0+)",
|
||||
"DownloadClientRTorrentSettingsDirectoryHelpText": "Optionele locatie om downloads op te slaan, leeg laten om de standaardlocatie van rTorrent te gebruiken",
|
||||
"DownloadClientRTorrentSettingsUrlPathHelpText": "Pad naar het XMLRPC-eindpunt, zie {url}. Dit is meestal RPC2 of [pad naar ruTorrent]{url2} bij gebruik van ruTorrent.",
|
||||
"DownloadClientSettingsAddPaused": "Toevoegen gepauzeerd",
|
||||
"DownloadClientValidationAuthenticationFailure": "Authenticatiefout",
|
||||
"DownloadClientValidationAuthenticationFailureDetail": "Gelieve uw gebruikersnaam en wachtwoord te verifiëren. Verifieer ook of de host waar {appName} op draait niet geblokkeerd is voor toegang tot {clientName} door Whitelist limitaties in de {clientName} instellingen.",
|
||||
"DownloadClientValidationCategoryMissing": "Categorie bestaat niet",
|
||||
"DownloadClientValidationVerifySsl": "Verifieer SSL instellingen",
|
||||
"DownloadClientValidationVerifySslDetail": "Gelieve uw SSL-configuratie te verifiëren in zowel {clientName} als {appName}",
|
||||
"IndexerHDBitsSettingsCodecsHelpText": "Als niet gespecificeerd, worden alle opties gebruikt.",
|
||||
"IndexerSettingsApiPath": "API pad",
|
||||
"IndexerSettingsApiPathHelpText": "Pad naar de API, meestal {url}",
|
||||
"DownloadClientFloodSettingsUrlBaseHelpText": "Voegt een voorvoegsel toe aan de Flood API, zoals {url}",
|
||||
"DownloadClientFreeboxSettingsApiUrlHelpText": "Stel de basis-URL van de Freebox API in met API-versie, bijvoorbeeld '{url}', standaard is '{defaultApiUrl}'",
|
||||
"DownloadClientFreeboxSettingsAppId": "App ID",
|
||||
"DownloadClientFreeboxSettingsHostHelpText": "Hostnaam of IP-adres van de Freebox, standaard '{url}' (werkt alleen als je op hetzelfde netwerk zit)",
|
||||
"DownloadClientFreeboxSettingsPortHelpText": "Poort die wordt gebruikt om toegang te krijgen tot de Freebox-interface, standaard '{port}'",
|
||||
"DownloadClientNzbgetSettingsAddPausedHelpText": "Deze optie vereist minimaal NzbGet versie 16.0",
|
||||
"DownloadClientQbittorrentSettingsUseSslHelpText": "Gebruik een beveiligde verbinding. Zie Opties -> Web UI -> 'Gebruik HTTPS in plaats van HTTP' in qBittorrent.",
|
||||
"IndexerDownloadClientHealthCheckMessage": "Indexeerders met ongeldige downloadclients: {indexerNames}.",
|
||||
"IndexerHDBitsSettingsMediumsHelpText": "Als niet gespecificeerd, worden alle opties gebruikt.",
|
||||
"IndexerNewznabSettingsAdditionalParametersHelpText": "Aanvullende Newznab-parameters",
|
||||
"Default": "Standaard",
|
||||
"Destination": "Bestemming",
|
||||
"DownloadClientDownloadStationSettingsDirectoryHelpText": "Optionele gedeelde map om downloads in te plaatsen, leeg laten om de standaardlocatie van Download Station te gebruiken",
|
||||
"DownloadClientFloodSettingsAdditionalTagsHelpText": "Voegt eigenschappen van media toe als tags. Hints zijn voorbeelden.",
|
||||
"DownloadClientFloodSettingsTagsHelpText": "Initiële tags van een download. Om herkend te worden, moet een download alle initiële tags hebben. Dit voorkomt conflicten met niet-gerelateerde downloads.",
|
||||
"DownloadClientFreeboxSettingsApiUrl": "API URL",
|
||||
"DownloadClientRTorrentSettingsAddStopped": "Toevoegen gestopt",
|
||||
"DownloadClientRTorrentSettingsUrlPath": "Url pad",
|
||||
"DownloadClientSettingsInitialState": "Initiële staat",
|
||||
"DownloadClientUTorrentTorrentStateError": "uTorrent rapporteert een fout",
|
||||
"DownloadClientTransmissionSettingsDirectoryHelpText": "Optionele locatie om downloads op te slaan, laat leeg om de standaardlocatie van Transmission te gebruiken",
|
||||
"DownloadClientValidationGroupMissingDetail": "De ingevoerde groep bestaat niet in {clientName}. Voeg deze eerst toe in {clientName}.",
|
||||
"DownloadClientValidationSslConnectFailure": "Kan niet verbinden via SSL",
|
||||
"DownloadClientValidationTestNzbs": "Kon de lijst van NZBs niet verkrijgen: {exceptionMessage}",
|
||||
"DownloadClientValidationUnableToConnectDetail": "Gelieve de hostnaam en poort te verifiëren.",
|
||||
"DownloadClientVuzeValidationErrorVersion": "Protocolversie niet ondersteund, gebruik Vuze 5.0.0.0 of hoger met de Vuze Web Remote plugin.",
|
||||
"IndexerSettingsCookie": "Cookie",
|
||||
"DownloadClientAriaSettingsDirectoryHelpText": "Optionele locatie om downloads op te slaan, leeg laten om de standaardlocatie van Aria2 te gebruiken",
|
||||
"DownloadClientFloodSettingsAdditionalTags": "Additionele Tags",
|
||||
"DownloadClientPneumaticSettingsStrmFolderHelpText": ".strm-bestanden in deze map worden geïmporteerd door Drone",
|
||||
"DefaultNameCopiedProfile": "{name} - Kopie",
|
||||
"DeleteSelectedIndexersMessageText": "Bent u zeker dat u de indexeerder '{count}' wilt verwijderen?",
|
||||
"DownloadClientSettingsDestinationHelpText": "Geef handmatig de downloadbestemming op, laat leeg om de standaardlocatie te gebruiken",
|
||||
"DownloadClientSettingsInitialStateHelpText": "Begintoestand voor torrents die aan {clientName} worden toegevoegd",
|
||||
"DownloadClientValidationErrorVersion": "{clientName} versie moet tenminste {requiredVersion} zijn. Gerapporteerde versie is {reportedVersion}",
|
||||
"DownloadClientValidationGroupMissing": "Groep bestaat niet",
|
||||
"DownloadClientValidationUnableToConnect": "Kon niet verbinden met {clientName}",
|
||||
"DownloadClientUTorrentProviderMessage": "uTorrent heeft een geschiedenis van het bevatten van cryptominers, malware en advertenties. We raden je sterk aan om een andere client te kiezen.",
|
||||
"DownloadClientValidationApiKeyIncorrect": "API-sleutel Incorrect",
|
||||
"DownloadClientValidationApiKeyRequired": "API-sleutel Nodig",
|
||||
"EditSelectedDownloadClients": "Geselecteerde downloadclients bewerken",
|
||||
"EditSelectedIndexers": "Geselecteerde indexeerders bewerken",
|
||||
"DownloadClientPneumaticSettingsNzbFolder": "Nzb map",
|
||||
"DownloadClientQbittorrentSettingsInitialStateHelpText": "Begintoestand voor torrents die aan qBittorrent worden toegevoegd. Let op: Gedwongen torrents houden zich niet aan seeding-beperkingen",
|
||||
"DownloadClientQbittorrentSettingsSequentialOrder": "Opeenvolgende volgorde",
|
||||
"DownloadClientQbittorrentSettingsSequentialOrderHelpText": "Download in opeenvolgende volgorde (qBittorrent 4.1.0+)",
|
||||
"DownloadClientValidationUnknownException": "Onbekende fout: {exception}",
|
||||
"Donate": "Doneer"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,16 +10,16 @@
|
|||
"AppDataLocationHealthCheckMessage": "Aktualizacja nie będzie możliwa w celu uniknięcia usunięcia danych aplikacji",
|
||||
"Analytics": "Analityka",
|
||||
"All": "Wszystkie",
|
||||
"Agenda": "Lista",
|
||||
"Agenda": "Terminarz",
|
||||
"AddNewTmdbIdMessage": "Możesz również użyć ID TMDb, np. 'tmdb:71663'",
|
||||
"AddNewMessage": "Dodanie nowego filmu jest bardzo łatwe, po prostu zacznij wpisywać nazwę jakiegoś filmu",
|
||||
"AddNewMessage": "Dodanie nowego filmu jest bardzo łatwe, po prostu zacznij wpisywać nazwę",
|
||||
"AddNew": "Dodaj nowy",
|
||||
"AddMovies": "Dodaj filmy",
|
||||
"AddExclusion": "Dodaj wyjątek",
|
||||
"Added": "Dodane",
|
||||
"Activity": "Aktywność",
|
||||
"Actions": "Akcje",
|
||||
"About": "O",
|
||||
"About": "Informacje",
|
||||
"Component": "Składnik",
|
||||
"DeleteMovieFolder": "Usuń folder filmu",
|
||||
"FileNameTokens": "Tokeny nazw plików",
|
||||
|
|
@ -74,7 +74,7 @@
|
|||
"CustomFormatHelpText": "{appName} ocenia każde wydanie, używając sumy wyników za dopasowanie niestandardowych formatów. Jeśli nowa wersja poprawiłaby wynik, przy tej samej lub lepszej jakości, {appName} ją złapie.",
|
||||
"CustomFormats": "Formaty niestandardowe",
|
||||
"Ended": "Zakończone",
|
||||
"CutoffUnmet": "Odcięcie niespełnione",
|
||||
"CutoffUnmet": "Nie spełnia wymagań",
|
||||
"Deleted": "Usunięto",
|
||||
"DeleteFile": "Usunąć plik",
|
||||
"DeleteSelectedMovieFiles": "Usuń wybrane pliki filmowe",
|
||||
|
|
@ -85,7 +85,7 @@
|
|||
"EditCustomFormat": "Edytuj format niestandardowy",
|
||||
"EnableCompletedDownloadHandlingHelpText": "Automatycznie importuj ukończone pliki do pobrania z klienta pobierania",
|
||||
"SearchIsNotSupportedWithThisIndexer": "Wyszukiwanie nie jest obsługiwane przez ten indeksator",
|
||||
"AnalyseVideoFilesHelpText": "Wyodrębnij z plików informacje wideo, takie jak rozdzielczość, czas działania i kodeki. Wymaga to odczytu przez {appName} części pliku, które mogą powodować dużą aktywność dysku lub sieci podczas skanowania.",
|
||||
"AnalyseVideoFilesHelpText": "Wyodrębnij z plików informacje o wideo, takie jak rozdzielczość, czas trwania i kodeki. Wymaga to odczytu przez {appName} części pliku, które mogą powodować dużą aktywność dysku lub sieci podczas skanowania.",
|
||||
"ChangeFileDateHelpText": "Zmień datę pliku przy imporcie / ponownym skanowaniu",
|
||||
"FollowPerson": "Śledź osobę",
|
||||
"Genres": "Gatunki",
|
||||
|
|
@ -124,9 +124,9 @@
|
|||
"TagsSettingsSummary": "Zobacz wszystkie tagi i sposób ich używania. Nieużywane tagi można usunąć",
|
||||
"TheLogLevelDefault": "Poziom dziennika jest domyślnie ustawiony na „Informacje” i można go zmienić w",
|
||||
"TorrentDelayTime": "Opóźnienie torrenta: {0}",
|
||||
"AddIndexerError": "Nie można dodać nowego indeksatora, spróbuj ponownie.",
|
||||
"AddQualityProfileError": "Nie udało się dodać nowego profilu jakości, spróbuj później.",
|
||||
"AddRemotePathMappingError": "Nie można dodać nowego mapowania ścieżki zdalnej, spróbuj ponownie.",
|
||||
"AddIndexerError": "Nie można dodać nowego indeksera, spróbuj ponownie.",
|
||||
"AddQualityProfileError": "Nie udało się dodać nowego profilu jakości, spróbuj ponownie.",
|
||||
"AddRemotePathMappingError": "Nie udało się dodać nowego mapowania ścieżki zdalnej, spróbuj ponownie.",
|
||||
"BackupsLoadError": "Nie można załadować kopii zapasowych",
|
||||
"ListOptionsLoadError": "Nie można załadować opcji listy",
|
||||
"UnableToLoadRestrictions": "Nie można załadować ograniczeń",
|
||||
|
|
@ -157,7 +157,7 @@
|
|||
"Date": "Data",
|
||||
"AddToDownloadQueue": "Dodaj do kolejki pobierania",
|
||||
"Add": "Dodaj",
|
||||
"AddCustomFormat": "Dodaj format niestandardowy",
|
||||
"AddCustomFormat": "Dodaj niestandardowy format",
|
||||
"AddDelayProfile": "Dodaj profil opóźnienia",
|
||||
"AddDownloadClient": "Dodaj klienta pobierania",
|
||||
"AfterManualRefresh": "Po ręcznym odświeżeniu",
|
||||
|
|
@ -172,7 +172,7 @@
|
|||
"EnableSsl": "Włącz SSL",
|
||||
"EditGroups": "Edytuj grupy",
|
||||
"AllMoviesInPathHaveBeenImported": "Wszystkie filmy w {path} zostały zaimportowane",
|
||||
"AllResultsHiddenFilter": "Wszystkie wyniki są ukrywane przez zastosowany filtr",
|
||||
"AllResultsHiddenFilter": "Wszystkie wyniki są ukryte przez zastosowany filtr",
|
||||
"Always": "Zawsze",
|
||||
"HomePage": "Strona główna",
|
||||
"Hours": "godziny",
|
||||
|
|
@ -389,7 +389,7 @@
|
|||
"FileBrowserPlaceholderText": "Zacznij pisać lub wybierz ścieżkę poniżej",
|
||||
"ApiKey": "Klucz API",
|
||||
"StartupDirectory": "Katalog Startowy",
|
||||
"AddImportListExclusion": "Dodaj wykluczenie z listy",
|
||||
"AddImportListExclusion": "Dodaj wykluczenie z listy importu",
|
||||
"AnalyseVideoFiles": "Analizuj pliki wideo",
|
||||
"Age": "Wiek",
|
||||
"AgeWhenGrabbed": "Wiek (przy złapaniu)",
|
||||
|
|
@ -397,7 +397,7 @@
|
|||
"AllowHardcodedSubsHelpText": "Wykryte wbudowane napisy zostaną automatycznie pobrane",
|
||||
"AlreadyInYourLibrary": "Już w Twojej bibliotece",
|
||||
"SystemTimeHealthCheckMessage": "Czas systemowy jest wyłączony o więcej niż 1 dzień. Zaplanowane zadania mogą nie działać poprawnie, dopóki czas nie zostanie skorygowany",
|
||||
"AnalyticsEnabledHelpText": "Wysyłaj anonimowe informacje o użytkowaniu i błędach do serwerów {appName}. Obejmuje to informacje o Twojej przeglądarce, z których stron {appName} WebUI korzystasz, raportowanie błędów, a także wersję systemu operacyjnego i środowiska wykonawczego. Wykorzystamy te informacje, aby nadać priorytet funkcjom i poprawkom błędów.",
|
||||
"AnalyticsEnabledHelpText": "Wysyłaj anonimowe informacje o użytkowaniu i błędach do serwerów {appName}. Obejmuje to informacje o Twojej przeglądarce, których stron interfejsu webowego {appName} używasz, raportowanie błędów, a także wersję systemu operacyjnego i środowiska wykonawczego. Wykorzystamy te informacje, aby nadać priorytet funkcjom i poprawkom błędów.",
|
||||
"MinimumFreeSpace": "Minimalna wolna przestrzeń",
|
||||
"MinimumLimits": "Minimalne limity",
|
||||
"ReleaseRejected": "Wersja odrzucona",
|
||||
|
|
@ -591,7 +591,7 @@
|
|||
"History": "Historia",
|
||||
"Host": "Gospodarz",
|
||||
"ICalLink": "Łącze do iCal",
|
||||
"IconForCutoffUnmet": "Ikona Cutoff Unmet",
|
||||
"IconForCutoffUnmet": "Ikona dla „Nie spełnia wymagań”",
|
||||
"IgnoredAddresses": "Ignorowane adresy",
|
||||
"UnmonitorDeletedMovies": "Nie monitoruj usuniętych filmów",
|
||||
"IgnoredHelpText": "Zgoda zostanie odrzucona, jeśli zawiera co najmniej jeden termin (bez rozróżniania wielkości liter)",
|
||||
|
|
@ -765,7 +765,7 @@
|
|||
"Script": "Scenariusz",
|
||||
"ScriptPath": "Ścieżka do Skryptu",
|
||||
"SearchAll": "Wyszukaj wszystko",
|
||||
"SearchCutoffUnmet": "Niespełnione Parametry Wyszukiwania",
|
||||
"SearchCutoffUnmet": "Szukaj pozycji niespełniających wymagań",
|
||||
"SearchFailedPleaseTryAgainLater": "Wyszukiwanie nie powiodło się, spróbuj ponownie później.",
|
||||
"SearchFiltered": "Szukaj przefiltrowane",
|
||||
"SearchForMissing": "Wyszukaj brakujące",
|
||||
|
|
@ -797,7 +797,7 @@
|
|||
"WeekColumnHeaderHelpText": "Wyświetlany nad każdą kolumną, gdy tydzień jest aktywnym widokiem",
|
||||
"ListMonitorMovieHelpText": "Czy filmy dodane przez tę listę mają być dodawane i monitorowane",
|
||||
"ICalShowAsAllDayEvents": "Pokaż jako wydarzenia całodniowe",
|
||||
"IconForCutoffUnmetHelpText": "Pokaż ikonę dla plików, gdy odcięcie nie zostało osiągnięte",
|
||||
"IconForCutoffUnmetHelpText": "Pokaż ikonę dla plików, które nie spełniają wymagań",
|
||||
"ShowDateAdded": "Pokaż datę dodania",
|
||||
"ShowMonitoredHelpText": "Pokaż monitorowany status pod plakatem",
|
||||
"ShowMovieInformation": "Pokaż informacje o filmie",
|
||||
|
|
@ -838,7 +838,7 @@
|
|||
"Status": "Status",
|
||||
"Studio": "Studio",
|
||||
"Style": "Styl",
|
||||
"AddNewMovieRootFolderHelpText": "Podfolder „{0}” zostanie utworzony automatycznie",
|
||||
"AddNewMovieRootFolderHelpText": "Podfolder '{folder}' zostanie utworzony automatycznie",
|
||||
"Sunday": "niedziela",
|
||||
"Table": "Stół",
|
||||
"TableOptions": "Opcje tabeli",
|
||||
|
|
@ -875,9 +875,9 @@
|
|||
"AddConditionError": "Nie można dodać nowego warunku, spróbuj ponownie.",
|
||||
"AddCustomFormatError": "Nie można dodać nowego formatu niestandardowego, spróbuj ponownie.",
|
||||
"AddDownloadClientError": "Nie można dodać nowego klienta pobierania, spróbuj ponownie.",
|
||||
"AddImportListExclusionError": "Nie można dodać nowego wykluczenia listy, spróbuj ponownie.",
|
||||
"AddImportListExclusionError": "Nie można dodać nowego wyjątku listy, spróbuj ponownie.",
|
||||
"AddListError": "Nie można dodać nowej listy, spróbuj ponownie.",
|
||||
"AddNotificationError": "Nie udało się dodać nowego powiadomienia, spróbuj później.",
|
||||
"AddNotificationError": "Nie udało się dodać nowego powiadomienia, spróbuj ponownie.",
|
||||
"CustomFormatsLoadError": "Nie można załadować formatów niestandardowych",
|
||||
"DelayProfilesLoadError": "Nie można załadować profili opóźnień",
|
||||
"DownloadClientOptionsLoadError": "Nie można załadować opcji klienta pobierania",
|
||||
|
|
@ -923,7 +923,7 @@
|
|||
"Version": "Wersja",
|
||||
"VisitTheWikiForMoreDetails": "Odwiedź wiki, aby uzyskać więcej informacji: ",
|
||||
"WaitingToProcess": "Czekam na przetworzenie",
|
||||
"Wanted": "Chciał",
|
||||
"Wanted": "Poszukiwany",
|
||||
"Warn": "Ostrzeż",
|
||||
"Week": "Tydzień",
|
||||
"Weeks": "Tygodni",
|
||||
|
|
@ -1045,7 +1045,7 @@
|
|||
"CollectionShowOverviewsHelpText": "Pokaż przegląd kolekcji",
|
||||
"TotalMovies": "Filmów całkowicie",
|
||||
"RottenTomatoesRating": "Ocena Tomato",
|
||||
"ApplicationUrlHelpText": "Zewnętrzny URL tej aplikacji zawierający http(s)://, port i adres URL",
|
||||
"ApplicationUrlHelpText": "Zewnętrzny link tej aplikacji zawierający http(s)://, port i adres URL",
|
||||
"ApplicationURL": "Link do aplikacji",
|
||||
"File": "Plik",
|
||||
"Language": "Język",
|
||||
|
|
@ -1065,22 +1065,22 @@
|
|||
"DeleteImportListExclusionMessageText": "Czy na pewno chcesz usunąć to wykluczenie listy importu?",
|
||||
"RemoveSelectedItemQueueMessageText": "Czy na pewno chcesz usunąć {0} element {1} z kolejki?",
|
||||
"ApplyTagsHelpTextAdd": "Dodaj: dodaj tagi do istniejącej listy tagów",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "Jak zastosować tagi do wybranych indeksatorów",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "Jak stosować tagi do wybranych indekserów",
|
||||
"ApplyTagsHelpTextRemove": "Usuń: usuń wprowadzone tagi",
|
||||
"ApplyTagsHelpTextReplace": "Zastąp: Zastąp tagi wprowadzonymi tagami (nie wprowadzaj tagów, aby usunąć wszystkie tagi)",
|
||||
"ApplyTagsHelpTextHowToApplyMovies": "Jak zastosować tagi do wybranych filmów",
|
||||
"ApplyTagsHelpTextHowToApplyMovies": "Jak stosować tagi do wybranych filmów",
|
||||
"ApplyChanges": "Zastosuj zmiany",
|
||||
"AddCondition": "Dodaj warunek",
|
||||
"AutoTagging": "Automatyczne tagowanie",
|
||||
"AllTitles": "Wszystkie tytuły",
|
||||
"ApplyTagsHelpTextHowToApplyImportLists": "Jak zastosować tagi do wybranych list",
|
||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "Jak",
|
||||
"ApplyTagsHelpTextHowToApplyImportLists": "Jak stosować tagi do wybranych list importu",
|
||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "Jak stosować tagi do wybranych klientów pobierania",
|
||||
"ApiKeyValidationHealthCheckMessage": "Zaktualizuj swój klucz API aby był długi na co najmniej {length} znaków. Możesz to zrobić poprzez ustawienia lub plik konfiguracyjny",
|
||||
"AddAutoTag": "Dodaj automatyczne tagi",
|
||||
"AutoTaggingNegateHelpText": "Jeśli zaznaczone, zasada automatycznego tagowania nie będzie zastosowana, jeśli ten {0} warunek będzie spełniony",
|
||||
"AutoTaggingNegateHelpText": "Jeśli zaznaczone, reguła automatycznego tagowania nie zostanie zastosowana, jeśli warunek {implementationName} zostanie spełniony.",
|
||||
"AddConnection": "Dodaj połączenie",
|
||||
"AddConditionImplementation": "Dodaj condition - {implementationName}",
|
||||
"AddConnectionImplementation": "Dodaj Connection - {implementationName}",
|
||||
"AddConditionImplementation": "Dodaj warunek – {implementationName}",
|
||||
"AddConnectionImplementation": "Dodaj połączenie - {implementationName}",
|
||||
"AddDownloadClientImplementation": "Dodaj klienta pobierania - {implementationName}",
|
||||
"No": "Nie",
|
||||
"QueueLoadError": "Nie udało się załadować kolejki",
|
||||
|
|
@ -1094,7 +1094,7 @@
|
|||
"BlocklistLoadError": "Nie można załadować czarnej listy",
|
||||
"QualityCutoffNotMet": "Odcięcie jakości nie zostało osiągnięte",
|
||||
"TablePageSizeHelpText": "Liczba elementów do pokazania na każdej stronie",
|
||||
"AddIndexerImplementation": "Dodaj indeks - {implementationName}",
|
||||
"AddIndexerImplementation": "Dodaj indekser - {implementationName}",
|
||||
"RemoveQueueItemConfirmation": "Czy na pewno chcesz usunąć elementy ({0}) z kolejki?",
|
||||
"Directory": "Folder",
|
||||
"DeleteReleaseProfile": "Usuń profil opóźnienia",
|
||||
|
|
@ -1128,9 +1128,9 @@
|
|||
"NotificationStatusSingleClientHealthCheckMessage": "Listy niedostępne z powodu błędów: {notificationNames}",
|
||||
"Lists": "Listy",
|
||||
"Yes": "tak",
|
||||
"AddRootFolderError": "Nie można dodać folderu głównego",
|
||||
"AddRootFolderError": "Nie udało się dodać folderu głównego",
|
||||
"DelayingDownloadUntil": "Opóźnianie pobierania do {0} o {1}",
|
||||
"AddListExclusion": "Dodaj wykluczenie z listy",
|
||||
"AddListExclusion": "Dodaj wyjątek z listy",
|
||||
"ConditionUsingRegularExpressions": "Ten warunek pasuje przy użyciu wyrażeń regularnych. Zwróć uwagę, że znaki {0} mają specjalne znaczenie i muszą być poprzedzone znakiem {1}",
|
||||
"CustomFilter": "Filtry niestandardowe",
|
||||
"DeleteSpecification": "Usuń powiadomienie",
|
||||
|
|
@ -1152,7 +1152,7 @@
|
|||
"DeletedReasonMovieMissingFromDisk": "{appName} nie mógł znaleźć pliku na dysku, więc został usunięty",
|
||||
"DeleteSelectedMovieFilesHelpText": "Czy na pewno chcesz usunąć wybrane pliki filmowe?",
|
||||
"IMDbId": "Identyfikator TMDb",
|
||||
"AddDelayProfileError": "Nie można dodać nowego profilu opóźnienia, spróbuj później.",
|
||||
"AddDelayProfileError": "Nie można dodać nowego profilu opóźnienia, spróbuj ponownie.",
|
||||
"MovieFileDeletedTooltip": "Usuń plik filmowy",
|
||||
"DeleteMovieFolders": "Usuń folder filmu",
|
||||
"DeleteMovieFoldersHelpText": "Usuń folder z filmami i jego zawartość",
|
||||
|
|
@ -1160,14 +1160,14 @@
|
|||
"DeleteSelectedImportListExclusionsMessageText": "Czy na pewno chcesz usunąć to wykluczenie listy importu?",
|
||||
"Any": "Dowolny",
|
||||
"AddImportList": "Dodaj listę importu",
|
||||
"AddImportListImplementation": "Dodaj Listę Importu - {implementationName}",
|
||||
"AddImportListImplementation": "Dodaj listę importu - {implementationName}",
|
||||
"AuthenticationRequired": "Wymagana Autoryzacja",
|
||||
"AudioLanguages": "Języki Dźwięku",
|
||||
"AuthenticationMethodHelpTextWarning": "Wybierz prawidłową metodę autoryzacji",
|
||||
"AuthenticationMethod": "Metoda Autoryzacji",
|
||||
"AppUpdatedVersion": "{appName} został zaktualizowany do wersji `{version}`, by uzyskać nowe zmiany należy przeładować {appName}",
|
||||
"AddReleaseProfile": "Dodaj Profil Wydania",
|
||||
"AppUpdated": "{appName} Zaktualizowany",
|
||||
"AddReleaseProfile": "Dodaj profil wydania",
|
||||
"AppUpdated": "{appName} zaktualizowany",
|
||||
"Theme": "Motyw",
|
||||
"AutoTaggingSpecificationTag": "Etykieta",
|
||||
"EditReleaseProfile": "Dodaj Profil Wydania",
|
||||
|
|
@ -1175,7 +1175,7 @@
|
|||
"PreferredProtocol": "Preferowany protokół",
|
||||
"ReleaseProfiles": "profil wydania",
|
||||
"Label": "Etykieta",
|
||||
"AutoTaggingRequiredHelpText": "Warunek {implementationName} musi być zgodny, aby format niestandardowy został zastosowany. W przeciwnym razie wystarczy jedno dopasowanie {implementationName}.",
|
||||
"AutoTaggingRequiredHelpText": "Ten warunek {implementationName} musi zostać spełniony, aby reguła automatycznego tagowania została zastosowana. W przeciwnym razie wystarczy pojedyncze dopasowanie {implementationName}.",
|
||||
"DeleteSelectedCustomFormats": "Usuń format niestandardowy",
|
||||
"ReleaseDate": "Daty wydania",
|
||||
"ShowDigitalReleaseDate": "Pokaż datę premiery w kinie",
|
||||
|
|
@ -1209,5 +1209,107 @@
|
|||
"IndexerHDBitsSettingsCodecs": "Kodek",
|
||||
"IndexerHDBitsSettingsMediums": "Średni",
|
||||
"IndexerSettingsCategories": "Kategorie",
|
||||
"ReleaseProfile": "profil wydania"
|
||||
"ReleaseProfile": "profil wydania",
|
||||
"IndexerSettingsRejectBlocklistedTorrentHashesHelpText": "Jeżeli torrent jest zablokowany przez hasha może zostać nieprawidłowo odrzucony podczas RSS/Szukania przez niektóre indeksery, włączenie tej opcji pozwoli na jego odrzucenie po zgarnięciu, ale przed wysłaniem do klienta.",
|
||||
"FormatAgeDays": "dni",
|
||||
"IndexerSettingsApiUrl": "Link API",
|
||||
"NotificationsDiscordSettingsAuthor": "Autor",
|
||||
"DefaultNameCopiedSpecification": "{name} - Kopia",
|
||||
"DownloadClientFloodSettingsAddPaused": "Dodaj zapauzowany",
|
||||
"ImportListsTraktSettingsAdditionalParameters": "Dodatkowe parametry",
|
||||
"NotificationsGotifySettingsAppToken": "Token aplikacji",
|
||||
"IndexerSettingsMinimumSeeders": "Minimalni seederzy",
|
||||
"IndexerHDBitsSettingsCategoriesHelpText": "Jeżeli niesprecyzowano wszystkie opcje są wykorzystywane.",
|
||||
"DefaultNameCopiedImportList": "{name} - Kopia",
|
||||
"AnnouncedMovieAvailabilityDescription": "Filmy są uważane za dostępne od momentu dodania do {appName}.",
|
||||
"AutoTaggingSpecificationKeyword": "Słowo(a) kluczowe",
|
||||
"AutoTaggingSpecificationMaximumRuntime": "Maksymalny czas trwania",
|
||||
"AutoTaggingSpecificationMaximumYear": "Maksymalny rok",
|
||||
"AutoTaggingSpecificationMinimumRuntime": "Minimalny czas trwania",
|
||||
"AutoTaggingSpecificationMinimumYear": "Minimalny rok",
|
||||
"AutoTaggingSpecificationStudio": "Studio(a)",
|
||||
"AutomaticAdd": "Automatyczne dodawanie",
|
||||
"SearchForCutoffUnmetMovies": "Szukaj wszystkich filmów niespełniających wymagań",
|
||||
"CutoffUnmetLoadError": "Błąd podczas ładowania pozycji niespełniających wymagań",
|
||||
"SearchForCutoffUnmetMoviesConfirmationCount": "Czy na pewno chcesz wyszukać wszystkie filmy ({totalRecords}) niespełniające wymagań?",
|
||||
"AutoRedownloadFailedFromInteractiveSearchHelpText": "Automatycznie wyszukaj i spróbuj pobrać inną wersję, jeśli nie udało się pobrać wersji z wyszukiwania interaktywnego",
|
||||
"AutoTaggingLoadError": "Nie można załadować automatycznego tagowania",
|
||||
"AutoTaggingSpecificationGenre": "Gatunek(i)",
|
||||
"AutoRedownloadFailedFromInteractiveSearch": "Ponowne pobieranie z wyszukiwania interaktywnego nie powiodło się",
|
||||
"AutomaticUpdatesDisabledDocker": "Automatyczne aktualizacje nie są bezpośrednio obsługiwane podczas korzystania z mechanizmu aktualizacji Docker. Konieczne będzie zaktualizowanie kontenera poza {appName} lub użycie skryptu",
|
||||
"BlackholeWatchFolder": "Folder obserwowany",
|
||||
"BlackholeWatchFolderHelpText": "Folder z którego {appName} powinien importować zakończone pobrania",
|
||||
"CutoffUnmetNoItems": "Brak pozycji niespełniających wymagań",
|
||||
"DeleteSelectedCustomFormatsMessageText": "Czy na pewno chcesz usunąć {count} wybranych niestandardowych formatów?",
|
||||
"DeleteSelectedImportLists": "Usuń listy importu",
|
||||
"DeleteSelectedImportListsMessageText": "Czy na pewno chcesz usunąć {count} wybranych list importu?",
|
||||
"DoNotBlocklistHint": "Usuń bez blokowania",
|
||||
"DownloadClientDelugeSettingsDirectoryCompleted": "Katalog do przenoszenia po zakończeniu",
|
||||
"DownloadClientDelugeValidationLabelPluginFailure": "Konfiguracja etykiety nie powiodła się",
|
||||
"DownloadClientDelugeValidationLabelPluginInactive": "Plugin etykiet nie jest aktywowany",
|
||||
"BlocklistReleaseHelpText": "Blokuje tą premierę przed ponownym pobraniem przez {appName} poprzez RSS albo automatyczne wyszukiwanie",
|
||||
"BypassDelayIfAboveCustomFormatScoreHelpText": "Włącz pomijanie kiedy premiera ma wagę wyższą niż skonfigurowane minimum niestandardowego formatu",
|
||||
"BypassDelayIfAboveCustomFormatScoreMinimumScoreHelpText": "Minimalna waga niestandardowego formatu wymagana by pominąć opóźnienie dla preferowanego protokołu",
|
||||
"ChangeCategory": "Zmień kategorię",
|
||||
"ChangeCategoryMultipleHint": "Zmień pobierania na 'Kategorię po imporcie' z klienta pobierania",
|
||||
"Complete": "Zakończone",
|
||||
"Completed": "Zakończone",
|
||||
"ConnectionSettingsUrlBaseHelpText": "Dodaj prefix do URL {connectionName}, np. {url}",
|
||||
"CustomFormatsSpecificationExceptLanguage": "Wyklucz język",
|
||||
"CustomFormatsSpecificationExceptLanguageHelpText": "Dopasowuje jeżeli występuje jakikolwiek inny język poza wybranym",
|
||||
"CustomFormatsSpecificationFlag": "Flaga",
|
||||
"DefaultNotFoundMessage": "Musiałeś się zgubić, nic tutaj nie ma.",
|
||||
"DeleteAutoTag": "Usuń automatyczny tag",
|
||||
"DeleteImportList": "Usuń listę importu",
|
||||
"Disposition": "Stan",
|
||||
"DoNotBlocklist": "Nie blokuj",
|
||||
"DownloadClientDelugeSettingsDirectory": "Katalog pobierania",
|
||||
"DownloadClientDelugeSettingsDirectoryHelpText": "Opcjonalna lokalizacja dla pobrań, pozostaw puste by korzystać z domyślnej lokalizacji Deluge",
|
||||
"DownloadClientDelugeValidationLabelPluginFailureDetail": "{appName} nie mógł dodać etykiety do {clientName}.",
|
||||
"DownloadClientDownloadStationProviderMessage": "{appName} nie jest w stanie połączyć się ze stacją pobierania jeżeli dwuetapowa autoryzacja nie jest włączona w Twoim koncie DSM",
|
||||
"DownloadClientDelugeValidationLabelPluginInactiveDetail": "Musisz mieć włączony plugin etykiet w {clientName} by korzystać z kategorii.",
|
||||
"CustomFormatsSpecificationMaximumYear": "Maksymalny rok",
|
||||
"CustomFormatsSpecificationMinimumSize": "Minimalny rozmiar",
|
||||
"CustomFormatsSpecificationRegularExpression": "Wyrażenie regularne",
|
||||
"CustomFormatsSpecificationRegularExpressionHelpText": "RegEx niestandardowego formatu nie rozróżnia wielkości liter",
|
||||
"CutoffNotMet": "Limit nieosiągnięty",
|
||||
"CinemaRelease": "Premiera kinowa",
|
||||
"ClearBlocklist": "Wyczyść listę blokowania",
|
||||
"ClickToChangeIndexerFlags": "Kliknij aby zmienić flagi indeksera",
|
||||
"ClearBlocklistMessageText": "Czy na pewno chcesz wyczyścić wszystkie elementy listy blokowania?",
|
||||
"CloneCondition": "Klonuj warunek",
|
||||
"CloneAutoTag": "Klonuj automatyczny tag",
|
||||
"CountCustomFormatsSelected": "Wybrano {count} niestandardowych formatów",
|
||||
"CountImportListsSelected": "Wybrano {count} list importu",
|
||||
"CountVotes": "{votes} głosów",
|
||||
"DeleteCondition": "Usuń warunek",
|
||||
"DeleteMovieFolderCountWithFilesConfirmation": "Czy na pewno chcesz usunąć {count} wybranych filmów i całą zawartość?",
|
||||
"DeletedReasonManual": "Plik został usunięty przez {appName}, ręcznie lub przez API",
|
||||
"DownloadClientDelugeTorrentStateError": "Deluge zgłasza błąd",
|
||||
"DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Opcjonalna lokalizacja do przenoszenia ukończonych pobrań, pozostaw puste by korzystać z domyślnej lokalizacji Deluge",
|
||||
"BlocklistAndSearch": "Lista blokowania i wyszukiwanie",
|
||||
"BlocklistAndSearchHint": "Rozpocznij wyszukiwanie po zamiennik po dodaniu do listy blokowania",
|
||||
"BlocklistAndSearchMultipleHint": "Rozpocznij wyszukiwania po zamienniki po dodaniu do listy blokowania",
|
||||
"BlocklistFilterHasNoItems": "Wybrane filtry listy blokowania nie zawierają pozycji",
|
||||
"BlocklistMultipleOnlyHint": "Blokuj bez wyszukiwania zamienników",
|
||||
"BlocklistOnly": "Tylko zablokuj",
|
||||
"BlocklistOnlyHint": "Zablokuj bez szukania zamiennika",
|
||||
"BlocklistedAt": "Zablokowany {date}",
|
||||
"BypassDelayIfAboveCustomFormatScore": "Pomiń jeśli powyżej wagi niestandardowego formatu",
|
||||
"ChangeCategoryHint": "Zmień pobieranie na 'Kategorię po imporcie' z klienta pobierania",
|
||||
"ChownGroup": "Zmień grupę właściciela (chown)",
|
||||
"CloneImportList": "Klonuj listę importu",
|
||||
"CountMissingMoviesFromLibrary": "{count} brakujących filmów z biblioteki",
|
||||
"CustomFormatJson": "JSON niestandardowego formatu",
|
||||
"CustomFormatsSettingsTriggerInfo": "Niestandardowy format zostanie zastosowany do premiery lub pliku kiedy spełni co najmniej jeden z każdego typu wybranych warunków.",
|
||||
"CustomFormatsSpecificationMaximumSizeHelpText": "Premiera musi mieć rozmiar mniejszy lub równy z tym",
|
||||
"CustomFormatsSpecificationMinimumSizeHelpText": "Premiera musi być większa niż ten rozmiar",
|
||||
"CustomFormatsSpecificationMinimumYear": "Minimalny rok",
|
||||
"CustomFormatsSpecificationQualityModifier": "Modyfikator jakości",
|
||||
"DayOfWeekAt": "{day} {time}",
|
||||
"DelayMinutes": "{delay} minut",
|
||||
"DelayProfileProtocol": "Protokół: {preferredProtocol}",
|
||||
"DeleteMovieFolderCountConfirmation": "Czy na pewno chcesz usunąć {count} wybranych filmów?",
|
||||
"DeleteMovieFolderMovieCount": "{movieFileCount} plików filmu o rozmiarze {size}",
|
||||
"DeleteSelected": "Usuń wybrane"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2036,5 +2036,6 @@
|
|||
"NotificationsPushcutSettingsIncludePoster": "Incluir Pôster",
|
||||
"NotificationsPushcutSettingsIncludePosterHelpText": "Incluir pôster com notificação",
|
||||
"NotificationsPushcutSettingsMetadataLinks": "Links de metadados",
|
||||
"NotificationsPushcutSettingsMetadataLinksHelpText": "Adicionar links para os metadados da série ao enviar notificações"
|
||||
"NotificationsPushcutSettingsMetadataLinksHelpText": "Adicionar links para os metadados da série ao enviar notificações",
|
||||
"FilterMoviePropertiesOnlyNotFileWarning": "Os filtros estão disponíveis apenas para as propriedades de um filme, eles não estão disponíveis para as propriedades do(s) arquivo(s) que você pode ter para esse filme."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2036,5 +2036,6 @@
|
|||
"NotificationsPushcutSettingsIncludePoster": "Добавить постер",
|
||||
"NotificationsPushcutSettingsIncludePosterHelpText": "Добавляет постер в уведомление",
|
||||
"NotificationsPushcutSettingsMetadataLinks": "Ссылки на метаданные",
|
||||
"NotificationsPushcutSettingsMetadataLinksHelpText": "Добавляет ссылки на метаданные сериала при отправке уведомлений"
|
||||
"NotificationsPushcutSettingsMetadataLinksHelpText": "Добавляет ссылки на метаданные сериала при отправке уведомлений",
|
||||
"FilterMoviePropertiesOnlyNotFileWarning": "Фильтры доступны только для свойств фильма, они недоступны для свойств файл(ов), которые могут быть у вас для этого фильма."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2036,5 +2036,6 @@
|
|||
"NotificationsPushcutSettingsIncludePosterHelpText": "Bildirim içeriğine poster ekleyin",
|
||||
"NotificationsPushcutSettingsMetadataLinks": "Meta Veri Bağlantıları",
|
||||
"NotificationsPushcutSettingsIncludePoster": "Poster'i ekle",
|
||||
"NotificationsPushcutSettingsMetadataLinksHelpText": "Bildirim gönderirken dizi meta verilerine bağlantılar ekleyin"
|
||||
"NotificationsPushcutSettingsMetadataLinksHelpText": "Bildirim gönderirken dizi meta verilerine bağlantılar ekleyin",
|
||||
"FilterMoviePropertiesOnlyNotFileWarning": "Filtreler yalnızca bir filmin özellikleri için kullanılabilir, o filme ait sahip olduğunuz dosyanın/dosyaların özellikleri için kullanılamaz."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -531,7 +531,7 @@
|
|||
"AddExclusion": "添加排除",
|
||||
"AddedToDownloadQueue": "已加入下载队列",
|
||||
"Added": "添加日期",
|
||||
"Actions": "动作",
|
||||
"Actions": "操作",
|
||||
"IMDb": "IMDb",
|
||||
"PendingChangesDiscardChanges": "舍弃修改并退出",
|
||||
"Pending": "挂起",
|
||||
|
|
@ -719,7 +719,7 @@
|
|||
"ReleaseRejected": "发布资源已拒绝",
|
||||
"UnmappedFilesOnly": "仅限未映射的文件",
|
||||
"Quality": "质量",
|
||||
"TheLogLevelDefault": "默认的日志等级为 \"Info\",可以在 [常规设置] 中修改 (/settings/general)",
|
||||
"TheLogLevelDefault": "預設的日誌等級為「Info」,可在[通用設定]中修改 (/settings/general)",
|
||||
"RestartReloadNote": "注意:{appName}将在恢复过程中自动重启并重新加载UI。",
|
||||
"PhysicalRelease": "碟片版发布日期",
|
||||
"Trace": "追踪",
|
||||
|
|
@ -1987,5 +1987,55 @@
|
|||
"EditMovieCollectionModalHeader": "编辑 - {title}",
|
||||
"DefaultNameCopiedImportList": "{name} - 复制",
|
||||
"ReleaseProfile": "发行配置文件",
|
||||
"NotificationsPushcutSettingsMetadataLinks": "元数据链接"
|
||||
"NotificationsPushcutSettingsMetadataLinks": "元数据链接",
|
||||
"IndexerFileListSettingsCategoriesHelpText": "如果未指定,則使用所有選項。",
|
||||
"Keywords": "關鍵字",
|
||||
"CountMissingMoviesFromLibrary": "媒體庫中缺少 {count} 部電影",
|
||||
"AutoTaggingSpecificationKeyword": "關鍵字",
|
||||
"AutoTaggingSpecificationStudio": "工作室",
|
||||
"FilterMoviePropertiesOnlyNotFileWarning": "此篩選器僅適用於電影屬性,不適用於檔案屬性。",
|
||||
"ICalReleaseTypes": "發行類型",
|
||||
"ICalReleaseTypesMoviesHelpText": "選擇要在 iCal 訂閱中包含的發行類型",
|
||||
"ImportListsRadarrSettingsRootFoldersHelpText": "您想從中匯入的 {appName} 實例的根目錄資料夾 ID(以逗號分隔)",
|
||||
"IndexerNewznabSettingsAdditionalParametersHelpText": "額外的 Newznab 參數(例如 `&t=search&cat=2000,2010&extended=1`)",
|
||||
"IndexerNewznabSettingsCategoriesHelpText": "要查詢的 Newznab 分類 ID(以逗號分隔)",
|
||||
"IndexerPassThePopcornSettingsApiUserHelpText": "PassThePopcorn 的 API 使用者",
|
||||
"IndexerSettingsApiUser": "API 使用者",
|
||||
"IndexerSettingsBaseUrl": "基礎 URL",
|
||||
"IndexerSettingsFailDownloads": "將下載標記為失敗",
|
||||
"IndexerSettingsRemoveYear": "從標題中移除年份",
|
||||
"IndexerSettingsRemoveYearHelpText": "從發行版本標題中移除年份以改進解析",
|
||||
"IndexerSettingsRequiredFlagsHelpText": "發行版本必須包含的旗標 (以逗號分隔)",
|
||||
"MediaInfoFootNote2": "語言代碼應為 ISO 639-2 (3字元) 格式,但 ISO 639-1 (2字元) 也受支援。",
|
||||
"MetadataMediaBrowserDeprecated": "MediaBrowser (Ember) 將不再被建立,支援將在 v6 版本中完全移除。",
|
||||
"MovieEditRootFolderHelpText": "變更電影的根目錄資料夾將會移動電影資料夾及其所有內容。",
|
||||
"NamingConfigMovieFolderFormatDeprecatedHealthCheckMessage": "「電影資料夾格式」設定已被棄用。請改用「設定」 -> 「媒體管理」中的「電影命名」設定。",
|
||||
"NotificationsPushcutSettingsIncludePoster": "包含海報",
|
||||
"NotificationsPushcutSettingsIncludePosterHelpText": "在通知中包含電影海報",
|
||||
"NotificationsPushcutSettingsMetadataLinksHelpText": "在通知中新增一個指向電影元資料的連結",
|
||||
"RemoveRootFolderMoviesMessageText": "此根目錄資料夾包含 {count} 部電影。您想如何處理這些電影?",
|
||||
"SelectMovieModalTitle": "{modalTitle} - 選擇電影",
|
||||
"ShowCinemaRelease": "顯示院線上映日期",
|
||||
"ShowCinemaReleaseCalendarHelpText": "在日曆上顯示院線上映日期",
|
||||
"ShowDigitalRelease": "顯示數位版發行日期",
|
||||
"ShowDigitalReleaseCalendarHelpText": "在日曆上顯示數位版發行日期",
|
||||
"ShowPhysicalReleaseCalendarHelpText": "在日曆上顯示實體版發行日期",
|
||||
"ShowPhysicalRelease": "顯示實體版發行日期",
|
||||
"MovieFolderFormatHelpTextDeprecatedWarning": "電影資料夾格式設定已移至 設定 -> 媒體管理 -> 電影命名",
|
||||
"IndexerSettingsFailDownloadsHelpText": "如果下載項目符合此索引器的做種規則但下載失敗,則將其標記為失敗而不是警告。",
|
||||
"IndexerSettingsRequiredFlags": "必要旗標",
|
||||
"FileSize": "檔案大小",
|
||||
"NotificationsAppriseSettingsIncludePoster": "包含海報",
|
||||
"NotificationsAppriseSettingsIncludePosterHelpText": "在通知中包含電影海報圖片",
|
||||
"UpdateMoviePath": "更新電影路徑",
|
||||
"UpdatePath": "更新路徑",
|
||||
"ImportListsRadarrSettingsFullUrl": "完整的 {appName} URL",
|
||||
"ImportListsRadarrSettingsFullUrlHelpText": "您想從中匯入的 {appName} 實例的完整 URL",
|
||||
"ImportListsRadarrSettingsApiKeyHelpText": "您想從中匯入的 {appName} 實例的 API 金鑰",
|
||||
"ImportListsRadarrSettingsQualityProfilesHelpText": "您想從中匯入的 {appName} 實例的品質設定檔 ID(以逗號分隔)",
|
||||
"ImportListsRadarrSettingsTagsHelpText": "您想從中匯入的 {appName} 實例的標籤(以逗號分隔)",
|
||||
"CinemaRelease": "院線上映",
|
||||
"CustomFormatsSpecificationQualityModifier": "品質修飾詞",
|
||||
"CloneImportList": "複製匯入清單",
|
||||
"DownloadClientItemErrorMessage": "下載用戶端回報錯誤:{message}"
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,11 +1,21 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles
|
||||
{
|
||||
internal static class FileExtensions
|
||||
public static class FileExtensions
|
||||
{
|
||||
private static List<string> _archiveExtensions = new List<string>
|
||||
private static readonly Regex FileExtensionRegex = new (@"\.[a-z0-9]{2,4}$",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly HashSet<string> UsenetExtensions = new HashSet<string>()
|
||||
{
|
||||
".par2",
|
||||
".nzb"
|
||||
};
|
||||
|
||||
public static HashSet<string> ArchiveExtensions => new (StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
".7z",
|
||||
".bz2",
|
||||
|
|
@ -20,8 +30,7 @@ internal static class FileExtensions
|
|||
".tgz",
|
||||
".zip"
|
||||
};
|
||||
|
||||
private static List<string> _dangerousExtensions = new List<string>
|
||||
public static HashSet<string> DangerousExtensions => new (StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
".arj",
|
||||
".lnk",
|
||||
|
|
@ -31,8 +40,7 @@ internal static class FileExtensions
|
|||
".vbs",
|
||||
".zipx"
|
||||
};
|
||||
|
||||
private static List<string> _executableExtensions = new List<string>
|
||||
public static HashSet<string> ExecutableExtensions => new (StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
".bat",
|
||||
".cmd",
|
||||
|
|
@ -40,8 +48,20 @@ internal static class FileExtensions
|
|||
".sh"
|
||||
};
|
||||
|
||||
public static HashSet<string> ArchiveExtensions => new HashSet<string>(_archiveExtensions, StringComparer.OrdinalIgnoreCase);
|
||||
public static HashSet<string> DangerousExtensions => new HashSet<string>(_dangerousExtensions, StringComparer.OrdinalIgnoreCase);
|
||||
public static HashSet<string> ExecutableExtensions => new HashSet<string>(_executableExtensions, StringComparer.OrdinalIgnoreCase);
|
||||
public static string RemoveFileExtension(string title)
|
||||
{
|
||||
title = FileExtensionRegex.Replace(title, m =>
|
||||
{
|
||||
var extension = m.Value.ToLower();
|
||||
if (MediaFileExtensions.Extensions.Contains(extension) || UsenetExtensions.Contains(extension))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return m.Value;
|
||||
});
|
||||
|
||||
return title;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -293,7 +293,7 @@ public static string FormatVideoCodec(MediaInfoModel mediaInfo, string sceneName
|
|||
|
||||
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ public ManualImportItem ReprocessItem(string path, string downloadId, int movieI
|
|||
|
||||
var downloadClientItem = GetTrackedDownload(downloadId)?.DownloadItem;
|
||||
var finalReleaseGroup = releaseGroup.IsNullOrWhiteSpace()
|
||||
? Parser.Parser.ParseReleaseGroup(path)
|
||||
? ReleaseGroupParser.ParseReleaseGroup(path)
|
||||
: releaseGroup;
|
||||
var finalQuality = (quality?.Quality ?? Quality.Unknown) == Quality.Unknown ? QualityParser.ParseQuality(path) : quality;
|
||||
var finalLanguages =
|
||||
|
|
@ -282,7 +282,7 @@ private ManualImportItem ProcessFile(string rootFolder, string baseFolder, strin
|
|||
{
|
||||
var localMovie = new LocalMovie();
|
||||
localMovie.Path = file;
|
||||
localMovie.ReleaseGroup = Parser.Parser.ParseReleaseGroup(file);
|
||||
localMovie.ReleaseGroup = ReleaseGroupParser.ParseReleaseGroup(file);
|
||||
localMovie.Quality = QualityParser.ParseQuality(file);
|
||||
localMovie.Languages = LanguageParser.ParseLanguages(file);
|
||||
localMovie.Size = _diskProvider.GetFileSize(file);
|
||||
|
|
@ -327,7 +327,7 @@ private List<ManualImportItem> ProcessDownloadDirectory(string rootFolder, List<
|
|||
localMovie.Path = file;
|
||||
localMovie.Quality = new QualityModel(Quality.Unknown);
|
||||
localMovie.Languages = new List<Language> { Language.Unknown };
|
||||
localMovie.ReleaseGroup = Parser.Parser.ParseReleaseGroup(file);
|
||||
localMovie.ReleaseGroup = ReleaseGroupParser.ParseReleaseGroup(file);
|
||||
localMovie.Size = _diskProvider.GetFileSize(file);
|
||||
|
||||
items.Add(MapItem(new ImportDecision(localMovie), rootFolder, null, null));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.MovieImport
|
||||
{
|
||||
|
|
@ -19,10 +18,5 @@ public RecycleBinException(string message, Exception innerException)
|
|||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected RecycleBinException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.MovieImport
|
||||
{
|
||||
|
|
@ -19,10 +18,5 @@ public RootFolderNotFoundException(string message, Exception innerException)
|
|||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected RootFolderNotFoundException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ public static string GetSceneName(LocalMovie localMovie)
|
|||
|
||||
if (!otherVideoFiles && downloadClientInfo != null)
|
||||
{
|
||||
return Parser.Parser.RemoveFileExtension(downloadClientInfo.ReleaseTitle);
|
||||
return FileExtensions.RemoveFileExtension(downloadClientInfo.ReleaseTitle);
|
||||
}
|
||||
|
||||
var fileName = Path.GetFileNameWithoutExtension(localMovie.Path.CleanFilePath());
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ public string GetHashFromTorrentFile(byte[] fileContents)
|
|||
{
|
||||
try
|
||||
{
|
||||
return Torrent.Load(fileContents).InfoHash.ToHex();
|
||||
return Torrent.Load(fileContents).InfoHashes.V1OrV2.ToHex();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -45,6 +45,11 @@ public void SendNotification(string title, string message, PushoverSettings sett
|
|||
requestBuilder.AddFormParameter("expire", settings.Expire);
|
||||
}
|
||||
|
||||
if (settings.Ttl > 0)
|
||||
{
|
||||
requestBuilder.AddFormParameter("ttl", settings.Ttl);
|
||||
}
|
||||
|
||||
if (!settings.Sound.IsNullOrWhiteSpace())
|
||||
{
|
||||
requestBuilder.AddFormParameter("sound", settings.Sound);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ public PushoverSettingsValidator()
|
|||
RuleFor(c => c.UserKey).NotEmpty();
|
||||
RuleFor(c => c.Retry).GreaterThanOrEqualTo(30).LessThanOrEqualTo(86400).When(c => (PushoverPriority)c.Priority == PushoverPriority.Emergency);
|
||||
RuleFor(c => c.Retry).GreaterThanOrEqualTo(0).LessThanOrEqualTo(86400).When(c => (PushoverPriority)c.Priority == PushoverPriority.Emergency);
|
||||
RuleFor(c => c.Ttl).GreaterThanOrEqualTo(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -44,7 +45,10 @@ public PushoverSettings()
|
|||
[FieldDefinition(5, Label = "NotificationsPushoverSettingsExpire", Type = FieldType.Textbox, HelpText = "NotificationsPushoverSettingsExpireHelpText")]
|
||||
public int Expire { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "NotificationsPushoverSettingsSound", Type = FieldType.Textbox, HelpText = "NotificationsPushoverSettingsSoundHelpText", HelpLink = "https://pushover.net/api#sounds")]
|
||||
[FieldDefinition(6, Label = "NotificationsPushoverSettingsTtl", Type = FieldType.Textbox, HelpText = "NotificationsPushoverSettingsTtlHelpText", Advanced = true)]
|
||||
public int Ttl { get; set; }
|
||||
|
||||
[FieldDefinition(7, Label = "NotificationsPushoverSettingsSound", Type = FieldType.Textbox, HelpText = "NotificationsPushoverSettingsSoundHelpText", HelpLink = "https://pushover.net/api#sounds")]
|
||||
public string Sound { get; set; }
|
||||
|
||||
public bool IsValid => !string.IsNullOrWhiteSpace(UserKey) && Priority >= -1 && Priority <= 2;
|
||||
|
|
|
|||
|
|
@ -174,29 +174,13 @@ public string GetMovieFolder(Movie movie, NamingConfig namingConfig = null)
|
|||
namingConfig = _namingConfigService.GetConfig();
|
||||
}
|
||||
|
||||
var pattern = namingConfig.MovieFolderFormat;
|
||||
var multipleTokens = TitleRegex.Matches(pattern).Count > 1;
|
||||
|
||||
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
||||
|
||||
AddMovieTokens(tokenHandlers, movie);
|
||||
AddReleaseDateTokens(tokenHandlers, movie.Year);
|
||||
AddIdTokens(tokenHandlers, movie);
|
||||
|
||||
var movieFile = movie.MovieFile;
|
||||
|
||||
if (movie.MovieFile != null)
|
||||
{
|
||||
AddQualityTokens(tokenHandlers, movie, movieFile);
|
||||
AddMediaInfoTokens(tokenHandlers, movieFile);
|
||||
AddMovieFileTokens(tokenHandlers, movieFile, multipleTokens);
|
||||
AddEditionTagsTokens(tokenHandlers, movieFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddMovieFileTokens(tokenHandlers, new MovieFile { SceneName = $"{movie.Title} {movie.Year}", RelativePath = $"{movie.Title} {movie.Year}" }, multipleTokens);
|
||||
}
|
||||
|
||||
var pattern = namingConfig.MovieFolderFormat;
|
||||
var splitPatterns = pattern.Split(new char[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var components = new List<string>();
|
||||
|
||||
|
|
@ -284,6 +268,7 @@ private void AddMovieTokens(Dictionary<string, Func<TokenMatch, string>> tokenHa
|
|||
|
||||
tokenHandlers["{Movie Certification}"] = m => movie.MovieMetadata.Value.Certification ?? string.Empty;
|
||||
tokenHandlers["{Movie Collection}"] = m => Truncate(movie.MovieMetadata.Value.CollectionTitle, m.CustomFormat) ?? string.Empty;
|
||||
tokenHandlers["{Movie CollectionThe}"] = m => Truncate(TitleThe(movie.MovieMetadata.Value.CollectionTitle), m.CustomFormat) ?? string.Empty;
|
||||
}
|
||||
|
||||
private string GetLanguageTitle(Movie movie, string isoCodes)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ public static IRuleBuilderOptions<T, string> ValidMovieFolderFormat<T>(this IRul
|
|||
{
|
||||
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
||||
ruleBuilder.SetValidator(new IllegalCharactersValidator());
|
||||
ruleBuilder.SetValidator(new IllegalMovieFolderTokensValidator());
|
||||
|
||||
return ruleBuilder.SetValidator(new ValidMovieFolderFormatValidator());
|
||||
}
|
||||
|
|
@ -59,9 +60,31 @@ protected override bool IsValid(PropertyValidatorContext context)
|
|||
return false;
|
||||
}
|
||||
|
||||
// TODO: Deprecate OriginalTokenRegex use for Movie Folder Format
|
||||
return FileNameBuilder.MovieTitleRegex.IsMatch(value) ||
|
||||
FileNameValidation.OriginalTokenRegex.IsMatch(value);
|
||||
return FileNameBuilder.MovieTitleRegex.IsMatch(value);
|
||||
}
|
||||
}
|
||||
|
||||
public class IllegalMovieFolderTokensValidator : PropertyValidator
|
||||
{
|
||||
protected override string GetDefaultMessageTemplate() => "Must not contain deprecated tokens derived from file properties: {tokens}";
|
||||
|
||||
protected override bool IsValid(PropertyValidatorContext context)
|
||||
{
|
||||
if (context.PropertyValue is not string value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var match = FileNameValidation.DeprecatedMovieFolderTokensRegex.Matches(value);
|
||||
|
||||
if (match.Any())
|
||||
{
|
||||
context.MessageFormatter.AppendArgument("tokens", string.Join(", ", match.Select(c => c.Value).ToArray()));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Parser
|
||||
|
|
@ -21,8 +22,6 @@ public static class Parser
|
|||
private static readonly Regex HardcodedSubsRegex = new Regex(@"\b((?<hcsub>(\w+(?<!SOFT|MULTI|HORRIBLE)SUBS?))|(?<hc>(HC|SUBBED)))\b",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
|
||||
private static readonly RegexReplace[] PreSubstitutionRegex = Array.Empty<RegexReplace>();
|
||||
|
||||
private static readonly Regex[] ReportMovieTitleRegex = new[]
|
||||
{
|
||||
// Anime [Subgroup] and Year
|
||||
|
|
@ -110,9 +109,6 @@ public static class Parser
|
|||
private static readonly Regex NormalizeRegex = new Regex(@"((?:\b|_)(?<!^|[^a-zA-Z0-9_']\w[^a-zA-Z0-9_'])([aà](?!$|[^a-zA-Z0-9_']\w[^a-zA-Z0-9_'])|an|the|and|or|of)(?!$)(?:\b|_))|\W|_",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex FileExtensionRegex = new Regex(@"\.[a-z0-9]{2,4}$",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex ReportImdbId = new Regex(@"(?<imdbid>tt\d{7,8})", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
private static readonly Regex ReportTmdbId = new Regex(@"tmdb(id)?-(?<tmdbid>\d+)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
|
|
@ -123,44 +119,13 @@ public static class Parser
|
|||
private static readonly Regex SimpleReleaseTitleRegex = new Regex(@"\s*(?:[<>?*|])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
// Valid TLDs http://data.iana.org/TLD/tlds-alpha-by-domain.txt
|
||||
private static readonly RegexReplace WebsitePrefixRegex = new RegexReplace(@"^(?:(?:\[|\()\s*)?(?:www\.)?[-a-z0-9-]{1,256}\.(?<!Naruto-Kun\.)(?:[a-z]{2,6}\.[a-z]{2,6}|xn--[a-z0-9-]{4,}|[a-z]{2,})\b(?:\s*(?:\]|\))|[ -]{2,})[ -]*",
|
||||
string.Empty,
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly RegexReplace WebsitePostfixRegex = new RegexReplace(@"(?:\[\s*)?(?:www\.)?[-a-z0-9-]{1,256}\.(?:xn--[a-z0-9-]{4,}|[a-z]{2,6})\b(?:\s*\])$",
|
||||
string.Empty,
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly RegexReplace CleanReleaseGroupRegex = new RegexReplace(@"(-(RP|1|NZBGeek|Obfuscated|Obfuscation|Scrambled|sample|Pre|postbot|xpost|Rakuv[a-z0-9]*|WhiteRev|BUYMORE|AsRequested|AlternativeToRequested|GEROV|Z0iDS3N|Chamele0n|4P|4Planet|AlteZachen|RePACKPOST))+$",
|
||||
string.Empty,
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly RegexReplace CleanTorrentSuffixRegex = new RegexReplace(@"\[(?:ettv|rartv|rarbg|cttv|publichd)\]$",
|
||||
string.Empty,
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex CleanQualityBracketsRegex = new Regex(@"\[[a-z0-9 ._-]+\]$",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex ReleaseGroupRegex = new Regex(@"-(?<releasegroup>[a-z0-9]+(?<part2>-[a-z0-9]+)?(?!.+?(?:480p|576p|720p|1080p|2160p)))(?<!(?:WEB-(DL|Rip)|Blu-Ray|480p|576p|720p|1080p|2160p|DTS-HD|DTS-X|DTS-MA|DTS-ES|-ES|-EN|-CAT|-ENG|-JAP|-GER|-FRA|-FRE|-ITA|-HDRip|\d{1,2}-bit|[ ._]\d{4}-\d{2}|-\d{2}|tmdb(id)?-(?<tmdbid>\d+)|(?<imdbid>tt\d{7,8}))(?:\k<part2>)?)(?:\b|[-._ ]|$)|[-._ ]\[(?<releasegroup>[a-z0-9]+)\]$",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex InvalidReleaseGroupRegex = new Regex(@"^([se]\d+|[0-9a-f]{8})$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex AnimeReleaseGroupRegex = new Regex(@"^(?:\[(?<subgroup>(?!\s).+?(?<!\s))\](?:_|-|\s|\.)?)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex YearInTitleRegex = new Regex(@"^(?<title>.+?)(?:\W|_.)?[\(\[]?(?<year>\d{4})[\]\)]?",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
// Handle Exception Release Groups that don't follow -RlsGrp; Manual List
|
||||
// groups whose releases end with RlsGroup) or RlsGroup]
|
||||
private static readonly Regex ExceptionReleaseGroupRegex = new Regex(@"(?<=[._ \[])(?<releasegroup>(Silence|afm72|Panda|Ghost|MONOLITH|Tigole|Joy|ImE|UTR|t3nzin|Anime Time|Project Angel|Hakata Ramen|HONE|Vyndros|SEV|Garshasp|Kappa|Natty|RCVR|SAMPA|YOGI|r00t|EDGE2020|RZeroX|FreetheFish|Anna|Bandi|Qman|theincognito|HDO|DusIctv|DHD|CtrlHD|-ZR-|ADC|XZVN|RH|Kametsu|Garshasp)(?=\]|\)))", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
// Handle Exception Release Groups that don't follow -RlsGrp; Manual List
|
||||
// name only...BE VERY CAREFUL WITH THIS, HIGH CHANCE OF FALSE POSITIVES
|
||||
private static readonly Regex ExceptionReleaseGroupRegexExact = new Regex(@"\b(?<releasegroup>KRaLiMaRKo|E\.N\.D|D\-Z0N3|Koten_Gars|BluDragon|ZØNEHD|Tigole|HQMUX|VARYG|YIFY|YTS(.(MX|LT|AG))?|TMd|Eml HDTeam|LMain|DarQ|BEN THE MEN)\b", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex SpecialCharRegex = new Regex(@"(\&|\:|\\|\/)+", RegexOptions.Compiled);
|
||||
private static readonly Regex PunctuationRegex = new Regex(@"[^\w\s]", RegexOptions.Compiled);
|
||||
private static readonly Regex ArticleWordRegex = new Regex(@"^(a|an|the)\s", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
|
@ -215,7 +180,7 @@ public static ParsedMovieInfo ParseMovieTitle(string title, bool isDir = false)
|
|||
|
||||
if (ReversedTitleRegex.IsMatch(title))
|
||||
{
|
||||
var titleWithoutExtension = RemoveFileExtension(title).ToCharArray();
|
||||
var titleWithoutExtension = FileExtensions.RemoveFileExtension(title).ToCharArray();
|
||||
Array.Reverse(titleWithoutExtension);
|
||||
|
||||
title = $"{titleWithoutExtension}{title.Substring(titleWithoutExtension.Length)}";
|
||||
|
|
@ -223,14 +188,14 @@ public static ParsedMovieInfo ParseMovieTitle(string title, bool isDir = false)
|
|||
Logger.Debug("Reversed name detected. Converted to '{0}'", title);
|
||||
}
|
||||
|
||||
var releaseTitle = RemoveFileExtension(title);
|
||||
var releaseTitle = FileExtensions.RemoveFileExtension(title);
|
||||
|
||||
// Trim dashes from end
|
||||
releaseTitle = releaseTitle.Trim('-', '_');
|
||||
|
||||
releaseTitle = releaseTitle.Replace("【", "[").Replace("】", "]");
|
||||
|
||||
foreach (var replace in PreSubstitutionRegex)
|
||||
foreach (var replace in ParserCommon.PreSubstitutionRegex)
|
||||
{
|
||||
if (replace.TryReplace(ref releaseTitle))
|
||||
{
|
||||
|
|
@ -242,10 +207,10 @@ public static ParsedMovieInfo ParseMovieTitle(string title, bool isDir = false)
|
|||
var simpleTitle = SimpleTitleRegex.Replace(releaseTitle);
|
||||
|
||||
// TODO: Quick fix stripping [url] - prefixes.
|
||||
simpleTitle = WebsitePrefixRegex.Replace(simpleTitle);
|
||||
simpleTitle = WebsitePostfixRegex.Replace(simpleTitle);
|
||||
simpleTitle = ParserCommon.WebsitePrefixRegex.Replace(simpleTitle);
|
||||
simpleTitle = ParserCommon.WebsitePostfixRegex.Replace(simpleTitle);
|
||||
|
||||
simpleTitle = CleanTorrentSuffixRegex.Replace(simpleTitle);
|
||||
simpleTitle = ParserCommon.CleanTorrentSuffixRegex.Replace(simpleTitle);
|
||||
|
||||
simpleTitle = CleanQualityBracketsRegex.Replace(simpleTitle, m =>
|
||||
{
|
||||
|
|
@ -295,7 +260,7 @@ public static ParsedMovieInfo ParseMovieTitle(string title, bool isDir = false)
|
|||
}
|
||||
}
|
||||
|
||||
result.ReleaseGroup = ParseReleaseGroup(simpleReleaseTitle);
|
||||
result.ReleaseGroup = ReleaseGroupParser.ParseReleaseGroup(simpleReleaseTitle);
|
||||
|
||||
var subGroup = GetSubGroup(match);
|
||||
if (!subGroup.IsNullOrWhiteSpace())
|
||||
|
|
@ -521,74 +486,6 @@ public static string ParseHardcodeSubs(string title)
|
|||
return null;
|
||||
}
|
||||
|
||||
public static string ParseReleaseGroup(string title)
|
||||
{
|
||||
title = title.Trim();
|
||||
title = RemoveFileExtension(title);
|
||||
title = WebsitePrefixRegex.Replace(title);
|
||||
title = CleanTorrentSuffixRegex.Replace(title);
|
||||
|
||||
var animeMatch = AnimeReleaseGroupRegex.Match(title);
|
||||
|
||||
if (animeMatch.Success)
|
||||
{
|
||||
return animeMatch.Groups["subgroup"].Value;
|
||||
}
|
||||
|
||||
title = CleanReleaseGroupRegex.Replace(title);
|
||||
|
||||
var exceptionReleaseGroupRegex = ExceptionReleaseGroupRegex.Matches(title);
|
||||
|
||||
if (exceptionReleaseGroupRegex.Count != 0)
|
||||
{
|
||||
return exceptionReleaseGroupRegex.OfType<Match>().Last().Groups["releasegroup"].Value;
|
||||
}
|
||||
|
||||
var exceptionExactMatch = ExceptionReleaseGroupRegexExact.Matches(title);
|
||||
|
||||
if (exceptionExactMatch.Count != 0)
|
||||
{
|
||||
return exceptionExactMatch.OfType<Match>().Last().Groups["releasegroup"].Value;
|
||||
}
|
||||
|
||||
var matches = ReleaseGroupRegex.Matches(title);
|
||||
|
||||
if (matches.Count != 0)
|
||||
{
|
||||
var group = matches.OfType<Match>().Last().Groups["releasegroup"].Value;
|
||||
|
||||
if (int.TryParse(group, out _))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (InvalidReleaseGroupRegex.IsMatch(group))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string RemoveFileExtension(string title)
|
||||
{
|
||||
title = FileExtensionRegex.Replace(title, m =>
|
||||
{
|
||||
var extension = m.Value.ToLower();
|
||||
if (MediaFiles.MediaFileExtensions.Extensions.Contains(extension) || new[] { ".par2", ".nzb" }.Contains(extension))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return m.Value;
|
||||
});
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
public static bool HasMultipleLanguages(string title)
|
||||
{
|
||||
return MultiRegex.IsMatch(title);
|
||||
|
|
@ -697,7 +594,7 @@ private static bool ValidateBeforeParsing(string title)
|
|||
return false;
|
||||
}
|
||||
|
||||
var titleWithoutExtension = RemoveFileExtension(title);
|
||||
var titleWithoutExtension = FileExtensions.RemoveFileExtension(title);
|
||||
|
||||
if (RejectHashedReleasesRegex.Any(v => v.IsMatch(titleWithoutExtension)))
|
||||
{
|
||||
|
|
|
|||
23
src/NzbDrone.Core/Parser/ParserCommon.cs
Normal file
23
src/NzbDrone.Core/Parser/ParserCommon.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace NzbDrone.Core.Parser;
|
||||
|
||||
// These are functions shared between different parser functions
|
||||
// they are not intended to be used outside of them parsing.
|
||||
internal static class ParserCommon
|
||||
{
|
||||
internal static readonly RegexReplace[] PreSubstitutionRegex = System.Array.Empty<RegexReplace>();
|
||||
|
||||
// Valid TLDs http://data.iana.org/TLD/tlds-alpha-by-domain.txt
|
||||
internal static readonly RegexReplace WebsitePrefixRegex = new (@"^(?:(?:\[|\()\s*)?(?:www\.)?[-a-z0-9-]{1,256}\.(?<!Naruto-Kun\.)(?:[a-z]{2,6}\.[a-z]{2,6}|xn--[a-z0-9-]{4,}|[a-z]{2,})\b(?:\s*(?:\]|\))|[ -]{2,})[ -]*",
|
||||
string.Empty,
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
internal static readonly RegexReplace WebsitePostfixRegex = new (@"(?:\[\s*)?(?:www\.)?[-a-z0-9-]{1,256}\.(?:xn--[a-z0-9-]{4,}|[a-z]{2,6})\b(?:\s*\])$",
|
||||
string.Empty,
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
internal static readonly RegexReplace CleanTorrentSuffixRegex = new (@"\[(?:ettv|rartv|rarbg|cttv|publichd)\]$",
|
||||
string.Empty,
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
}
|
||||
87
src/NzbDrone.Core/Parser/ReleaseGroupParser.cs
Normal file
87
src/NzbDrone.Core/Parser/ReleaseGroupParser.cs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
|
||||
namespace NzbDrone.Core.Parser;
|
||||
|
||||
public static class ReleaseGroupParser
|
||||
{
|
||||
private static readonly Regex ReleaseGroupRegex = new (@"-(?<releasegroup>[a-z0-9]+(?<part2>-[a-z0-9]+)?(?!.+?(?:480p|576p|720p|1080p|2160p)))(?<!(?:WEB-(DL|Rip)|Blu-Ray|480p|576p|720p|1080p|2160p|DTS-HD|DTS-X|DTS-MA|DTS-ES|-ES|-EN|-CAT|-ENG|-JAP|-GER|-FRA|-FRE|-ITA|-HDRip|\d{1,2}-bit|[ ._]\d{4}-\d{2}|-\d{2}|tmdb(id)?-(?<tmdbid>\d+)|(?<imdbid>tt\d{7,8}))(?:\k<part2>)?)(?:\b|[-._ ]|$)|[-._ ]\[(?<releasegroup>[a-z0-9]+)\]$",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex InvalidReleaseGroupRegex = new (@"^([se]\d+|[0-9a-f]{8})$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex AnimeReleaseGroupRegex = new (@"^(?:\[(?<subgroup>(?!\s).+?(?<!\s))\](?:_|-|\s|\.)?)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
// Handle Exception Release Groups that don't follow -RlsGrp; Manual List
|
||||
// name only...be very careful with this last; high chance of false positives
|
||||
private static readonly Regex ExceptionReleaseGroupRegexExact = new (@"\b(?<releasegroup>KRaLiMaRKo|E\.N\.D|D\-Z0N3|Koten_Gars|BluDragon|ZØNEHD|HQMUX|VARYG|YIFY|YTS(.(MX|LT|AG))?|TMd|Eml HDTeam|LMain|DarQ|BEN THE MEN|TAoE|QxR|126811)\b", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
// groups whose releases end with RlsGroup) or RlsGroup]
|
||||
private static readonly Regex ExceptionReleaseGroupRegex = new (@"(?<=[._ \[])(?<releasegroup>(Silence|afm72|Panda|Ghost|MONOLITH|Tigole|Joy|ImE|UTR|t3nzin|Anime Time|Project Angel|Hakata Ramen|HONE|Vyndros|SEV|Garshasp|Kappa|Natty|RCVR|SAMPA|YOGI|r00t|EDGE2020|RZeroX|FreetheFish|Anna|Bandi|Qman|theincognito|HDO|DusIctv|DHD|CtrlHD|-ZR-|ADC|XZVN|RH|Kametsu)(?=\]|\)))", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly RegexReplace CleanReleaseGroupRegex = new (@"(-(RP|1|NZBGeek|Obfuscated|Obfuscation|Scrambled|sample|Pre|postbot|xpost|Rakuv[a-z0-9]*|WhiteRev|BUYMORE|AsRequested|AlternativeToRequested|GEROV|Z0iDS3N|Chamele0n|4P|4Planet|AlteZachen|RePACKPOST))+$",
|
||||
string.Empty,
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
public static string ParseReleaseGroup(string title)
|
||||
{
|
||||
title = title.Trim();
|
||||
title = FileExtensions.RemoveFileExtension(title);
|
||||
foreach (var replace in ParserCommon.PreSubstitutionRegex)
|
||||
{
|
||||
if (replace.TryReplace(ref title))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
title = ParserCommon.WebsitePrefixRegex.Replace(title);
|
||||
title = ParserCommon.CleanTorrentSuffixRegex.Replace(title);
|
||||
|
||||
var animeMatch = AnimeReleaseGroupRegex.Match(title);
|
||||
|
||||
if (animeMatch.Success)
|
||||
{
|
||||
return animeMatch.Groups["subgroup"].Value;
|
||||
}
|
||||
|
||||
title = CleanReleaseGroupRegex.Replace(title);
|
||||
|
||||
var exceptionExactMatch = ExceptionReleaseGroupRegexExact.Matches(title);
|
||||
|
||||
if (exceptionExactMatch.Count != 0)
|
||||
{
|
||||
return exceptionExactMatch.OfType<Match>().Last().Groups["releasegroup"].Value;
|
||||
}
|
||||
|
||||
var exceptionReleaseGroupRegex = ExceptionReleaseGroupRegex.Matches(title);
|
||||
|
||||
if (exceptionReleaseGroupRegex.Count != 0)
|
||||
{
|
||||
return exceptionReleaseGroupRegex.OfType<Match>().Last().Groups["releasegroup"].Value;
|
||||
}
|
||||
|
||||
var matches = ReleaseGroupRegex.Matches(title);
|
||||
|
||||
if (matches.Count != 0)
|
||||
{
|
||||
var group = matches.OfType<Match>().Last().Groups["releasegroup"].Value;
|
||||
|
||||
if (int.TryParse(group, out _))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (InvalidReleaseGroupRegex.IsMatch(group))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -260,7 +260,7 @@ public QualityProfile GetDefaultProfile(string name, Quality cutoff = null, para
|
|||
Name = name,
|
||||
Cutoff = profileCutoff,
|
||||
Items = items,
|
||||
Language = Language.English,
|
||||
Language = Language.Original,
|
||||
MinFormatScore = 0,
|
||||
CutoffFormatScore = 0,
|
||||
MinUpgradeFormatScore = 1,
|
||||
|
|
|
|||
|
|
@ -1,32 +1,33 @@
|
|||
<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="Diacritical.Net" Version="1.0.4" />
|
||||
<PackageReference Include="Equ" Version="2.3.0" />
|
||||
<PackageReference Include="MailKit" Version="4.8.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="6.0.35" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="2.1.7" />
|
||||
<PackageReference Include="Npgsql" Version="7.0.10" />
|
||||
<PackageReference Include="MailKit" Version="4.13.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="8.0.17" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.1" />
|
||||
<PackageReference Include="Npgsql" Version="9.0.3" />
|
||||
<PackageReference Include="Polly" Version="8.6.0" />
|
||||
<PackageReference Include="Servarr.FFMpegCore" Version="4.7.0-26" />
|
||||
<PackageReference Include="Servarr.FFprobe" Version="5.1.4.112" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.20" />
|
||||
<PackageReference Include="System.Memory" Version="4.6.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
||||
<PackageReference Include="Servarr.FluentMigrator.Runner" Version="3.3.2.9" />
|
||||
<PackageReference Include="Servarr.FluentMigrator.Runner.SQLite" Version="3.3.2.9" />
|
||||
<PackageReference Include="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||
<PackageReference Include="FluentMigrator.Runner.Core" Version="6.2.0" />
|
||||
<PackageReference Include="FluentMigrator.Runner.SQLite" Version="6.2.0" />
|
||||
<PackageReference Include="FluentMigrator.Runner.Postgres" Version="6.2.0" />
|
||||
<PackageReference Include="FluentValidation" Version="9.5.4" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NLog" Version="5.4.0" />
|
||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||
<PackageReference Include="MonoTorrent" Version="2.0.7" />
|
||||
<PackageReference Include="System.Text.Json" Version="6.0.10" />
|
||||
<PackageReference Include="MonoTorrent" Version="3.0.2" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.5" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Common\Radarr.Common.csproj" />
|
||||
|
|
|
|||
|
|
@ -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,12 +1,12 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0</TargetFrameworks>
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.2" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.6.2" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="8.1.4" />
|
||||
<PackageReference Include="DryIoc.dll" Version="5.4.3" />
|
||||
<PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using DryIoc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
|
@ -32,6 +33,7 @@
|
|||
using Radarr.Http.ErrorManagement;
|
||||
using Radarr.Http.Frontend;
|
||||
using Radarr.Http.Middleware;
|
||||
using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
|
||||
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
|
||||
|
||||
namespace NzbDrone.Host
|
||||
|
|
@ -60,8 +62,11 @@ public void ConfigureServices(IServiceCollection services)
|
|||
services.Configure<ForwardedHeadersOptions>(options =>
|
||||
{
|
||||
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost;
|
||||
options.KnownNetworks.Clear();
|
||||
options.KnownProxies.Clear();
|
||||
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("10.0.0.0"), 8));
|
||||
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("172.16.0.0"), 12));
|
||||
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("192.168.0.0"), 16));
|
||||
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("fc00::"), 7));
|
||||
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("fe80::"), 10));
|
||||
});
|
||||
|
||||
services.AddRouting(options => options.LowercaseUrls = true);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0</TargetFrameworks>
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.35" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.17" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Test.Common\Radarr.Test.Common.csproj" />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Test.Common\Radarr.Test.Common.csproj" />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<!--
|
||||
The netstandard version here doesn't work in net framework
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0</TargetFrameworks>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
<!--
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0</TargetFrameworks>
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ public void Start(bool enableAuth = false)
|
|||
|
||||
if (BuildInfo.IsDebug)
|
||||
{
|
||||
Start(Path.Combine(TestContext.CurrentContext.TestDirectory, "..", "..", "_output", "net6.0", consoleExe));
|
||||
Start(Path.Combine(TestContext.CurrentContext.TestDirectory, "..", "..", "_output", "net8.0", consoleExe));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.1" />
|
||||
<PackageReference Include="FluentValidation" Version="9.5.4" />
|
||||
<PackageReference Include="Moq" Version="4.16.1" />
|
||||
<PackageReference Include="Moq" Version="4.18.4" />
|
||||
<PackageReference Include="NLog" Version="5.4.0" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
<PackageReference Include="RestSharp" Version="106.15.0" />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Test.Common\Radarr.Test.Common.csproj" />
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DryIoc.dll" Version="5.4.3" />
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue