mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-04-18 21:55:12 -04:00
Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e05d86a58 | |||
| 4f83116413 | |||
| d440bc079f | |||
| a5c7c6cbcb | |||
| 710c3d6deb | |||
| 1959efbd09 | |||
| c4468b9cbb | |||
| 8762d94dda | |||
| e07ea80977 | |||
| 37c393a659 | |||
| b4b779df5c | |||
| 1dbf35deb5 | |||
| 6ab226c43a | |||
| 3a896fc43e | |||
| a81d632878 | |||
| cb8c0d4aa7 | |||
| 788a5a3e24 | |||
| 4f056bf228 | |||
| 6ce9c779c1 | |||
| 32fc2ec365 | |||
| 9ddd38a334 | |||
| b45e5a5e38 | |||
| 135efe6d94 | |||
| 69de6d18eb | |||
| 627da14a32 | |||
| 83bf3620c0 | |||
| fa41bf3c58 | |||
| 31e7a101ef | |||
| 22f9e9e6b7 | |||
| 8834431ba6 | |||
| 01cc9b3d07 | |||
| a0cbe1de5d | |||
| 75792c0760 | |||
| 83ca724120 | |||
| aee6ee1a00 | |||
| e66c69a292 | |||
| 930370729b | |||
| 2f5b55013a | |||
| cb74fade18 | |||
| e0da422b0e | |||
| 77caaa2a55 | |||
| 55d03967ec | |||
| 684c30893a | |||
| 3b51a3a618 | |||
| 09bd8137fc | |||
| 495daf7967 | |||
| bb3821c254 | |||
| 855f8d35f2 | |||
| 15dab381af | |||
| 9c5a88e2e7 | |||
| 80d295cce5 | |||
| 76afb70b01 | |||
| c29fba3a2b | |||
| 68e41f0860 | |||
| 4c68645175 | |||
| aaef8fb29c | |||
| b2e300b6da | |||
| 77c840b03a | |||
| 98c3408909 | |||
| 40f6c2e59d | |||
| 64c9bb4231 | |||
| 94ef3ea88f | |||
| dab4500b16 | |||
| d951943c67 | |||
| bba6f9349b | |||
| 7e0f88ad7a | |||
| 88c6cbf943 | |||
| 69d31f96de | |||
| a5718ad937 | |||
| 505d9c151d | |||
| a637677ec4 | |||
| a0d5421dc8 | |||
| 95f62be50c | |||
| d7b5100e35 | |||
| 64c1e1fa54 | |||
| 74663ea077 |
+13
-16
@@ -13,7 +13,9 @@ variables:
|
|||||||
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
|
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
|
||||||
sentryOrg: 'servarr'
|
sentryOrg: 'servarr'
|
||||||
sentryUrl: 'https://sentry.servarr.com'
|
sentryUrl: 'https://sentry.servarr.com'
|
||||||
dotnetVersion: '6.0.100'
|
dotnetVersion: '6.0.201'
|
||||||
|
innoVersion: '6.2.0'
|
||||||
|
nodeVersion: '16.x'
|
||||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||||
|
|
||||||
trigger:
|
trigger:
|
||||||
@@ -155,7 +157,7 @@ stages:
|
|||||||
- task: NodeTool@0
|
- task: NodeTool@0
|
||||||
displayName: Set Node.js version
|
displayName: Set Node.js version
|
||||||
inputs:
|
inputs:
|
||||||
versionSpec: '12.x'
|
versionSpec: $(nodeVersion)
|
||||||
- checkout: self
|
- checkout: self
|
||||||
submodules: true
|
submodules: true
|
||||||
fetchDepth: 1
|
fetchDepth: 1
|
||||||
@@ -200,16 +202,11 @@ stages:
|
|||||||
artifactName: WindowsFrontend
|
artifactName: WindowsFrontend
|
||||||
targetPath: _output
|
targetPath: _output
|
||||||
displayName: Fetch Frontend
|
displayName: Fetch Frontend
|
||||||
- bash: ./build.sh --packages
|
|
||||||
displayName: Create Packages
|
|
||||||
- bash: |
|
- bash: |
|
||||||
distribution/windows/setup/inno/ISCC.exe distribution/windows/setup/prowlarr.iss //DFramework=net6.0 //DRuntime=win-x86
|
./build.sh --packages --installer
|
||||||
cp distribution/windows/setup/output/Prowlarr.*windows.net6.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Prowlarr.${BUILDNAME}.windows-core-x86-installer.exe
|
cp distribution/windows/setup/output/Prowlarr.*win-x64.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Prowlarr.${BUILDNAME}.windows-core-x64-installer.exe
|
||||||
displayName: Create x86 .NET Core Windows installer
|
cp distribution/windows/setup/output/Prowlarr.*win-x86.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Prowlarr.${BUILDNAME}.windows-core-x86-installer.exe
|
||||||
- bash: |
|
displayName: Create Installers
|
||||||
distribution/windows/setup/inno/ISCC.exe distribution/windows/setup/prowlarr.iss //DFramework=net6.0 //DRuntime=win-x64
|
|
||||||
cp distribution/windows/setup/output/Prowlarr.*windows.net6.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Prowlarr.${BUILDNAME}.windows-core-x64-installer.exe
|
|
||||||
displayName: Create x64 .NET Core Windows installer
|
|
||||||
- publish: $(Build.ArtifactStagingDirectory)
|
- publish: $(Build.ArtifactStagingDirectory)
|
||||||
artifact: 'WindowsInstaller'
|
artifact: 'WindowsInstaller'
|
||||||
displayName: Publish Installer
|
displayName: Publish Installer
|
||||||
@@ -706,17 +703,17 @@ stages:
|
|||||||
osName: 'Linux'
|
osName: 'Linux'
|
||||||
imageName: 'ubuntu-18.04'
|
imageName: 'ubuntu-18.04'
|
||||||
pattern: 'Prowlarr.*.linux-core-x64.tar.gz'
|
pattern: 'Prowlarr.*.linux-core-x64.tar.gz'
|
||||||
failBuild: false
|
failBuild: true
|
||||||
Mac:
|
Mac:
|
||||||
osName: 'Mac'
|
osName: 'Mac'
|
||||||
imageName: 'macos-10.15'
|
imageName: 'macos-10.15'
|
||||||
pattern: 'Prowlarr.*.osx-core-x64.tar.gz'
|
pattern: 'Prowlarr.*.osx-core-x64.tar.gz'
|
||||||
failBuild: false
|
failBuild: true
|
||||||
Windows:
|
Windows:
|
||||||
osName: 'Windows'
|
osName: 'Windows'
|
||||||
imageName: 'windows-2019'
|
imageName: 'windows-2019'
|
||||||
pattern: 'Prowlarr.*.windows-core-x64.zip'
|
pattern: 'Prowlarr.*.windows-core-x64.zip'
|
||||||
failBuild: false
|
failBuild: true
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: $(imageName)
|
vmImage: $(imageName)
|
||||||
@@ -807,7 +804,7 @@ stages:
|
|||||||
- task: NodeTool@0
|
- task: NodeTool@0
|
||||||
displayName: Set Node.js version
|
displayName: Set Node.js version
|
||||||
inputs:
|
inputs:
|
||||||
versionSpec: '12.x'
|
versionSpec: $(nodeVersion)
|
||||||
- checkout: self
|
- checkout: self
|
||||||
submodules: true
|
submodules: true
|
||||||
fetchDepth: 1
|
fetchDepth: 1
|
||||||
@@ -944,7 +941,7 @@ stages:
|
|||||||
- job:
|
- job:
|
||||||
displayName: Discord Notification
|
displayName: Discord Notification
|
||||||
pool:
|
pool:
|
||||||
vmImage: 'windows-2019'
|
vmImage: 'ubuntu-18.04'
|
||||||
steps:
|
steps:
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
continueOnError: true
|
continueOnError: true
|
||||||
|
|||||||
@@ -234,6 +234,32 @@ Package()
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BuildInstaller()
|
||||||
|
{
|
||||||
|
local framework="$1"
|
||||||
|
local runtime="$2"
|
||||||
|
|
||||||
|
./_inno/ISCC.exe distribution/windows/setup/prowlarr.iss "//DFramework=$framework" "//DRuntime=$runtime"
|
||||||
|
}
|
||||||
|
|
||||||
|
InstallInno()
|
||||||
|
{
|
||||||
|
ProgressStart "Installing portable Inno Setup"
|
||||||
|
|
||||||
|
rm -rf _inno
|
||||||
|
curl -s --output innosetup.exe "https://files.jrsoftware.org/is/6/innosetup-${INNOVERSION:-6.2.0}.exe"
|
||||||
|
mkdir _inno
|
||||||
|
./innosetup.exe //portable=1 //silent //currentuser //dir=.\\_inno
|
||||||
|
rm innosetup.exe
|
||||||
|
|
||||||
|
ProgressEnd "Installed portable Inno Setup"
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveInno()
|
||||||
|
{
|
||||||
|
rm -rf _inno
|
||||||
|
}
|
||||||
|
|
||||||
PackageTests()
|
PackageTests()
|
||||||
{
|
{
|
||||||
local framework="$1"
|
local framework="$1"
|
||||||
@@ -265,6 +291,7 @@ if [ $# -eq 0 ]; then
|
|||||||
BACKEND=YES
|
BACKEND=YES
|
||||||
FRONTEND=YES
|
FRONTEND=YES
|
||||||
PACKAGES=YES
|
PACKAGES=YES
|
||||||
|
INSTALLER=NO
|
||||||
LINT=YES
|
LINT=YES
|
||||||
ENABLE_BSD=NO
|
ENABLE_BSD=NO
|
||||||
fi
|
fi
|
||||||
@@ -300,6 +327,10 @@ case $key in
|
|||||||
PACKAGES=YES
|
PACKAGES=YES
|
||||||
shift # past argument
|
shift # past argument
|
||||||
;;
|
;;
|
||||||
|
--installer)
|
||||||
|
INSTALLER=YES
|
||||||
|
shift # past argument
|
||||||
|
;;
|
||||||
--lint)
|
--lint)
|
||||||
LINT=YES
|
LINT=YES
|
||||||
shift # past argument
|
shift # past argument
|
||||||
@@ -383,3 +414,11 @@ then
|
|||||||
Package "$FRAMEWORK" "$RID"
|
Package "$FRAMEWORK" "$RID"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "$INSTALLER" = "YES" ];
|
||||||
|
then
|
||||||
|
InstallInno
|
||||||
|
BuildInstaller "net6.0" "win-x64"
|
||||||
|
BuildInstaller "net6.0" "win-x86"
|
||||||
|
RemoveInno
|
||||||
|
fi
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>prowlarr.icns</string>
|
<string>prowlarr.icns</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.osx.prowlarr.video</string>
|
<string>com.osx.prowlarr.com</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
#SET BUILD_NUMBER=1
|
|
||||||
#SET branch=develop
|
|
||||||
inno\ISCC.exe prowlarr.iss
|
|
||||||
@@ -1,336 +0,0 @@
|
|||||||
; *** Inno Setup version 5.5.3+ English messages ***
|
|
||||||
;
|
|
||||||
; To download user-contributed translations of this file, go to:
|
|
||||||
; http://www.jrsoftware.org/files/istrans/
|
|
||||||
;
|
|
||||||
; Note: When translating this text, do not add periods (.) to the end of
|
|
||||||
; messages that didn't have them already, because on those messages Inno
|
|
||||||
; Setup adds the periods automatically (appending a period would result in
|
|
||||||
; two periods being displayed).
|
|
||||||
|
|
||||||
[LangOptions]
|
|
||||||
; The following three entries are very important. Be sure to read and
|
|
||||||
; understand the '[LangOptions] section' topic in the help file.
|
|
||||||
LanguageName=English
|
|
||||||
LanguageID=$0409
|
|
||||||
LanguageCodePage=0
|
|
||||||
; If the language you are translating to requires special font faces or
|
|
||||||
; sizes, uncomment any of the following entries and change them accordingly.
|
|
||||||
;DialogFontName=
|
|
||||||
;DialogFontSize=8
|
|
||||||
;WelcomeFontName=Verdana
|
|
||||||
;WelcomeFontSize=12
|
|
||||||
;TitleFontName=Arial
|
|
||||||
;TitleFontSize=29
|
|
||||||
;CopyrightFontName=Arial
|
|
||||||
;CopyrightFontSize=8
|
|
||||||
|
|
||||||
[Messages]
|
|
||||||
|
|
||||||
; *** Application titles
|
|
||||||
SetupAppTitle=Setup
|
|
||||||
SetupWindowTitle=Setup - %1
|
|
||||||
UninstallAppTitle=Uninstall
|
|
||||||
UninstallAppFullTitle=%1 Uninstall
|
|
||||||
|
|
||||||
; *** Misc. common
|
|
||||||
InformationTitle=Information
|
|
||||||
ConfirmTitle=Confirm
|
|
||||||
ErrorTitle=Error
|
|
||||||
|
|
||||||
; *** SetupLdr messages
|
|
||||||
SetupLdrStartupMessage=This will install %1. Do you wish to continue?
|
|
||||||
LdrCannotCreateTemp=Unable to create a temporary file. Setup aborted
|
|
||||||
LdrCannotExecTemp=Unable to execute file in the temporary directory. Setup aborted
|
|
||||||
|
|
||||||
; *** Startup error messages
|
|
||||||
LastErrorMessage=%1.%n%nError %2: %3
|
|
||||||
SetupFileMissing=The file %1 is missing from the installation directory. Please correct the problem or obtain a new copy of the program.
|
|
||||||
SetupFileCorrupt=The setup files are corrupted. Please obtain a new copy of the program.
|
|
||||||
SetupFileCorruptOrWrongVer=The setup files are corrupted, or are incompatible with this version of Setup. Please correct the problem or obtain a new copy of the program.
|
|
||||||
InvalidParameter=An invalid parameter was passed on the command line:%n%n%1
|
|
||||||
SetupAlreadyRunning=Setup is already running.
|
|
||||||
WindowsVersionNotSupported=This program does not support the version of Windows your computer is running.
|
|
||||||
WindowsServicePackRequired=This program requires %1 Service Pack %2 or later.
|
|
||||||
NotOnThisPlatform=This program will not run on %1.
|
|
||||||
OnlyOnThisPlatform=This program must be run on %1.
|
|
||||||
OnlyOnTheseArchitectures=This program can only be installed on versions of Windows designed for the following processor architectures:%n%n%1
|
|
||||||
MissingWOW64APIs=The version of Windows you are running does not include functionality required by Setup to perform a 64-bit installation. To correct this problem, please install Service Pack %1.
|
|
||||||
WinVersionTooLowError=This program requires %1 version %2 or later.
|
|
||||||
WinVersionTooHighError=This program cannot be installed on %1 version %2 or later.
|
|
||||||
AdminPrivilegesRequired=You must be logged in as an administrator when installing this program.
|
|
||||||
PowerUserPrivilegesRequired=You must be logged in as an administrator or as a member of the Power Users group when installing this program.
|
|
||||||
SetupAppRunningError=Setup has detected that %1 is currently running.%n%nPlease close all instances of it now, then click OK to continue, or Cancel to exit.
|
|
||||||
UninstallAppRunningError=Uninstall has detected that %1 is currently running.%n%nPlease close all instances of it now, then click OK to continue, or Cancel to exit.
|
|
||||||
|
|
||||||
; *** Misc. errors
|
|
||||||
ErrorCreatingDir=Setup was unable to create the directory "%1"
|
|
||||||
ErrorTooManyFilesInDir=Unable to create a file in the directory "%1" because it contains too many files
|
|
||||||
|
|
||||||
; *** Setup common messages
|
|
||||||
ExitSetupTitle=Exit Setup
|
|
||||||
ExitSetupMessage=Setup is not complete. If you exit now, the program will not be installed.%n%nYou may run Setup again at another time to complete the installation.%n%nExit Setup?
|
|
||||||
AboutSetupMenuItem=&About Setup...
|
|
||||||
AboutSetupTitle=About Setup
|
|
||||||
AboutSetupMessage=%1 version %2%n%3%n%n%1 home page:%n%4
|
|
||||||
AboutSetupNote=
|
|
||||||
TranslatorNote=
|
|
||||||
|
|
||||||
; *** Buttons
|
|
||||||
ButtonBack=< &Back
|
|
||||||
ButtonNext=&Next >
|
|
||||||
ButtonInstall=&Install
|
|
||||||
ButtonOK=OK
|
|
||||||
ButtonCancel=Cancel
|
|
||||||
ButtonYes=&Yes
|
|
||||||
ButtonYesToAll=Yes to &All
|
|
||||||
ButtonNo=&No
|
|
||||||
ButtonNoToAll=N&o to All
|
|
||||||
ButtonFinish=&Finish
|
|
||||||
ButtonBrowse=&Browse...
|
|
||||||
ButtonWizardBrowse=B&rowse...
|
|
||||||
ButtonNewFolder=&Make New Folder
|
|
||||||
|
|
||||||
; *** "Select Language" dialog messages
|
|
||||||
SelectLanguageTitle=Select Setup Language
|
|
||||||
SelectLanguageLabel=Select the language to use during the installation:
|
|
||||||
|
|
||||||
; *** Common wizard text
|
|
||||||
ClickNext=Click Next to continue, or Cancel to exit Setup.
|
|
||||||
BeveledLabel=
|
|
||||||
BrowseDialogTitle=Browse For Folder
|
|
||||||
BrowseDialogLabel=Select a folder in the list below, then click OK.
|
|
||||||
NewFolderName=New Folder
|
|
||||||
|
|
||||||
; *** "Welcome" wizard page
|
|
||||||
WelcomeLabel1=Welcome to the [name] Setup Wizard
|
|
||||||
WelcomeLabel2=This will install [name/ver] on your computer.%n%nIt is recommended that you close all other applications before continuing.
|
|
||||||
|
|
||||||
; *** "Password" wizard page
|
|
||||||
WizardPassword=Password
|
|
||||||
PasswordLabel1=This installation is password protected.
|
|
||||||
PasswordLabel3=Please provide the password, then click Next to continue. Passwords are case-sensitive.
|
|
||||||
PasswordEditLabel=&Password:
|
|
||||||
IncorrectPassword=The password you entered is not correct. Please try again.
|
|
||||||
|
|
||||||
; *** "License Agreement" wizard page
|
|
||||||
WizardLicense=License Agreement
|
|
||||||
LicenseLabel=Please read the following important information before continuing.
|
|
||||||
LicenseLabel3=Please read the following License Agreement. You must accept the terms of this agreement before continuing with the installation.
|
|
||||||
LicenseAccepted=I &accept the agreement
|
|
||||||
LicenseNotAccepted=I &do not accept the agreement
|
|
||||||
|
|
||||||
; *** "Information" wizard pages
|
|
||||||
WizardInfoBefore=Information
|
|
||||||
InfoBeforeLabel=Please read the following important information before continuing.
|
|
||||||
InfoBeforeClickLabel=When you are ready to continue with Setup, click Next.
|
|
||||||
WizardInfoAfter=Information
|
|
||||||
InfoAfterLabel=Please read the following important information before continuing.
|
|
||||||
InfoAfterClickLabel=When you are ready to continue with Setup, click Next.
|
|
||||||
|
|
||||||
; *** "User Information" wizard page
|
|
||||||
WizardUserInfo=User Information
|
|
||||||
UserInfoDesc=Please enter your information.
|
|
||||||
UserInfoName=&User Name:
|
|
||||||
UserInfoOrg=&Organization:
|
|
||||||
UserInfoSerial=&Serial Number:
|
|
||||||
UserInfoNameRequired=You must enter a name.
|
|
||||||
|
|
||||||
; *** "Select Destination Location" wizard page
|
|
||||||
WizardSelectDir=Select Destination Location
|
|
||||||
SelectDirDesc=Where should [name] be installed?
|
|
||||||
SelectDirLabel3=Setup will install [name] into the following folder.
|
|
||||||
SelectDirBrowseLabel=To continue, click Next. If you would like to select a different folder, click Browse.
|
|
||||||
DiskSpaceMBLabel=At least [mb] MB of free disk space is required.
|
|
||||||
CannotInstallToNetworkDrive=Setup cannot install to a network drive.
|
|
||||||
CannotInstallToUNCPath=Setup cannot install to a UNC path.
|
|
||||||
InvalidPath=You must enter a full path with drive letter; for example:%n%nC:\APP%n%nor a UNC path in the form:%n%n\\server\share
|
|
||||||
InvalidDrive=The drive or UNC share you selected does not exist or is not accessible. Please select another.
|
|
||||||
DiskSpaceWarningTitle=Not Enough Disk Space
|
|
||||||
DiskSpaceWarning=Setup requires at least %1 KB of free space to install, but the selected drive only has %2 KB available.%n%nDo you want to continue anyway?
|
|
||||||
DirNameTooLong=The folder name or path is too long.
|
|
||||||
InvalidDirName=The folder name is not valid.
|
|
||||||
BadDirName32=Folder names cannot include any of the following characters:%n%n%1
|
|
||||||
DirExistsTitle=Folder Exists
|
|
||||||
DirExists=The folder:%n%n%1%n%nalready exists. Would you like to install to that folder anyway?
|
|
||||||
DirDoesntExistTitle=Folder Does Not Exist
|
|
||||||
DirDoesntExist=The folder:%n%n%1%n%ndoes not exist. Would you like the folder to be created?
|
|
||||||
|
|
||||||
; *** "Select Components" wizard page
|
|
||||||
WizardSelectComponents=Select Components
|
|
||||||
SelectComponentsDesc=Which components should be installed?
|
|
||||||
SelectComponentsLabel2=Select the components you want to install; clear the components you do not want to install. Click Next when you are ready to continue.
|
|
||||||
FullInstallation=Full installation
|
|
||||||
; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language)
|
|
||||||
CompactInstallation=Compact installation
|
|
||||||
CustomInstallation=Custom installation
|
|
||||||
NoUninstallWarningTitle=Components Exist
|
|
||||||
NoUninstallWarning=Setup has detected that the following components are already installed on your computer:%n%n%1%n%nDeselecting these components will not uninstall them.%n%nWould you like to continue anyway?
|
|
||||||
ComponentSize1=%1 KB
|
|
||||||
ComponentSize2=%1 MB
|
|
||||||
ComponentsDiskSpaceMBLabel=Current selection requires at least [mb] MB of disk space.
|
|
||||||
|
|
||||||
; *** "Select Additional Tasks" wizard page
|
|
||||||
WizardSelectTasks=Select Additional Tasks
|
|
||||||
SelectTasksDesc=Which additional tasks should be performed?
|
|
||||||
SelectTasksLabel2=Select the additional tasks you would like Setup to perform while installing [name], then click Next.
|
|
||||||
|
|
||||||
; *** "Select Start Menu Folder" wizard page
|
|
||||||
WizardSelectProgramGroup=Select Start Menu Folder
|
|
||||||
SelectStartMenuFolderDesc=Where should Setup place the program's shortcuts?
|
|
||||||
SelectStartMenuFolderLabel3=Setup will create the program's shortcuts in the following Start Menu folder.
|
|
||||||
SelectStartMenuFolderBrowseLabel=To continue, click Next. If you would like to select a different folder, click Browse.
|
|
||||||
MustEnterGroupName=You must enter a folder name.
|
|
||||||
GroupNameTooLong=The folder name or path is too long.
|
|
||||||
InvalidGroupName=The folder name is not valid.
|
|
||||||
BadGroupName=The folder name cannot include any of the following characters:%n%n%1
|
|
||||||
NoProgramGroupCheck2=&Don't create a Start Menu folder
|
|
||||||
|
|
||||||
; *** "Ready to Install" wizard page
|
|
||||||
WizardReady=Ready to Install
|
|
||||||
ReadyLabel1=Setup is now ready to begin installing [name] on your computer.
|
|
||||||
ReadyLabel2a=Click Install to continue with the installation, or click Back if you want to review or change any settings.
|
|
||||||
ReadyLabel2b=Click Install to continue with the installation.
|
|
||||||
ReadyMemoUserInfo=User information:
|
|
||||||
ReadyMemoDir=Destination location:
|
|
||||||
ReadyMemoType=Setup type:
|
|
||||||
ReadyMemoComponents=Selected components:
|
|
||||||
ReadyMemoGroup=Start Menu folder:
|
|
||||||
ReadyMemoTasks=Additional tasks:
|
|
||||||
|
|
||||||
; *** "Preparing to Install" wizard page
|
|
||||||
WizardPreparing=Preparing to Install
|
|
||||||
PreparingDesc=Setup is preparing to install [name] on your computer.
|
|
||||||
PreviousInstallNotCompleted=The installation/removal of a previous program was not completed. You will need to restart your computer to complete that installation.%n%nAfter restarting your computer, run Setup again to complete the installation of [name].
|
|
||||||
CannotContinue=Setup cannot continue. Please click Cancel to exit.
|
|
||||||
ApplicationsFound=The following applications are using files that need to be updated by Setup. It is recommended that you allow Setup to automatically close these applications.
|
|
||||||
ApplicationsFound2=The following applications are using files that need to be updated by Setup. It is recommended that you allow Setup to automatically close these applications. After the installation has completed, Setup will attempt to restart the applications.
|
|
||||||
CloseApplications=&Automatically close the applications
|
|
||||||
DontCloseApplications=&Do not close the applications
|
|
||||||
ErrorCloseApplications=Setup was unable to automatically close all applications. It is recommended that you close all applications using files that need to be updated by Setup before continuing.
|
|
||||||
|
|
||||||
; *** "Installing" wizard page
|
|
||||||
WizardInstalling=Installing
|
|
||||||
InstallingLabel=Please wait while Setup installs [name] on your computer.
|
|
||||||
|
|
||||||
; *** "Setup Completed" wizard page
|
|
||||||
FinishedHeadingLabel=Completing the [name] Setup Wizard
|
|
||||||
FinishedLabelNoIcons=Setup has finished installing [name] on your computer.
|
|
||||||
FinishedLabel=Setup has finished installing [name] on your computer. The application may be launched by selecting the installed icons.
|
|
||||||
ClickFinish=Click Finish to exit Setup.
|
|
||||||
FinishedRestartLabel=To complete the installation of [name], Setup must restart your computer. Would you like to restart now?
|
|
||||||
FinishedRestartMessage=To complete the installation of [name], Setup must restart your computer.%n%nWould you like to restart now?
|
|
||||||
ShowReadmeCheck=Yes, I would like to view the README file
|
|
||||||
YesRadio=&Yes, restart the computer now
|
|
||||||
NoRadio=&No, I will restart the computer later
|
|
||||||
; used for example as 'Run MyProg.exe'
|
|
||||||
RunEntryExec=Run %1
|
|
||||||
; used for example as 'View Readme.txt'
|
|
||||||
RunEntryShellExec=View %1
|
|
||||||
|
|
||||||
; *** "Setup Needs the Next Disk" stuff
|
|
||||||
ChangeDiskTitle=Setup Needs the Next Disk
|
|
||||||
SelectDiskLabel2=Please insert Disk %1 and click OK.%n%nIf the files on this disk can be found in a folder other than the one displayed below, enter the correct path or click Browse.
|
|
||||||
PathLabel=&Path:
|
|
||||||
FileNotInDir2=The file "%1" could not be located in "%2". Please insert the correct disk or select another folder.
|
|
||||||
SelectDirectoryLabel=Please specify the location of the next disk.
|
|
||||||
|
|
||||||
; *** Installation phase messages
|
|
||||||
SetupAborted=Setup was not completed.%n%nPlease correct the problem and run Setup again.
|
|
||||||
EntryAbortRetryIgnore=Click Retry to try again, Ignore to proceed anyway, or Abort to cancel installation.
|
|
||||||
|
|
||||||
; *** Installation status messages
|
|
||||||
StatusClosingApplications=Closing applications...
|
|
||||||
StatusCreateDirs=Creating directories...
|
|
||||||
StatusExtractFiles=Extracting files...
|
|
||||||
StatusCreateIcons=Creating shortcuts...
|
|
||||||
StatusCreateIniEntries=Creating INI entries...
|
|
||||||
StatusCreateRegistryEntries=Creating registry entries...
|
|
||||||
StatusRegisterFiles=Registering files...
|
|
||||||
StatusSavingUninstall=Saving uninstall information...
|
|
||||||
StatusRunProgram=Finishing installation...
|
|
||||||
StatusRestartingApplications=Restarting applications...
|
|
||||||
StatusRollback=Rolling back changes...
|
|
||||||
|
|
||||||
; *** Misc. errors
|
|
||||||
ErrorInternal2=Internal error: %1
|
|
||||||
ErrorFunctionFailedNoCode=%1 failed
|
|
||||||
ErrorFunctionFailed=%1 failed; code %2
|
|
||||||
ErrorFunctionFailedWithMessage=%1 failed; code %2.%n%3
|
|
||||||
ErrorExecutingProgram=Unable to execute file:%n%1
|
|
||||||
|
|
||||||
; *** Registry errors
|
|
||||||
ErrorRegOpenKey=Error opening registry key:%n%1\%2
|
|
||||||
ErrorRegCreateKey=Error creating registry key:%n%1\%2
|
|
||||||
ErrorRegWriteKey=Error writing to registry key:%n%1\%2
|
|
||||||
|
|
||||||
; *** INI errors
|
|
||||||
ErrorIniEntry=Error creating INI entry in file "%1".
|
|
||||||
|
|
||||||
; *** File copying errors
|
|
||||||
FileAbortRetryIgnore=Click Retry to try again, Ignore to skip this file (not recommended), or Abort to cancel installation.
|
|
||||||
FileAbortRetryIgnore2=Click Retry to try again, Ignore to proceed anyway (not recommended), or Abort to cancel installation.
|
|
||||||
SourceIsCorrupted=The source file is corrupted
|
|
||||||
SourceDoesntExist=The source file "%1" does not exist
|
|
||||||
ExistingFileReadOnly=The existing file is marked as read-only.%n%nClick Retry to remove the read-only attribute and try again, Ignore to skip this file, or Abort to cancel installation.
|
|
||||||
ErrorReadingExistingDest=An error occurred while trying to read the existing file:
|
|
||||||
FileExists=The file already exists.%n%nWould you like Setup to overwrite it?
|
|
||||||
ExistingFileNewer=The existing file is newer than the one Setup is trying to install. It is recommended that you keep the existing file.%n%nDo you want to keep the existing file?
|
|
||||||
ErrorChangingAttr=An error occurred while trying to change the attributes of the existing file:
|
|
||||||
ErrorCreatingTemp=An error occurred while trying to create a file in the destination directory:
|
|
||||||
ErrorReadingSource=An error occurred while trying to read the source file:
|
|
||||||
ErrorCopying=An error occurred while trying to copy a file:
|
|
||||||
ErrorReplacingExistingFile=An error occurred while trying to replace the existing file:
|
|
||||||
ErrorRestartReplace=RestartReplace failed:
|
|
||||||
ErrorRenamingTemp=An error occurred while trying to rename a file in the destination directory:
|
|
||||||
ErrorRegisterServer=Unable to register the DLL/OCX: %1
|
|
||||||
ErrorRegSvr32Failed=RegSvr32 failed with exit code %1
|
|
||||||
ErrorRegisterTypeLib=Unable to register the type library: %1
|
|
||||||
|
|
||||||
; *** Post-installation errors
|
|
||||||
ErrorOpeningReadme=An error occurred while trying to open the README file.
|
|
||||||
ErrorRestartingComputer=Setup was unable to restart the computer. Please do this manually.
|
|
||||||
|
|
||||||
; *** Uninstaller messages
|
|
||||||
UninstallNotFound=File "%1" does not exist. Cannot uninstall.
|
|
||||||
UninstallOpenError=File "%1" could not be opened. Cannot uninstall
|
|
||||||
UninstallUnsupportedVer=The uninstall log file "%1" is in a format not recognized by this version of the uninstaller. Cannot uninstall
|
|
||||||
UninstallUnknownEntry=An unknown entry (%1) was encountered in the uninstall log
|
|
||||||
ConfirmUninstall=Are you sure you want to completely remove %1 and all of its components?
|
|
||||||
UninstallOnlyOnWin64=This installation can only be uninstalled on 64-bit Windows.
|
|
||||||
OnlyAdminCanUninstall=This installation can only be uninstalled by a user with administrative privileges.
|
|
||||||
UninstallStatusLabel=Please wait while %1 is removed from your computer.
|
|
||||||
UninstalledAll=%1 was successfully removed from your computer.
|
|
||||||
UninstalledMost=%1 uninstall complete.%n%nSome elements could not be removed. These can be removed manually.
|
|
||||||
UninstalledAndNeedsRestart=To complete the uninstallation of %1, your computer must be restarted.%n%nWould you like to restart now?
|
|
||||||
UninstallDataCorrupted="%1" file is corrupted. Cannot uninstall
|
|
||||||
|
|
||||||
; *** Uninstallation phase messages
|
|
||||||
ConfirmDeleteSharedFileTitle=Remove Shared File?
|
|
||||||
ConfirmDeleteSharedFile2=The system indicates that the following shared file is no longer in use by any programs. Would you like for Uninstall to remove this shared file?%n%nIf any programs are still using this file and it is removed, those programs may not function properly. If you are unsure, choose No. Leaving the file on your system will not cause any harm.
|
|
||||||
SharedFileNameLabel=File name:
|
|
||||||
SharedFileLocationLabel=Location:
|
|
||||||
WizardUninstalling=Uninstall Status
|
|
||||||
StatusUninstalling=Uninstalling %1...
|
|
||||||
|
|
||||||
; *** Shutdown block reasons
|
|
||||||
ShutdownBlockReasonInstallingApp=Installing %1.
|
|
||||||
ShutdownBlockReasonUninstallingApp=Uninstalling %1.
|
|
||||||
|
|
||||||
; The custom messages below aren't used by Setup itself, but if you make
|
|
||||||
; use of them in your scripts, you'll want to translate them.
|
|
||||||
|
|
||||||
[CustomMessages]
|
|
||||||
|
|
||||||
NameAndVersion=%1 version %2
|
|
||||||
AdditionalIcons=Additional icons:
|
|
||||||
CreateDesktopIcon=Create a &desktop icon
|
|
||||||
CreateQuickLaunchIcon=Create a &Quick Launch icon
|
|
||||||
ProgramOnTheWeb=%1 on the Web
|
|
||||||
UninstallProgram=Uninstall %1
|
|
||||||
LaunchProgram=Launch %1
|
|
||||||
AssocFileExtension=&Associate %1 with the %2 file extension
|
|
||||||
AssocingFileExtension=Associating %1 with the %2 file extension...
|
|
||||||
AutoStartProgramGroupDescription=Startup:
|
|
||||||
AutoStartProgram=Automatically start %1
|
|
||||||
AddonHostProgramNotFound=%1 could not be located in the folder you selected.%n%nDo you want to continue anyway?
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 51 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.1 KiB |
Binary file not shown.
@@ -3,13 +3,12 @@
|
|||||||
|
|
||||||
#define AppName "Prowlarr"
|
#define AppName "Prowlarr"
|
||||||
#define AppPublisher "Team Prowlarr"
|
#define AppPublisher "Team Prowlarr"
|
||||||
#define AppURL "https://prowlarr.video/"
|
#define AppURL "https://prowlarr.com/"
|
||||||
#define ForumsURL "https://forums.prowlarr.video/"
|
#define ForumsURL "https://prowlarr.com/discord/"
|
||||||
#define AppExeName "Prowlarr.exe"
|
#define AppExeName "Prowlarr.exe"
|
||||||
#define BaseVersion GetEnv('MAJORVERSION')
|
#define BaseVersion GetEnv('MAJORVERSION')
|
||||||
#define BuildNumber GetEnv('MINORVERSION')
|
#define BuildNumber GetEnv('MINORVERSION')
|
||||||
#define BuildVersion GetEnv('PROWLARRVERSION')
|
#define BuildVersion GetEnv('PROWLARRVERSION')
|
||||||
#define BranchName GetEnv('BUILD_SOURCEBRANCHNAME')
|
|
||||||
|
|
||||||
[Setup]
|
[Setup]
|
||||||
; NOTE: The value of AppId uniquely identifies this application.
|
; NOTE: The value of AppId uniquely identifies this application.
|
||||||
@@ -22,15 +21,15 @@ AppPublisher={#AppPublisher}
|
|||||||
AppPublisherURL={#AppURL}
|
AppPublisherURL={#AppURL}
|
||||||
AppSupportURL={#ForumsURL}
|
AppSupportURL={#ForumsURL}
|
||||||
AppUpdatesURL={#AppURL}
|
AppUpdatesURL={#AppURL}
|
||||||
DefaultDirName={commonappdata}\Prowlarr\bin
|
DefaultDirName={commonappdata}\Prowlarr
|
||||||
DisableDirPage=yes
|
DisableDirPage=yes
|
||||||
DefaultGroupName={#AppName}
|
DefaultGroupName={#AppName}
|
||||||
DisableProgramGroupPage=yes
|
DisableProgramGroupPage=yes
|
||||||
OutputBaseFilename=Prowlarr.{#BranchName}.{#BuildVersion}.windows.{#Framework}
|
OutputBaseFilename=Prowlarr.{#BuildVersion}.{#Runtime}
|
||||||
SolidCompression=yes
|
SolidCompression=yes
|
||||||
AppCopyright=Creative Commons 3.0 License
|
AppCopyright=Creative Commons 3.0 License
|
||||||
AllowUNCPath=False
|
AllowUNCPath=False
|
||||||
UninstallDisplayIcon={app}\Prowlarr.exe
|
UninstallDisplayIcon={app}\bin\Prowlarr.exe
|
||||||
DisableReadyPage=True
|
DisableReadyPage=True
|
||||||
CompressionThreads=2
|
CompressionThreads=2
|
||||||
Compression=lzma2/normal
|
Compression=lzma2/normal
|
||||||
@@ -38,6 +37,7 @@ AppContact={#ForumsURL}
|
|||||||
VersionInfoVersion={#BaseVersion}.{#BuildNumber}
|
VersionInfoVersion={#BaseVersion}.{#BuildNumber}
|
||||||
SetupLogging=yes
|
SetupLogging=yes
|
||||||
OutputDir=output
|
OutputDir=output
|
||||||
|
WizardStyle=modern
|
||||||
|
|
||||||
[Languages]
|
[Languages]
|
||||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
@@ -48,28 +48,31 @@ Name: "windowsService"; Description: "Install Windows Service (Starts when the c
|
|||||||
Name: "startupShortcut"; Description: "Create shortcut in Startup folder (Starts when you log into Windows)"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
|
Name: "startupShortcut"; Description: "Create shortcut in Startup folder (Starts when you log into Windows)"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
|
||||||
Name: "none"; Description: "Do not start automatically"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
|
Name: "none"; Description: "Do not start automatically"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
|
||||||
|
|
||||||
|
[Dirs]
|
||||||
|
Name: "{app}"; Permissions: users-modify
|
||||||
|
|
||||||
[Files]
|
[Files]
|
||||||
Source: "..\..\..\_artifacts\{#Runtime}\{#Framework}\Prowlarr\Prowlarr.exe"; DestDir: "{app}"; Flags: ignoreversion
|
Source: "..\..\..\_artifacts\{#Runtime}\{#Framework}\Prowlarr\Prowlarr.exe"; DestDir: "{app}\bin"; Flags: ignoreversion
|
||||||
Source: "..\..\..\_artifacts\{#Runtime}\{#Framework}\Prowlarr\*"; Excludes: "Prowlarr.Update"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
Source: "..\..\..\_artifacts\{#Runtime}\{#Framework}\Prowlarr\*"; Excludes: "Prowlarr.Update"; DestDir: "{app}\bin"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||||
|
|
||||||
[Icons]
|
[Icons]
|
||||||
Name: "{group}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Parameters: "/icon"
|
Name: "{group}\{#AppName}"; Filename: "{app}\bin\{#AppExeName}"; Parameters: "/icon"
|
||||||
Name: "{commondesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Parameters: "/icon"; Tasks: desktopIcon
|
Name: "{commondesktop}\{#AppName}"; Filename: "{app}\bin\{#AppExeName}"; Parameters: "/icon"; Tasks: desktopIcon
|
||||||
Name: "{userstartup}\{#AppName}"; Filename: "{app}\Prowlarr.exe"; WorkingDir: "{app}"; Tasks: startupShortcut
|
Name: "{userstartup}\{#AppName}"; Filename: "{app}\bin\Prowlarr.exe"; WorkingDir: "{app}\bin"; Tasks: startupShortcut
|
||||||
|
|
||||||
[InstallDelete]
|
[InstallDelete]
|
||||||
Name: "{app}"; Type: filesandordirs
|
Name: "{app}\bin"; Type: filesandordirs
|
||||||
|
|
||||||
[Run]
|
[Run]
|
||||||
Filename: "{app}\Prowlarr.Console.exe"; StatusMsg: "Removing previous Windows Service"; Parameters: "/u /exitimmediately"; Flags: runhidden waituntilterminated;
|
Filename: "{app}\bin\Prowlarr.Console.exe"; StatusMsg: "Removing previous Windows Service"; Parameters: "/u /exitimmediately"; Flags: runhidden waituntilterminated;
|
||||||
Filename: "{app}\Prowlarr.Console.exe"; Description: "Enable Access from Other Devices"; StatusMsg: "Enabling Remote access"; Parameters: "/registerurl /exitimmediately"; Flags: postinstall runascurrentuser runhidden waituntilterminated; Tasks: startupShortcut none;
|
Filename: "{app}\bin\Prowlarr.Console.exe"; Description: "Enable Access from Other Devices"; StatusMsg: "Enabling Remote access"; Parameters: "/registerurl /exitimmediately"; Flags: postinstall runascurrentuser runhidden waituntilterminated; Tasks: startupShortcut none;
|
||||||
Filename: "{app}\Prowlarr.Console.exe"; StatusMsg: "Installing Windows Service"; Parameters: "/i /exitimmediately"; Flags: runhidden waituntilterminated; Tasks: windowsService
|
Filename: "{app}\bin\Prowlarr.Console.exe"; StatusMsg: "Installing Windows Service"; Parameters: "/i /exitimmediately"; Flags: runhidden waituntilterminated; Tasks: windowsService
|
||||||
Filename: "{app}\Prowlarr.exe"; Description: "Open Prowlarr Web UI"; Flags: postinstall skipifsilent nowait; Tasks: windowsService;
|
Filename: "{app}\bin\Prowlarr.exe"; Description: "Open Prowlarr Web UI"; Flags: postinstall skipifsilent nowait; Tasks: windowsService;
|
||||||
Filename: "{app}\Prowlarr.exe"; Description: "Start Prowlarr"; Flags: postinstall skipifsilent nowait; Tasks: startupShortcut none;
|
Filename: "{app}\bin\Prowlarr.exe"; Description: "Start Prowlarr"; Flags: postinstall skipifsilent nowait; Tasks: startupShortcut none;
|
||||||
|
|
||||||
[UninstallRun]
|
[UninstallRun]
|
||||||
Filename: "{app}\prowlarr.console.exe"; Parameters: "/u"; Flags: waituntilterminated skipifdoesntexist
|
Filename: "{app}\bin\prowlarr.console.exe"; Parameters: "/u"; Flags: waituntilterminated skipifdoesntexist
|
||||||
|
|
||||||
[Code]
|
[Code]
|
||||||
function PrepareToInstall(var NeedsRestart: Boolean): String;
|
function PrepareToInstall(var NeedsRestart: Boolean): String;
|
||||||
|
|||||||
@@ -27,11 +27,11 @@ dotnet clean $slnFile -c Release
|
|||||||
dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p:RuntimeIdentifiers=$RUNTIME -t:PublishAllRids
|
dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p:RuntimeIdentifiers=$RUNTIME -t:PublishAllRids
|
||||||
|
|
||||||
dotnet new tool-manifest
|
dotnet new tool-manifest
|
||||||
dotnet tool install --version 6.2.3 Swashbuckle.AspNetCore.Cli
|
dotnet tool install --version 6.3.0 Swashbuckle.AspNetCore.Cli
|
||||||
|
|
||||||
dotnet tool run swagger tofile --output ./src/Prowlarr.Api.V1/openapi.json "$outputFolder/net6.0/$RUNTIME/prowlarr.console.dll" v1 &
|
dotnet tool run swagger tofile --output ./src/Prowlarr.Api.V1/openapi.json "$outputFolder/net6.0/$RUNTIME/prowlarr.console.dll" v1 &
|
||||||
|
|
||||||
sleep 10
|
sleep 30
|
||||||
|
|
||||||
kill %1
|
kill %1
|
||||||
|
|
||||||
|
|||||||
@@ -47,10 +47,6 @@ class Link extends Component {
|
|||||||
el = 'a';
|
el = 'a';
|
||||||
linkProps.href = to;
|
linkProps.href = to;
|
||||||
linkProps.target = target || '_self';
|
linkProps.target = target || '_self';
|
||||||
} else if (to.startsWith(`${window.Prowlarr.urlBase}/`)) {
|
|
||||||
el = RouterLink;
|
|
||||||
linkProps.to = to;
|
|
||||||
linkProps.target = target;
|
|
||||||
} else {
|
} else {
|
||||||
el = RouterLink;
|
el = RouterLink;
|
||||||
linkProps.to = `${window.Prowlarr.urlBase}/${to.replace(/^\//, '')}`;
|
linkProps.to = `${window.Prowlarr.urlBase}/${to.replace(/^\//, '')}`;
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ function ModalError(props) {
|
|||||||
{translate('Close')}
|
{translate('Close')}
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>);
|
</ModalContent>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ModalError.propTypes = {
|
ModalError.propTypes = {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { Scrollbars } from 'react-custom-scrollbars';
|
import { Scrollbars } from 'react-custom-scrollbars-2';
|
||||||
import { scrollDirections } from 'Helpers/Props';
|
import { scrollDirections } from 'Helpers/Props';
|
||||||
import styles from './OverlayScroller.css';
|
import styles from './OverlayScroller.css';
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ const columns = [
|
|||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'description',
|
||||||
|
label: translate('Description'),
|
||||||
|
isSortable: false,
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'privacy',
|
name: 'privacy',
|
||||||
label: translate('Privacy'),
|
label: translate('Privacy'),
|
||||||
@@ -136,12 +142,12 @@ class AddIndexerModalContent extends Component {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
const errorMessage = getErrorMessage(error, 'Unable to load indexers');
|
const errorMessage = getErrorMessage(error, translate('UnableToLoadIndexers'));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
Add Indexer
|
{translate('AddIndexer')}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody
|
<ModalBody
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class SelectIndexerRow extends Component {
|
|||||||
privacy,
|
privacy,
|
||||||
name,
|
name,
|
||||||
language,
|
language,
|
||||||
|
description,
|
||||||
isExistingIndexer
|
isExistingIndexer
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@@ -61,6 +62,10 @@ class SelectIndexerRow extends Component {
|
|||||||
{language}
|
{language}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell>
|
||||||
|
{description}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell>
|
<TableRowCell>
|
||||||
{translate(firstCharToUpper(privacy))}
|
{translate(firstCharToUpper(privacy))}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
@@ -74,6 +79,7 @@ SelectIndexerRow.propTypes = {
|
|||||||
protocol: PropTypes.string.isRequired,
|
protocol: PropTypes.string.isRequired,
|
||||||
privacy: PropTypes.string.isRequired,
|
privacy: PropTypes.string.isRequired,
|
||||||
language: PropTypes.string.isRequired,
|
language: PropTypes.string.isRequired,
|
||||||
|
description: PropTypes.string.isRequired,
|
||||||
implementation: PropTypes.string.isRequired,
|
implementation: PropTypes.string.isRequired,
|
||||||
onIndexerSelect: PropTypes.func.isRequired,
|
onIndexerSelect: PropTypes.func.isRequired,
|
||||||
isExistingIndexer: PropTypes.bool.isRequired
|
isExistingIndexer: PropTypes.bool.isRequired
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import DescriptionList from 'Components/DescriptionList/DescriptionList';
|
|||||||
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
|
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
|
||||||
import DescriptionListItemDescription from 'Components/DescriptionList/DescriptionListItemDescription';
|
import DescriptionListItemDescription from 'Components/DescriptionList/DescriptionListItemDescription';
|
||||||
import DescriptionListItemTitle from 'Components/DescriptionList/DescriptionListItemTitle';
|
import DescriptionListItemTitle from 'Components/DescriptionList/DescriptionListItemTitle';
|
||||||
|
import FieldSet from 'Components/FieldSet';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
import ModalBody from 'Components/Modal/ModalBody';
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
import ModalContent from 'Components/Modal/ModalContent';
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
@@ -20,6 +21,7 @@ function IndexerInfoModalContent(props) {
|
|||||||
language,
|
language,
|
||||||
indexerUrls,
|
indexerUrls,
|
||||||
protocol,
|
protocol,
|
||||||
|
capabilities,
|
||||||
onModalClose
|
onModalClose
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -30,41 +32,78 @@ function IndexerInfoModalContent(props) {
|
|||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<DescriptionList>
|
<FieldSet legend={translate('IndexerDetails')}>
|
||||||
<DescriptionListItem
|
<div className={styles.groups}>
|
||||||
descriptionClassName={styles.description}
|
<DescriptionList>
|
||||||
title={translate('Id')}
|
<DescriptionListItem
|
||||||
data={id}
|
descriptionClassName={styles.description}
|
||||||
/>
|
title={translate('Id')}
|
||||||
<DescriptionListItem
|
data={id}
|
||||||
descriptionClassName={styles.description}
|
/>
|
||||||
title={translate('Description')}
|
<DescriptionListItem
|
||||||
data={description ? description : '-'}
|
descriptionClassName={styles.description}
|
||||||
/>
|
title={translate('Description')}
|
||||||
<DescriptionListItem
|
data={description ? description : '-'}
|
||||||
descriptionClassName={styles.description}
|
/>
|
||||||
title={translate('Encoding')}
|
<DescriptionListItem
|
||||||
data={encoding ? encoding : '-'}
|
descriptionClassName={styles.description}
|
||||||
/>
|
title={translate('Encoding')}
|
||||||
<DescriptionListItem
|
data={encoding ? encoding : '-'}
|
||||||
descriptionClassName={styles.description}
|
/>
|
||||||
title={translate('Language')}
|
<DescriptionListItem
|
||||||
data={language ?? '-'}
|
descriptionClassName={styles.description}
|
||||||
/>
|
title={translate('Language')}
|
||||||
|
data={language ?? '-'}
|
||||||
<DescriptionListItemTitle>Indexer Site</DescriptionListItemTitle>
|
/>
|
||||||
<DescriptionListItemDescription>
|
<DescriptionListItemTitle>{translate('IndexerSite')}</DescriptionListItemTitle>
|
||||||
<Link to={indexerUrls[0]}>{indexerUrls[0]}</Link>
|
<DescriptionListItemDescription>
|
||||||
</DescriptionListItemDescription>
|
<Link to={indexerUrls[0]}>{indexerUrls[0]}</Link>
|
||||||
|
</DescriptionListItemDescription>
|
||||||
<DescriptionListItemTitle>{`${protocol === 'usenet' ? 'Newznab' : 'Torznab'} Url`}</DescriptionListItemTitle>
|
<DescriptionListItemTitle>{`${protocol === 'usenet' ? 'Newznab' : 'Torznab'} Url`}</DescriptionListItemTitle>
|
||||||
<DescriptionListItemDescription>
|
<DescriptionListItemDescription>
|
||||||
{`${window.location.origin}${window.Prowlarr.urlBase}/${id}/api`}
|
{`${window.location.origin}${window.Prowlarr.urlBase}/${id}/api`}
|
||||||
</DescriptionListItemDescription>
|
</DescriptionListItemDescription>
|
||||||
|
</DescriptionList>
|
||||||
</DescriptionList>
|
</div>
|
||||||
|
</FieldSet>
|
||||||
|
<FieldSet legend={translate('SearchCapabilities')}>
|
||||||
|
<div className={styles.groups}>
|
||||||
|
<DescriptionList>
|
||||||
|
<DescriptionListItem
|
||||||
|
descriptionClassName={styles.description}
|
||||||
|
title={translate('RawSearchSupported')}
|
||||||
|
data={capabilities.supportsRawSearch ? translate('Yes') : translate('No')}
|
||||||
|
/>
|
||||||
|
<DescriptionListItem
|
||||||
|
descriptionClassName={styles.description}
|
||||||
|
title={translate('SearchTypes')}
|
||||||
|
data={capabilities.searchParams.length === 0 ? translate('NotSupported') : capabilities.searchParams[0]}
|
||||||
|
/>
|
||||||
|
<DescriptionListItem
|
||||||
|
descriptionClassName={styles.description}
|
||||||
|
title={translate('TVSearchTypes')}
|
||||||
|
data={capabilities.tvSearchParams.length === 0 ? translate('NotSupported') : capabilities.tvSearchParams.join(', ')}
|
||||||
|
/>
|
||||||
|
<DescriptionListItem
|
||||||
|
descriptionClassName={styles.description}
|
||||||
|
title={translate('MovieSearchTypes')}
|
||||||
|
data={capabilities.movieSearchParams.length === 0 ? translate('NotSupported') : capabilities.movieSearchParams.join(', ')}
|
||||||
|
/>
|
||||||
|
<DescriptionListItem
|
||||||
|
descriptionClassName={styles.description}
|
||||||
|
title={translate('BookSearchTypes')}
|
||||||
|
data={capabilities.bookSearchParams.length === 0 ? translate('NotSupported') : capabilities.bookSearchParams.join(', ')}
|
||||||
|
/>
|
||||||
|
<DescriptionListItem
|
||||||
|
descriptionClassName={styles.description}
|
||||||
|
title={translate('MusicSearchTypes')}
|
||||||
|
data={capabilities.musicSearchParams.length === 0 ? translate('NotSupported') : capabilities.musicSearchParams.join(', ')}
|
||||||
|
/>
|
||||||
|
</DescriptionList>
|
||||||
|
</div>
|
||||||
|
</FieldSet>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
</ModalContent>
|
</ModalContent >
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +115,7 @@ IndexerInfoModalContent.propTypes = {
|
|||||||
language: PropTypes.string.isRequired,
|
language: PropTypes.string.isRequired,
|
||||||
indexerUrls: PropTypes.arrayOf(PropTypes.string).isRequired,
|
indexerUrls: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||||
protocol: PropTypes.string.isRequired,
|
protocol: PropTypes.string.isRequired,
|
||||||
|
capabilities: PropTypes.object.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ class QueryParameterModal extends Component {
|
|||||||
onSelectionChange={this.onInputSelectionChange}
|
onSelectionChange={this.onInputSelectionChange}
|
||||||
/>
|
/>
|
||||||
<Button onPress={onModalClose}>
|
<Button onPress={onModalClose}>
|
||||||
Close
|
{translate('Close')}
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|||||||
@@ -37,7 +37,10 @@ class SearchFooter extends Component {
|
|||||||
searchingReleases: false,
|
searchingReleases: false,
|
||||||
searchQuery: defaultSearchQuery || '',
|
searchQuery: defaultSearchQuery || '',
|
||||||
searchIndexerIds: defaultIndexerIds,
|
searchIndexerIds: defaultIndexerIds,
|
||||||
searchCategories: defaultCategories
|
searchCategories: defaultCategories,
|
||||||
|
searchLimit: 100,
|
||||||
|
searchOffset: 0,
|
||||||
|
newSearch: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,11 +118,28 @@ class SearchFooter extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onSearchPress = () => {
|
onSearchPress = () => {
|
||||||
this.props.onSearchPress(this.state.searchQuery, this.state.searchIndexerIds, this.state.searchCategories, this.state.searchType);
|
|
||||||
|
const {
|
||||||
|
searchLimit,
|
||||||
|
searchOffset,
|
||||||
|
searchQuery,
|
||||||
|
searchIndexerIds,
|
||||||
|
searchCategories,
|
||||||
|
searchType
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
this.props.onSearchPress(searchQuery, searchIndexerIds, searchCategories, searchType, searchLimit, searchOffset);
|
||||||
|
|
||||||
|
this.setState({ searchOffset: searchOffset + 100, newSearch: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
onSearchInputChange = ({ value }) => {
|
onSearchInputChange = ({ value }) => {
|
||||||
this.setState({ searchQuery: value });
|
this.setState({ searchQuery: value, newSearch: true, searchOffset: 0 });
|
||||||
|
};
|
||||||
|
|
||||||
|
onInputChange = ({ name, value }) => {
|
||||||
|
this.props.onInputChange({ name, value });
|
||||||
|
this.setState({ newSearch: true, searchOffset: 0 });
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -141,6 +161,7 @@ class SearchFooter extends Component {
|
|||||||
searchQuery,
|
searchQuery,
|
||||||
searchIndexerIds,
|
searchIndexerIds,
|
||||||
searchCategories,
|
searchCategories,
|
||||||
|
newSearch,
|
||||||
isQueryParameterModalOpen,
|
isQueryParameterModalOpen,
|
||||||
queryModalOptions,
|
queryModalOptions,
|
||||||
searchType
|
searchType
|
||||||
@@ -206,7 +227,7 @@ class SearchFooter extends Component {
|
|||||||
name='searchIndexerIds'
|
name='searchIndexerIds'
|
||||||
value={searchIndexerIds}
|
value={searchIndexerIds}
|
||||||
isDisabled={isFetching}
|
isDisabled={isFetching}
|
||||||
onChange={onInputChange}
|
onChange={this.onInputChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -220,7 +241,7 @@ class SearchFooter extends Component {
|
|||||||
name='searchCategories'
|
name='searchCategories'
|
||||||
value={searchCategories}
|
value={searchCategories}
|
||||||
isDisabled={isFetching}
|
isDisabled={isFetching}
|
||||||
onChange={onInputChange}
|
onChange={this.onInputChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -243,7 +264,7 @@ class SearchFooter extends Component {
|
|||||||
isDisabled={isFetching || !hasIndexers || selectedCount === 0}
|
isDisabled={isFetching || !hasIndexers || selectedCount === 0}
|
||||||
onPress={onBulkGrabPress}
|
onPress={onBulkGrabPress}
|
||||||
>
|
>
|
||||||
{translate('Grab Releases')}
|
{translate('GrabReleases')}
|
||||||
</SpinnerButton>
|
</SpinnerButton>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +274,7 @@ class SearchFooter extends Component {
|
|||||||
isDisabled={isFetching || !hasIndexers}
|
isDisabled={isFetching || !hasIndexers}
|
||||||
onPress={this.onSearchPress}
|
onPress={this.onSearchPress}
|
||||||
>
|
>
|
||||||
{translate('Search')}
|
{newSearch ? translate('Search') : translate('More')}
|
||||||
</SpinnerButton>
|
</SpinnerButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -196,8 +196,8 @@ class SearchIndex extends Component {
|
|||||||
this.setState({ jumpToCharacter });
|
this.setState({ jumpToCharacter });
|
||||||
};
|
};
|
||||||
|
|
||||||
onSearchPress = (query, indexerIds, categories, type) => {
|
onSearchPress = (query, indexerIds, categories, type, limit, offset) => {
|
||||||
this.props.onSearchPress({ query, indexerIds, categories, type });
|
this.props.onSearchPress({ query, indexerIds, categories, type, limit, offset });
|
||||||
};
|
};
|
||||||
|
|
||||||
onBulkGrabPress = () => {
|
onBulkGrabPress = () => {
|
||||||
|
|||||||
@@ -5,13 +5,14 @@ import createAllIndexersSelector from './createAllIndexersSelector';
|
|||||||
function createProfileInUseSelector(profileProp) {
|
function createProfileInUseSelector(profileProp) {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state, { id }) => id,
|
(state, { id }) => id,
|
||||||
|
(state) => state.settings.appProfiles.items,
|
||||||
createAllIndexersSelector(),
|
createAllIndexersSelector(),
|
||||||
(id, indexers) => {
|
(id, profiles, indexers) => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_.some(indexers, { [profileProp]: id })) {
|
if (_.some(indexers, { [profileProp]: id }) || profiles.length <= 1) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+41
-41
@@ -25,17 +25,17 @@
|
|||||||
"not chrome < 60"
|
"not chrome < 60"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "5.15.3",
|
"@fortawesome/fontawesome-free": "6.1.1",
|
||||||
"@fortawesome/fontawesome-svg-core": "1.2.35",
|
"@fortawesome/fontawesome-svg-core": "6.1.1",
|
||||||
"@fortawesome/free-regular-svg-icons": "5.15.3",
|
"@fortawesome/free-regular-svg-icons": "6.1.1",
|
||||||
"@fortawesome/free-solid-svg-icons": "5.15.3",
|
"@fortawesome/free-solid-svg-icons": "6.1.1",
|
||||||
"@fortawesome/react-fontawesome": "0.1.14",
|
"@fortawesome/react-fontawesome": "0.1.18",
|
||||||
"@microsoft/signalr": "6.0.0",
|
"@microsoft/signalr": "6.0.3",
|
||||||
"@sentry/browser": "6.15.0",
|
"@sentry/browser": "6.19.2",
|
||||||
"@sentry/integrations": "6.15.0",
|
"@sentry/integrations": "6.19.2",
|
||||||
"chart.js": "3.2.0",
|
"chart.js": "3.7.1",
|
||||||
"classnames": "2.3.1",
|
"classnames": "2.3.1",
|
||||||
"clipboard": "2.0.8",
|
"clipboard": "2.0.10",
|
||||||
"connected-react-router": "6.9.1",
|
"connected-react-router": "6.9.1",
|
||||||
"element-class": "0.2.2",
|
"element-class": "0.2.2",
|
||||||
"filesize": "6.3.0",
|
"filesize": "6.3.0",
|
||||||
@@ -48,13 +48,13 @@
|
|||||||
"moment": "2.29.1",
|
"moment": "2.29.1",
|
||||||
"mousetrap": "1.6.5",
|
"mousetrap": "1.6.5",
|
||||||
"normalize.css": "8.0.1",
|
"normalize.css": "8.0.1",
|
||||||
"prop-types": "15.7.2",
|
"prop-types": "15.8.1",
|
||||||
"qs": "6.10.1",
|
"qs": "6.10.3",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-addons-shallow-compare": "15.6.3",
|
"react-addons-shallow-compare": "15.6.3",
|
||||||
"react-async-script": "1.2.0",
|
"react-async-script": "1.2.0",
|
||||||
"react-autosuggest": "10.1.0",
|
"react-autosuggest": "10.1.0",
|
||||||
"react-custom-scrollbars": "4.2.1",
|
"react-custom-scrollbars-2": "4.4.0",
|
||||||
"react-dnd": "14.0.4",
|
"react-dnd": "14.0.4",
|
||||||
"react-dnd-html5-backend": "14.0.2",
|
"react-dnd-html5-backend": "14.0.2",
|
||||||
"react-dnd-multi-backend": "6.0.2",
|
"react-dnd-multi-backend": "6.0.2",
|
||||||
@@ -78,41 +78,41 @@
|
|||||||
"reselect": "4.0.0"
|
"reselect": "4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.16.0",
|
"@babel/core": "7.17.8",
|
||||||
"@babel/eslint-parser": "7.16.3",
|
"@babel/eslint-parser": "7.17.0",
|
||||||
"@babel/plugin-proposal-class-properties": "7.16.0",
|
"@babel/plugin-proposal-class-properties": "7.16.7",
|
||||||
"@babel/plugin-proposal-decorators": "7.16.4",
|
"@babel/plugin-proposal-decorators": "7.17.8",
|
||||||
"@babel/plugin-proposal-export-default-from": "7.16.0",
|
"@babel/plugin-proposal-export-default-from": "7.16.7",
|
||||||
"@babel/plugin-proposal-export-namespace-from": "7.16.0",
|
"@babel/plugin-proposal-export-namespace-from": "7.16.7",
|
||||||
"@babel/plugin-proposal-function-sent": "7.16.0",
|
"@babel/plugin-proposal-function-sent": "7.16.7",
|
||||||
"@babel/plugin-proposal-nullish-coalescing-operator": "7.16.0",
|
"@babel/plugin-proposal-nullish-coalescing-operator": "7.16.7",
|
||||||
"@babel/plugin-proposal-numeric-separator": "7.16.0",
|
"@babel/plugin-proposal-numeric-separator": "7.16.7",
|
||||||
"@babel/plugin-proposal-optional-chaining": "7.16.0",
|
"@babel/plugin-proposal-optional-chaining": "7.16.7",
|
||||||
"@babel/plugin-proposal-throw-expressions": "7.16.0",
|
"@babel/plugin-proposal-throw-expressions": "7.16.7",
|
||||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||||
"@babel/preset-env": "7.16.4",
|
"@babel/preset-env": "7.16.11",
|
||||||
"@babel/preset-react": "7.16.0",
|
"@babel/preset-react": "7.16.7",
|
||||||
"autoprefixer": "10.2.5",
|
"autoprefixer": "10.4.4",
|
||||||
"babel-loader": "8.2.3",
|
"babel-loader": "8.2.4",
|
||||||
"babel-plugin-inline-classnames": "2.0.1",
|
"babel-plugin-inline-classnames": "2.0.1",
|
||||||
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
|
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
|
||||||
"core-js": "3.11.0",
|
"core-js": "3.21.1",
|
||||||
"css-loader": "6.5.1",
|
"css-loader": "6.7.1",
|
||||||
"eslint": "8.3.0",
|
"eslint": "8.11.0",
|
||||||
"eslint-plugin-filenames": "1.3.2",
|
"eslint-plugin-filenames": "1.3.2",
|
||||||
"eslint-plugin-import": "2.25.3",
|
"eslint-plugin-import": "2.25.4",
|
||||||
"eslint-plugin-react": "7.27.1",
|
"eslint-plugin-react": "7.29.4",
|
||||||
"eslint-plugin-simple-import-sort": "7.0.0",
|
"eslint-plugin-simple-import-sort": "7.0.0",
|
||||||
"esprint": "3.1.0",
|
"esprint": "3.3.0",
|
||||||
"file-loader": "6.2.0",
|
"file-loader": "6.2.0",
|
||||||
"filemanager-webpack-plugin": "6.1.7",
|
"filemanager-webpack-plugin": "6.1.7",
|
||||||
"html-webpack-plugin": "5.5.0",
|
"html-webpack-plugin": "5.5.0",
|
||||||
"loader-utils": "^3.0.0",
|
"loader-utils": "^3.0.0",
|
||||||
"mini-css-extract-plugin": "2.4.5",
|
"mini-css-extract-plugin": "2.6.0",
|
||||||
"postcss": "8.3.11",
|
"postcss": "8.4.12",
|
||||||
"postcss-color-function": "4.1.0",
|
"postcss-color-function": "4.1.0",
|
||||||
"postcss-loader": "6.2.0",
|
"postcss-loader": "6.2.1",
|
||||||
"postcss-mixins": "8.1.0",
|
"postcss-mixins": "9.0.2",
|
||||||
"postcss-nested": "5.0.6",
|
"postcss-nested": "5.0.6",
|
||||||
"postcss-simple-vars": "6.0.3",
|
"postcss-simple-vars": "6.0.3",
|
||||||
"postcss-url": "10.1.3",
|
"postcss-url": "10.1.3",
|
||||||
@@ -121,11 +121,11 @@
|
|||||||
"run-sequence": "2.2.1",
|
"run-sequence": "2.2.1",
|
||||||
"streamqueue": "1.1.2",
|
"streamqueue": "1.1.2",
|
||||||
"style-loader": "3.3.1",
|
"style-loader": "3.3.1",
|
||||||
"stylelint": "14.1.0",
|
"stylelint": "14.6.0",
|
||||||
"stylelint-order": "5.0.0",
|
"stylelint-order": "5.0.0",
|
||||||
"url-loader": "4.1.1",
|
"url-loader": "4.1.1",
|
||||||
"webpack": "5.64.2",
|
"webpack": "5.70.0",
|
||||||
"webpack-cli": "4.9.1",
|
"webpack-cli": "4.9.2",
|
||||||
"webpack-livereload-plugin": "3.0.2"
|
"webpack-livereload-plugin": "3.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,10 +94,10 @@
|
|||||||
|
|
||||||
<!-- Standard testing packages -->
|
<!-- Standard testing packages -->
|
||||||
<ItemGroup Condition="'$(TestProject)'=='true'">
|
<ItemGroup Condition="'$(TestProject)'=='true'">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.1" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||||
<PackageReference Include="NunitXml.TestLogger" Version="3.0.97" />
|
<PackageReference Include="NunitXml.TestLogger" Version="3.0.117" />
|
||||||
<PackageReference Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" />
|
<PackageReference Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ namespace NzbDrone.Automation.Test
|
|||||||
public abstract class AutomationTest
|
public abstract class AutomationTest
|
||||||
{
|
{
|
||||||
private NzbDroneRunner _runner;
|
private NzbDroneRunner _runner;
|
||||||
protected RemoteWebDriver driver;
|
protected WebDriver driver;
|
||||||
|
|
||||||
public AutomationTest()
|
public AutomationTest()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using OpenQA.Selenium;
|
using OpenQA.Selenium;
|
||||||
using OpenQA.Selenium.Remote;
|
|
||||||
using OpenQA.Selenium.Support.UI;
|
using OpenQA.Selenium.Support.UI;
|
||||||
|
|
||||||
namespace NzbDrone.Automation.Test.PageModel
|
namespace NzbDrone.Automation.Test.PageModel
|
||||||
{
|
{
|
||||||
public class PageBase
|
public class PageBase
|
||||||
{
|
{
|
||||||
private readonly RemoteWebDriver _driver;
|
private readonly WebDriver _driver;
|
||||||
|
|
||||||
public PageBase(RemoteWebDriver driver)
|
public PageBase(WebDriver driver)
|
||||||
{
|
{
|
||||||
_driver = driver;
|
_driver = driver;
|
||||||
driver.Manage().Window.Maximize();
|
driver.Manage().Window.Maximize();
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Selenium.Support" Version="3.141.0" />
|
<PackageReference Include="Selenium.Support" Version="4.1.0" />
|
||||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="90.0.4430.2400" />
|
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="99.0.4844.5100" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\NzbDrone.Test.Common\Prowlarr.Test.Common.csproj" />
|
<ProjectReference Include="..\NzbDrone.Test.Common\Prowlarr.Test.Common.csproj" />
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
|||||||
[TestCase(@"https://hd-space.org/index.php?page=login: uid=mySecret&pwd=mySecret")]
|
[TestCase(@"https://hd-space.org/index.php?page=login: uid=mySecret&pwd=mySecret")]
|
||||||
[TestCase(@"https://beyond-hd.me/api/torrents/2b51db35e1912ffc138825a12b9933d2")]
|
[TestCase(@"https://beyond-hd.me/api/torrents/2b51db35e1912ffc138825a12b9933d2")]
|
||||||
[TestCase(@"Req: [POST] https://www3.yggtorrent.nz/user/login: id=mySecret&pass=mySecret&ci_csrf_token=2b51db35e1912ffc138825a12b9933d2")]
|
[TestCase(@"Req: [POST] https://www3.yggtorrent.nz/user/login: id=mySecret&pass=mySecret&ci_csrf_token=2b51db35e1912ffc138825a12b9933d2")]
|
||||||
|
[TestCase(@"https://torrentseeds.org/api/torrents/filter?api_token=2b51db35e1912ffc138825a12b9933d2&name=&sortField=created_at&sortDirection=desc&perPage=100&page=1")]
|
||||||
|
|
||||||
//Indexer Responses
|
//Indexer Responses
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
|
||||||
@@ -24,7 +24,8 @@ namespace NzbDrone.Common.Disk
|
|||||||
"/boot",
|
"/boot",
|
||||||
"/lib",
|
"/lib",
|
||||||
"/sbin",
|
"/sbin",
|
||||||
"/proc"
|
"/proc",
|
||||||
|
"/usr/bin"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ namespace NzbDrone.Common.Http
|
|||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
request.Url += new HttpUri(response.Headers.GetSingleValue("Location"));
|
request.Url = new HttpUri(response.RedirectUrl);
|
||||||
autoRedirectChain.Add(request.Url.ToString());
|
autoRedirectChain.Add(request.Url.ToString());
|
||||||
|
|
||||||
_logger.Trace("Redirected to {0}", request.Url);
|
_logger.Trace("Redirected to {0}", request.Url);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace NzbDrone.Common.Http
|
|||||||
{
|
{
|
||||||
public class HttpResponse
|
public class HttpResponse
|
||||||
{
|
{
|
||||||
private static readonly Regex RegexSetCookie = new Regex("^(.*?)=(.*?)(?:;|$)", RegexOptions.Compiled);
|
private static readonly Regex RegexRefresh = new Regex("^(.*?url)=(.*?)(?:;|$)", RegexOptions.Compiled);
|
||||||
|
|
||||||
public HttpResponse(HttpRequest request, HttpHeader headers, CookieCollection cookies, byte[] binaryData, long elapsedTime = 0, HttpStatusCode statusCode = HttpStatusCode.OK)
|
public HttpResponse(HttpRequest request, HttpHeader headers, CookieCollection cookies, byte[] binaryData, long elapsedTime = 0, HttpStatusCode statusCode = HttpStatusCode.OK)
|
||||||
{
|
{
|
||||||
@@ -67,7 +67,8 @@ namespace NzbDrone.Common.Http
|
|||||||
StatusCode == HttpStatusCode.MovedPermanently ||
|
StatusCode == HttpStatusCode.MovedPermanently ||
|
||||||
StatusCode == HttpStatusCode.RedirectMethod ||
|
StatusCode == HttpStatusCode.RedirectMethod ||
|
||||||
StatusCode == HttpStatusCode.TemporaryRedirect ||
|
StatusCode == HttpStatusCode.TemporaryRedirect ||
|
||||||
StatusCode == HttpStatusCode.Found;
|
StatusCode == HttpStatusCode.Found ||
|
||||||
|
Headers.ContainsKey("Refresh");
|
||||||
|
|
||||||
public string RedirectUrl
|
public string RedirectUrl
|
||||||
{
|
{
|
||||||
@@ -76,6 +77,20 @@ namespace NzbDrone.Common.Http
|
|||||||
var newUrl = Headers["Location"];
|
var newUrl = Headers["Location"];
|
||||||
if (newUrl == null)
|
if (newUrl == null)
|
||||||
{
|
{
|
||||||
|
newUrl = Headers["Refresh"];
|
||||||
|
|
||||||
|
if (newUrl == null)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var match = RegexRefresh.Match(newUrl);
|
||||||
|
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
return (Request.Url += new HttpUri(match.Groups[2].Value)).FullUri;
|
||||||
|
}
|
||||||
|
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace NzbDrone.Common.Instrumentation
|
|||||||
private static readonly Regex[] CleansingRules = new[]
|
private static readonly Regex[] CleansingRules = new[]
|
||||||
{
|
{
|
||||||
// Url
|
// Url
|
||||||
new Regex(@"(?<=[?&: ;])(apikey|(?:access[-_]?)?token|pass(?:key|wd)?|auth|authkey|user|u?id|api|[a-z_]*apikey|account|pwd)=(?<secret>[^&=]+?)(?= |&|$|<)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
new Regex(@"(?<=[?&: ;])(apikey|(?:(?:access|api)[-_]?)?token|pass(?:key|wd)?|auth|authkey|user|u?id|api|[a-z_]*apikey|account|pwd)=(?<secret>[^&=]+?)(?= |&|$|<)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||||
new Regex(@"(?<=[?& ;])[^=]*?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
new Regex(@"(?<=[?& ;])[^=]*?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||||
new Regex(@"rss\.torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
new Regex(@"rss\.torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||||
new Regex(@"rss\.torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
new Regex(@"rss\.torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||||
|
|||||||
@@ -4,14 +4,14 @@
|
|||||||
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
|
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DryIoc.dll" Version="4.8.1" />
|
<PackageReference Include="DryIoc.dll" Version="4.8.8" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||||
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.2" />
|
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.4" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="NLog" Version="4.7.9" />
|
<PackageReference Include="NLog" Version="4.7.14" />
|
||||||
<PackageReference Include="Sentry" Version="3.8.3" />
|
<PackageReference Include="Sentry" Version="3.15.0" />
|
||||||
<PackageReference Include="SharpZipLib" Version="1.3.1" />
|
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
||||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
|
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
|
||||||
|
|||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
using FizzWare.NBuilder;
|
||||||
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Applications;
|
||||||
|
using NzbDrone.Core.Housekeeping.Housekeepers;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CleanupOrphanedApplicationFixture : DbTest<CleanupOrphanedApplicationStatus, ApplicationStatus>
|
||||||
|
{
|
||||||
|
private ApplicationDefinition _application;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_application = Builder<ApplicationDefinition>.CreateNew()
|
||||||
|
.BuildNew();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenApplication()
|
||||||
|
{
|
||||||
|
Db.Insert(_application);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_delete_orphaned_applicationstatus()
|
||||||
|
{
|
||||||
|
var status = Builder<ApplicationStatus>.CreateNew()
|
||||||
|
.With(h => h.ProviderId = _application.Id)
|
||||||
|
.BuildNew();
|
||||||
|
Db.Insert(status);
|
||||||
|
|
||||||
|
Subject.Clean();
|
||||||
|
AllStoredModels.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_delete_unorphaned_applicationstatus()
|
||||||
|
{
|
||||||
|
GivenApplication();
|
||||||
|
|
||||||
|
var status = Builder<ApplicationStatus>.CreateNew()
|
||||||
|
.With(h => h.ProviderId = _application.Id)
|
||||||
|
.BuildNew();
|
||||||
|
Db.Insert(status);
|
||||||
|
|
||||||
|
Subject.Clean();
|
||||||
|
AllStoredModels.Should().HaveCount(1);
|
||||||
|
AllStoredModels.Should().Contain(h => h.ProviderId == _application.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
using FizzWare.NBuilder;
|
||||||
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.Download.Clients.Flood;
|
||||||
|
using NzbDrone.Core.Housekeeping.Housekeepers;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CleanupOrphanedDownloadClientStatusFixture : DbTest<CleanupOrphanedDownloadClientStatus, DownloadClientStatus>
|
||||||
|
{
|
||||||
|
private DownloadClientDefinition _downloadClient;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_downloadClient = Builder<DownloadClientDefinition>.CreateNew()
|
||||||
|
.With(c => c.Settings = new FloodSettings())
|
||||||
|
.BuildNew();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenClient()
|
||||||
|
{
|
||||||
|
Db.Insert(_downloadClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_delete_orphaned_downloadclientstatus()
|
||||||
|
{
|
||||||
|
var status = Builder<DownloadClientStatus>.CreateNew()
|
||||||
|
.With(h => h.ProviderId = _downloadClient.Id)
|
||||||
|
.BuildNew();
|
||||||
|
Db.Insert(status);
|
||||||
|
|
||||||
|
Subject.Clean();
|
||||||
|
AllStoredModels.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_delete_unorphaned_downloadclientstatus()
|
||||||
|
{
|
||||||
|
GivenClient();
|
||||||
|
|
||||||
|
var status = Builder<DownloadClientStatus>.CreateNew()
|
||||||
|
.With(h => h.ProviderId = _downloadClient.Id)
|
||||||
|
.BuildNew();
|
||||||
|
Db.Insert(status);
|
||||||
|
|
||||||
|
Subject.Clean();
|
||||||
|
AllStoredModels.Should().HaveCount(1);
|
||||||
|
AllStoredModels.Should().Contain(h => h.ProviderId == _downloadClient.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+109
@@ -0,0 +1,109 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using FizzWare.NBuilder;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Applications;
|
||||||
|
using NzbDrone.Core.Housekeeping.Housekeepers;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Core.ThingiProvider.Status;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class FixFutureApplicationStatusTimesFixture : CoreTest<FixFutureApplicationStatusTimes>
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void should_set_disabled_till_when_its_too_far_in_the_future()
|
||||||
|
{
|
||||||
|
var disabledTillTime = EscalationBackOff.Periods[1];
|
||||||
|
var applicationStatuses = Builder<ApplicationStatus>.CreateListOfSize(5)
|
||||||
|
.All()
|
||||||
|
.With(t => t.DisabledTill = DateTime.UtcNow.AddDays(5))
|
||||||
|
.With(t => t.InitialFailure = DateTime.UtcNow.AddDays(-5))
|
||||||
|
.With(t => t.MostRecentFailure = DateTime.UtcNow.AddDays(-5))
|
||||||
|
.With(t => t.EscalationLevel = 1)
|
||||||
|
.BuildListOfNew();
|
||||||
|
|
||||||
|
Mocker.GetMock<IApplicationStatusRepository>()
|
||||||
|
.Setup(s => s.All())
|
||||||
|
.Returns(applicationStatuses);
|
||||||
|
|
||||||
|
Subject.Clean();
|
||||||
|
|
||||||
|
Mocker.GetMock<IApplicationStatusRepository>()
|
||||||
|
.Verify(v => v.UpdateMany(
|
||||||
|
It.Is<List<ApplicationStatus>>(i => i.All(
|
||||||
|
s => s.DisabledTill.Value <= DateTime.UtcNow.AddMinutes(disabledTillTime)))));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_set_initial_failure_when_its_in_the_future()
|
||||||
|
{
|
||||||
|
var applicationStatuses = Builder<ApplicationStatus>.CreateListOfSize(5)
|
||||||
|
.All()
|
||||||
|
.With(t => t.DisabledTill = DateTime.UtcNow.AddDays(-5))
|
||||||
|
.With(t => t.InitialFailure = DateTime.UtcNow.AddDays(5))
|
||||||
|
.With(t => t.MostRecentFailure = DateTime.UtcNow.AddDays(-5))
|
||||||
|
.With(t => t.EscalationLevel = 1)
|
||||||
|
.BuildListOfNew();
|
||||||
|
|
||||||
|
Mocker.GetMock<IApplicationStatusRepository>()
|
||||||
|
.Setup(s => s.All())
|
||||||
|
.Returns(applicationStatuses);
|
||||||
|
|
||||||
|
Subject.Clean();
|
||||||
|
|
||||||
|
Mocker.GetMock<IApplicationStatusRepository>()
|
||||||
|
.Verify(v => v.UpdateMany(
|
||||||
|
It.Is<List<ApplicationStatus>>(i => i.All(
|
||||||
|
s => s.InitialFailure.Value <= DateTime.UtcNow))));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_set_most_recent_failure_when_its_in_the_future()
|
||||||
|
{
|
||||||
|
var applicationStatuses = Builder<ApplicationStatus>.CreateListOfSize(5)
|
||||||
|
.All()
|
||||||
|
.With(t => t.DisabledTill = DateTime.UtcNow.AddDays(-5))
|
||||||
|
.With(t => t.InitialFailure = DateTime.UtcNow.AddDays(-5))
|
||||||
|
.With(t => t.MostRecentFailure = DateTime.UtcNow.AddDays(5))
|
||||||
|
.With(t => t.EscalationLevel = 1)
|
||||||
|
.BuildListOfNew();
|
||||||
|
|
||||||
|
Mocker.GetMock<IApplicationStatusRepository>()
|
||||||
|
.Setup(s => s.All())
|
||||||
|
.Returns(applicationStatuses);
|
||||||
|
|
||||||
|
Subject.Clean();
|
||||||
|
|
||||||
|
Mocker.GetMock<IApplicationStatusRepository>()
|
||||||
|
.Verify(v => v.UpdateMany(
|
||||||
|
It.Is<List<ApplicationStatus>>(i => i.All(
|
||||||
|
s => s.MostRecentFailure.Value <= DateTime.UtcNow))));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_change_statuses_when_times_are_in_the_past()
|
||||||
|
{
|
||||||
|
var indexerStatuses = Builder<ApplicationStatus>.CreateListOfSize(5)
|
||||||
|
.All()
|
||||||
|
.With(t => t.DisabledTill = DateTime.UtcNow.AddDays(-5))
|
||||||
|
.With(t => t.InitialFailure = DateTime.UtcNow.AddDays(-5))
|
||||||
|
.With(t => t.MostRecentFailure = DateTime.UtcNow.AddDays(-5))
|
||||||
|
.With(t => t.EscalationLevel = 0)
|
||||||
|
.BuildListOfNew();
|
||||||
|
|
||||||
|
Mocker.GetMock<IApplicationStatusRepository>()
|
||||||
|
.Setup(s => s.All())
|
||||||
|
.Returns(indexerStatuses);
|
||||||
|
|
||||||
|
Subject.Clean();
|
||||||
|
|
||||||
|
Mocker.GetMock<IApplicationStatusRepository>()
|
||||||
|
.Verify(v => v.UpdateMany(
|
||||||
|
It.Is<List<ApplicationStatus>>(i => i.Count == 0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+109
@@ -0,0 +1,109 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using FizzWare.NBuilder;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.Housekeeping.Housekeepers;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Core.ThingiProvider.Status;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class FixFutureDownloadClientStatusTimesFixture : CoreTest<FixFutureDownloadClientStatusTimes>
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void should_set_disabled_till_when_its_too_far_in_the_future()
|
||||||
|
{
|
||||||
|
var disabledTillTime = EscalationBackOff.Periods[1];
|
||||||
|
var clientStatuses = Builder<DownloadClientStatus>.CreateListOfSize(5)
|
||||||
|
.All()
|
||||||
|
.With(t => t.DisabledTill = DateTime.UtcNow.AddDays(5))
|
||||||
|
.With(t => t.InitialFailure = DateTime.UtcNow.AddDays(-5))
|
||||||
|
.With(t => t.MostRecentFailure = DateTime.UtcNow.AddDays(-5))
|
||||||
|
.With(t => t.EscalationLevel = 1)
|
||||||
|
.BuildListOfNew();
|
||||||
|
|
||||||
|
Mocker.GetMock<IDownloadClientStatusRepository>()
|
||||||
|
.Setup(s => s.All())
|
||||||
|
.Returns(clientStatuses);
|
||||||
|
|
||||||
|
Subject.Clean();
|
||||||
|
|
||||||
|
Mocker.GetMock<IDownloadClientStatusRepository>()
|
||||||
|
.Verify(v => v.UpdateMany(
|
||||||
|
It.Is<List<DownloadClientStatus>>(i => i.All(
|
||||||
|
s => s.DisabledTill.Value <= DateTime.UtcNow.AddMinutes(disabledTillTime)))));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_set_initial_failure_when_its_in_the_future()
|
||||||
|
{
|
||||||
|
var clientStatuses = Builder<DownloadClientStatus>.CreateListOfSize(5)
|
||||||
|
.All()
|
||||||
|
.With(t => t.DisabledTill = DateTime.UtcNow.AddDays(-5))
|
||||||
|
.With(t => t.InitialFailure = DateTime.UtcNow.AddDays(5))
|
||||||
|
.With(t => t.MostRecentFailure = DateTime.UtcNow.AddDays(-5))
|
||||||
|
.With(t => t.EscalationLevel = 1)
|
||||||
|
.BuildListOfNew();
|
||||||
|
|
||||||
|
Mocker.GetMock<IDownloadClientStatusRepository>()
|
||||||
|
.Setup(s => s.All())
|
||||||
|
.Returns(clientStatuses);
|
||||||
|
|
||||||
|
Subject.Clean();
|
||||||
|
|
||||||
|
Mocker.GetMock<IDownloadClientStatusRepository>()
|
||||||
|
.Verify(v => v.UpdateMany(
|
||||||
|
It.Is<List<DownloadClientStatus>>(i => i.All(
|
||||||
|
s => s.InitialFailure.Value <= DateTime.UtcNow))));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_set_most_recent_failure_when_its_in_the_future()
|
||||||
|
{
|
||||||
|
var clientStatuses = Builder<DownloadClientStatus>.CreateListOfSize(5)
|
||||||
|
.All()
|
||||||
|
.With(t => t.DisabledTill = DateTime.UtcNow.AddDays(-5))
|
||||||
|
.With(t => t.InitialFailure = DateTime.UtcNow.AddDays(-5))
|
||||||
|
.With(t => t.MostRecentFailure = DateTime.UtcNow.AddDays(5))
|
||||||
|
.With(t => t.EscalationLevel = 1)
|
||||||
|
.BuildListOfNew();
|
||||||
|
|
||||||
|
Mocker.GetMock<IDownloadClientStatusRepository>()
|
||||||
|
.Setup(s => s.All())
|
||||||
|
.Returns(clientStatuses);
|
||||||
|
|
||||||
|
Subject.Clean();
|
||||||
|
|
||||||
|
Mocker.GetMock<IDownloadClientStatusRepository>()
|
||||||
|
.Verify(v => v.UpdateMany(
|
||||||
|
It.Is<List<DownloadClientStatus>>(i => i.All(
|
||||||
|
s => s.MostRecentFailure.Value <= DateTime.UtcNow))));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_change_statuses_when_times_are_in_the_past()
|
||||||
|
{
|
||||||
|
var clientStatuses = Builder<DownloadClientStatus>.CreateListOfSize(5)
|
||||||
|
.All()
|
||||||
|
.With(t => t.DisabledTill = DateTime.UtcNow.AddDays(-5))
|
||||||
|
.With(t => t.InitialFailure = DateTime.UtcNow.AddDays(-5))
|
||||||
|
.With(t => t.MostRecentFailure = DateTime.UtcNow.AddDays(-5))
|
||||||
|
.With(t => t.EscalationLevel = 0)
|
||||||
|
.BuildListOfNew();
|
||||||
|
|
||||||
|
Mocker.GetMock<IDownloadClientStatusRepository>()
|
||||||
|
.Setup(s => s.All())
|
||||||
|
.Returns(clientStatuses);
|
||||||
|
|
||||||
|
Subject.Clean();
|
||||||
|
|
||||||
|
Mocker.GetMock<IDownloadClientStatusRepository>()
|
||||||
|
.Verify(v => v.UpdateMany(
|
||||||
|
It.Is<List<DownloadClientStatus>>(i => i.Count == 0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Dapper" Version="2.0.90" />
|
<PackageReference Include="Dapper" Version="2.0.123" />
|
||||||
<PackageReference Include="NBuilder" Version="6.1.0" />
|
<PackageReference Include="NBuilder" Version="6.1.0" />
|
||||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||||
<PackageReference Include="YamlDotNet" Version="11.2.1" />
|
<PackageReference Include="YamlDotNet" Version="11.2.1" />
|
||||||
|
|||||||
@@ -116,6 +116,12 @@ namespace NzbDrone.Core.Applications.Lidarr
|
|||||||
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Lidarr cannot connect to Prowlarr");
|
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Lidarr cannot connect to Prowlarr");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ex.Response.StatusCode == HttpStatusCode.SeeOther)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Lidarr returned redirect and is invalid");
|
||||||
|
return new ValidationFailure("BaseUrl", "Lidarr url is invalid, Prowlarr cannot connect to Lidarr - are you missing a url base?");
|
||||||
|
}
|
||||||
|
|
||||||
_logger.Error(ex, "Unable to send test message");
|
_logger.Error(ex, "Unable to send test message");
|
||||||
return new ValidationFailure("BaseUrl", "Unable to complete application test");
|
return new ValidationFailure("BaseUrl", "Unable to complete application test");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,6 +116,12 @@ namespace NzbDrone.Core.Applications.Radarr
|
|||||||
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Radarr cannot connect to Prowlarr");
|
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Radarr cannot connect to Prowlarr");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ex.Response.StatusCode == HttpStatusCode.SeeOther)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Radarr returned redirect and is invalid");
|
||||||
|
return new ValidationFailure("BaseUrl", "Radarr url is invalid, Prowlarr cannot connect to Radarr - are you missing a url base?");
|
||||||
|
}
|
||||||
|
|
||||||
_logger.Error(ex, "Unable to send test message");
|
_logger.Error(ex, "Unable to send test message");
|
||||||
return new ValidationFailure("BaseUrl", "Unable to complete application test");
|
return new ValidationFailure("BaseUrl", "Unable to complete application test");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,6 +116,12 @@ namespace NzbDrone.Core.Applications.Readarr
|
|||||||
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Readarr cannot connect to Prowlarr");
|
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Readarr cannot connect to Prowlarr");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ex.Response.StatusCode == HttpStatusCode.SeeOther)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Readarr returned redirect and is invalid");
|
||||||
|
return new ValidationFailure("BaseUrl", "Readarr url is invalid, Prowlarr cannot connect to Readarr - are you missing a url base?");
|
||||||
|
}
|
||||||
|
|
||||||
_logger.Error(ex, "Unable to send test message");
|
_logger.Error(ex, "Unable to send test message");
|
||||||
return new ValidationFailure("BaseUrl", "Unable to complete application test");
|
return new ValidationFailure("BaseUrl", "Unable to complete application test");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,6 +116,18 @@ namespace NzbDrone.Core.Applications.Sonarr
|
|||||||
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Sonarr cannot connect to Prowlarr");
|
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Sonarr cannot connect to Prowlarr");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ex.Response.StatusCode == HttpStatusCode.SeeOther)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Sonarr returned redirect and is invalid");
|
||||||
|
return new ValidationFailure("BaseUrl", "Sonarr url is invalid, Prowlarr cannot connect to Sonarr - are you missing a url base?");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ex.Response.StatusCode == HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Sonarr not found");
|
||||||
|
return new ValidationFailure("BaseUrl", "Sonarr url is invalid, Prowlarr cannot connect to Sonarr. Is Sonarr running and accessible? Sonarr v2 is not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
_logger.Error(ex, "Unable to send test message");
|
_logger.Error(ex, "Unable to send test message");
|
||||||
return new ValidationFailure("BaseUrl", "Unable to complete application test");
|
return new ValidationFailure("BaseUrl", "Unable to complete application test");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,179 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Cache;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.Indexers;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Applications.Whisparr
|
||||||
|
{
|
||||||
|
public class Whisparr : ApplicationBase<WhisparrSettings>
|
||||||
|
{
|
||||||
|
public override string Name => "Whisparr";
|
||||||
|
|
||||||
|
private readonly IWhisparrV3Proxy _whisparrV3Proxy;
|
||||||
|
private readonly ICached<List<WhisparrIndexer>> _schemaCache;
|
||||||
|
private readonly IConfigFileProvider _configFileProvider;
|
||||||
|
|
||||||
|
public Whisparr(ICacheManager cacheManager, IWhisparrV3Proxy whisparrV3Proxy, IConfigFileProvider configFileProvider, IAppIndexerMapService appIndexerMapService, Logger logger)
|
||||||
|
: base(appIndexerMapService, logger)
|
||||||
|
{
|
||||||
|
_schemaCache = cacheManager.GetCache<List<WhisparrIndexer>>(GetType());
|
||||||
|
_whisparrV3Proxy = whisparrV3Proxy;
|
||||||
|
_configFileProvider = configFileProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ValidationResult Test()
|
||||||
|
{
|
||||||
|
var failures = new List<ValidationFailure>();
|
||||||
|
|
||||||
|
var testIndexer = new IndexerDefinition
|
||||||
|
{
|
||||||
|
Id = 0,
|
||||||
|
Name = "Test",
|
||||||
|
Protocol = DownloadProtocol.Usenet,
|
||||||
|
Capabilities = new IndexerCapabilities()
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var cat in NewznabStandardCategory.AllCats)
|
||||||
|
{
|
||||||
|
testIndexer.Capabilities.Categories.AddCategoryMapping(1, cat);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
failures.AddIfNotNull(_whisparrV3Proxy.TestConnection(BuildWhisparrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
|
||||||
|
}
|
||||||
|
catch (WebException ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Unable to send test message");
|
||||||
|
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Whisparr"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ValidationResult(failures);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override List<AppIndexerMap> GetIndexerMappings()
|
||||||
|
{
|
||||||
|
var indexers = _whisparrV3Proxy.GetIndexers(Settings)
|
||||||
|
.Where(i => i.Implementation == "Newznab" || i.Implementation == "Torznab");
|
||||||
|
|
||||||
|
var mappings = new List<AppIndexerMap>();
|
||||||
|
|
||||||
|
foreach (var indexer in indexers)
|
||||||
|
{
|
||||||
|
if ((string)indexer.Fields.FirstOrDefault(x => x.Name == "apiKey")?.Value == _configFileProvider.ApiKey)
|
||||||
|
{
|
||||||
|
var match = AppIndexerRegex.Match((string)indexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value);
|
||||||
|
|
||||||
|
if (match.Groups["indexer"].Success && int.TryParse(match.Groups["indexer"].Value, out var indexerId))
|
||||||
|
{
|
||||||
|
//Add parsed mapping if it's mapped to a Indexer in this Prowlarr instance
|
||||||
|
mappings.Add(new AppIndexerMap { RemoteIndexerId = indexer.Id, IndexerId = indexerId });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void AddIndexer(IndexerDefinition indexer)
|
||||||
|
{
|
||||||
|
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
|
||||||
|
{
|
||||||
|
var radarrIndexer = BuildWhisparrIndexer(indexer, indexer.Protocol);
|
||||||
|
|
||||||
|
var remoteIndexer = _whisparrV3Proxy.AddIndexer(radarrIndexer, Settings);
|
||||||
|
_appIndexerMapService.Insert(new AppIndexerMap { AppId = Definition.Id, IndexerId = indexer.Id, RemoteIndexerId = remoteIndexer.Id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void RemoveIndexer(int indexerId)
|
||||||
|
{
|
||||||
|
var appMappings = _appIndexerMapService.GetMappingsForApp(Definition.Id);
|
||||||
|
|
||||||
|
var indexerMapping = appMappings.FirstOrDefault(m => m.IndexerId == indexerId);
|
||||||
|
|
||||||
|
if (indexerMapping != null)
|
||||||
|
{
|
||||||
|
//Remove Indexer remotely and then remove the mapping
|
||||||
|
_whisparrV3Proxy.RemoveIndexer(indexerMapping.RemoteIndexerId, Settings);
|
||||||
|
_appIndexerMapService.Delete(indexerMapping.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdateIndexer(IndexerDefinition indexer)
|
||||||
|
{
|
||||||
|
_logger.Debug("Updating indexer {0} [{1}]", indexer.Name, indexer.Id);
|
||||||
|
|
||||||
|
var appMappings = _appIndexerMapService.GetMappingsForApp(Definition.Id);
|
||||||
|
var indexerMapping = appMappings.FirstOrDefault(m => m.IndexerId == indexer.Id);
|
||||||
|
|
||||||
|
var radarrIndexer = BuildWhisparrIndexer(indexer, indexer.Protocol, indexerMapping?.RemoteIndexerId ?? 0);
|
||||||
|
|
||||||
|
var remoteIndexer = _whisparrV3Proxy.GetIndexer(indexerMapping.RemoteIndexerId, Settings);
|
||||||
|
|
||||||
|
if (remoteIndexer != null)
|
||||||
|
{
|
||||||
|
_logger.Debug("Remote indexer found, syncing with current settings");
|
||||||
|
|
||||||
|
if (!radarrIndexer.Equals(remoteIndexer))
|
||||||
|
{
|
||||||
|
_whisparrV3Proxy.UpdateIndexer(radarrIndexer, Settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_appIndexerMapService.Delete(indexerMapping.Id);
|
||||||
|
|
||||||
|
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
|
||||||
|
{
|
||||||
|
_logger.Debug("Remote indexer not found, re-adding {0} to Whisparr", indexer.Name);
|
||||||
|
radarrIndexer.Id = 0;
|
||||||
|
var newRemoteIndexer = _whisparrV3Proxy.AddIndexer(radarrIndexer, Settings);
|
||||||
|
_appIndexerMapService.Insert(new AppIndexerMap { AppId = Definition.Id, IndexerId = indexer.Id, RemoteIndexerId = newRemoteIndexer.Id });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Debug("Remote indexer not found for {0}, skipping re-add to Radarr due to indexer capabilities", indexer.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private WhisparrIndexer BuildWhisparrIndexer(IndexerDefinition indexer, DownloadProtocol protocol, int id = 0)
|
||||||
|
{
|
||||||
|
var cacheKey = $"{Settings.BaseUrl}";
|
||||||
|
var schemas = _schemaCache.Get(cacheKey, () => _whisparrV3Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
|
||||||
|
|
||||||
|
var newznab = schemas.Where(i => i.Implementation == "Newznab").First();
|
||||||
|
var torznab = schemas.Where(i => i.Implementation == "Torznab").First();
|
||||||
|
|
||||||
|
var schema = protocol == DownloadProtocol.Usenet ? newznab : torznab;
|
||||||
|
|
||||||
|
var whisparrIndexer = new WhisparrIndexer
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Name = $"{indexer.Name} (Prowlarr)",
|
||||||
|
EnableRss = indexer.Enable && indexer.AppProfile.Value.EnableRss,
|
||||||
|
EnableAutomaticSearch = indexer.Enable && indexer.AppProfile.Value.EnableAutomaticSearch,
|
||||||
|
EnableInteractiveSearch = indexer.Enable && indexer.AppProfile.Value.EnableInteractiveSearch,
|
||||||
|
Priority = indexer.Priority,
|
||||||
|
Implementation = indexer.Protocol == DownloadProtocol.Usenet ? "Newznab" : "Torznab",
|
||||||
|
ConfigContract = schema.ConfigContract,
|
||||||
|
Fields = schema.Fields,
|
||||||
|
};
|
||||||
|
|
||||||
|
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
|
||||||
|
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
|
||||||
|
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
|
||||||
|
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
|
||||||
|
|
||||||
|
return whisparrIndexer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
namespace NzbDrone.Core.Applications.Whisparr
|
||||||
|
{
|
||||||
|
public class WhisparrField
|
||||||
|
{
|
||||||
|
public int Order { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Label { get; set; }
|
||||||
|
public string Unit { get; set; }
|
||||||
|
public string HelpText { get; set; }
|
||||||
|
public string HelpLink { get; set; }
|
||||||
|
public object Value { get; set; }
|
||||||
|
public string Type { get; set; }
|
||||||
|
public bool Advanced { get; set; }
|
||||||
|
public string Section { get; set; }
|
||||||
|
public string Hidden { get; set; }
|
||||||
|
|
||||||
|
public WhisparrField Clone()
|
||||||
|
{
|
||||||
|
return (WhisparrField)MemberwiseClone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Applications.Whisparr
|
||||||
|
{
|
||||||
|
public class WhisparrIndexer
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public bool EnableRss { get; set; }
|
||||||
|
public bool EnableAutomaticSearch { get; set; }
|
||||||
|
public bool EnableInteractiveSearch { get; set; }
|
||||||
|
public int Priority { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string ImplementationName { get; set; }
|
||||||
|
public string Implementation { get; set; }
|
||||||
|
public string ConfigContract { get; set; }
|
||||||
|
public string InfoLink { get; set; }
|
||||||
|
public HashSet<int> Tags { get; set; }
|
||||||
|
public List<WhisparrField> Fields { get; set; }
|
||||||
|
|
||||||
|
public bool Equals(WhisparrIndexer other)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(null, other))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseUrl = (string)Fields.FirstOrDefault(x => x.Name == "baseUrl").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value;
|
||||||
|
var apiPath = (string)Fields.FirstOrDefault(x => x.Name == "apiPath").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||||
|
var apiKey = (string)Fields.FirstOrDefault(x => x.Name == "apiKey").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "apiKey").Value;
|
||||||
|
var cats = JToken.DeepEquals((JArray)Fields.FirstOrDefault(x => x.Name == "categories").Value, (JArray)other.Fields.FirstOrDefault(x => x.Name == "categories").Value);
|
||||||
|
|
||||||
|
return other.EnableRss == EnableRss &&
|
||||||
|
other.EnableAutomaticSearch == EnableAutomaticSearch &&
|
||||||
|
other.EnableInteractiveSearch == EnableInteractiveSearch &&
|
||||||
|
other.Name == Name &&
|
||||||
|
other.Implementation == Implementation &&
|
||||||
|
other.Priority == Priority &&
|
||||||
|
other.Id == Id &&
|
||||||
|
apiKey && apiPath && baseUrl && cats;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using FluentValidation;
|
||||||
|
using NzbDrone.Core.Annotations;
|
||||||
|
using NzbDrone.Core.Indexers;
|
||||||
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Applications.Whisparr
|
||||||
|
{
|
||||||
|
public class WhisparrSettingsValidator : AbstractValidator<WhisparrSettings>
|
||||||
|
{
|
||||||
|
public WhisparrSettingsValidator()
|
||||||
|
{
|
||||||
|
RuleFor(c => c.BaseUrl).IsValidUrl();
|
||||||
|
RuleFor(c => c.ProwlarrUrl).IsValidUrl();
|
||||||
|
RuleFor(c => c.ApiKey).NotEmpty();
|
||||||
|
RuleFor(c => c.SyncCategories).NotEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WhisparrSettings : IApplicationSettings
|
||||||
|
{
|
||||||
|
private static readonly WhisparrSettingsValidator Validator = new WhisparrSettingsValidator();
|
||||||
|
|
||||||
|
public WhisparrSettings()
|
||||||
|
{
|
||||||
|
SyncCategories = new[] { 6000, 6010, 6020, 6030, 6040, 6045, 6050, 6070, 6080, 6090 };
|
||||||
|
}
|
||||||
|
|
||||||
|
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Whisparr sees it, including http(s)://, port, and urlbase if needed", Placeholder = "http://localhost:9696")]
|
||||||
|
public string ProwlarrUrl { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(1, Label = "Whisparr Server", HelpText = "URL used to connect to Whisparr server, including http(s)://, port, and urlbase if required", Placeholder = "http://localhost:6969")]
|
||||||
|
public string BaseUrl { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Whisparr in Settings/General")]
|
||||||
|
public string ApiKey { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")]
|
||||||
|
public IEnumerable<int> SyncCategories { get; set; }
|
||||||
|
|
||||||
|
public NzbDroneValidationResult Validate()
|
||||||
|
{
|
||||||
|
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace NzbDrone.Core.Applications.Whisparr
|
||||||
|
{
|
||||||
|
public class WhisparrStatus
|
||||||
|
{
|
||||||
|
public string Version { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Common.Serializer;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Applications.Whisparr
|
||||||
|
{
|
||||||
|
public interface IWhisparrV3Proxy
|
||||||
|
{
|
||||||
|
WhisparrIndexer AddIndexer(WhisparrIndexer indexer, WhisparrSettings settings);
|
||||||
|
List<WhisparrIndexer> GetIndexers(WhisparrSettings settings);
|
||||||
|
WhisparrIndexer GetIndexer(int indexerId, WhisparrSettings settings);
|
||||||
|
List<WhisparrIndexer> GetIndexerSchema(WhisparrSettings settings);
|
||||||
|
void RemoveIndexer(int indexerId, WhisparrSettings settings);
|
||||||
|
WhisparrIndexer UpdateIndexer(WhisparrIndexer indexer, WhisparrSettings settings);
|
||||||
|
ValidationFailure TestConnection(WhisparrIndexer indexer, WhisparrSettings settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WhisparrV3Proxy : IWhisparrV3Proxy
|
||||||
|
{
|
||||||
|
private readonly IHttpClient _httpClient;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public WhisparrV3Proxy(IHttpClient httpClient, Logger logger)
|
||||||
|
{
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WhisparrStatus GetStatus(WhisparrSettings settings)
|
||||||
|
{
|
||||||
|
var request = BuildRequest(settings, "/api/v3/system/status", HttpMethod.Get);
|
||||||
|
return Execute<WhisparrStatus>(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<WhisparrIndexer> GetIndexers(WhisparrSettings settings)
|
||||||
|
{
|
||||||
|
var request = BuildRequest(settings, "/api/v3/indexer", HttpMethod.Get);
|
||||||
|
return Execute<List<WhisparrIndexer>>(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WhisparrIndexer GetIndexer(int indexerId, WhisparrSettings settings)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = BuildRequest(settings, $"/api/v3/indexer/{indexerId}", HttpMethod.Get);
|
||||||
|
return Execute<WhisparrIndexer>(request);
|
||||||
|
}
|
||||||
|
catch (HttpException ex)
|
||||||
|
{
|
||||||
|
if (ex.Response.StatusCode != HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveIndexer(int indexerId, WhisparrSettings settings)
|
||||||
|
{
|
||||||
|
var request = BuildRequest(settings, $"/api/v3/indexer/{indexerId}", HttpMethod.Delete);
|
||||||
|
_httpClient.Execute(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<WhisparrIndexer> GetIndexerSchema(WhisparrSettings settings)
|
||||||
|
{
|
||||||
|
var request = BuildRequest(settings, "/api/v3/indexer/schema", HttpMethod.Get);
|
||||||
|
return Execute<List<WhisparrIndexer>>(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WhisparrIndexer AddIndexer(WhisparrIndexer indexer, WhisparrSettings settings)
|
||||||
|
{
|
||||||
|
var request = BuildRequest(settings, "/api/v3/indexer", HttpMethod.Post);
|
||||||
|
|
||||||
|
request.SetContent(indexer.ToJson());
|
||||||
|
|
||||||
|
return Execute<WhisparrIndexer>(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WhisparrIndexer UpdateIndexer(WhisparrIndexer indexer, WhisparrSettings settings)
|
||||||
|
{
|
||||||
|
var request = BuildRequest(settings, $"/api/v3/indexer/{indexer.Id}", HttpMethod.Put);
|
||||||
|
|
||||||
|
request.SetContent(indexer.ToJson());
|
||||||
|
|
||||||
|
return Execute<WhisparrIndexer>(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidationFailure TestConnection(WhisparrIndexer indexer, WhisparrSettings settings)
|
||||||
|
{
|
||||||
|
var request = BuildRequest(settings, $"/api/v3/indexer/test", HttpMethod.Post);
|
||||||
|
|
||||||
|
request.SetContent(indexer.ToJson());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Execute<WhisparrIndexer>(request);
|
||||||
|
}
|
||||||
|
catch (HttpException ex)
|
||||||
|
{
|
||||||
|
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "API Key is invalid");
|
||||||
|
return new ValidationFailure("ApiKey", "API Key is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ex.Response.StatusCode == HttpStatusCode.BadRequest)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Prowlarr URL is invalid");
|
||||||
|
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Whisparr cannot connect to Prowlarr");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ex.Response.StatusCode == HttpStatusCode.SeeOther)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Whisparr returned redirect and is invalid");
|
||||||
|
return new ValidationFailure("BaseUrl", "Whisparr url is invalid, Prowlarr cannot connect to Whisparr - are you missing a url base?");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Error(ex, "Unable to send test message");
|
||||||
|
return new ValidationFailure("BaseUrl", "Unable to complete application test");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Unable to send test message");
|
||||||
|
return new ValidationFailure("", "Unable to send test message");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpRequest BuildRequest(WhisparrSettings settings, string resource, HttpMethod method)
|
||||||
|
{
|
||||||
|
var baseUrl = settings.BaseUrl.TrimEnd('/');
|
||||||
|
|
||||||
|
var request = new HttpRequestBuilder(baseUrl).Resource(resource)
|
||||||
|
.SetHeader("X-Api-Key", settings.ApiKey)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
request.Headers.ContentType = "application/json";
|
||||||
|
|
||||||
|
request.Method = method;
|
||||||
|
request.AllowAutoRedirect = true;
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TResource Execute<TResource>(HttpRequest request)
|
||||||
|
where TResource : new()
|
||||||
|
{
|
||||||
|
var response = _httpClient.Execute(request);
|
||||||
|
|
||||||
|
var results = JsonConvert.DeserializeObject<TResource>(response.Content);
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -90,6 +90,8 @@ namespace NzbDrone.Core.Backup
|
|||||||
|
|
||||||
_archiveService.CreateZip(backupPath, _diskProvider.GetFiles(_backupTempFolder, SearchOption.TopDirectoryOnly));
|
_archiveService.CreateZip(backupPath, _diskProvider.GetFiles(_backupTempFolder, SearchOption.TopDirectoryOnly));
|
||||||
|
|
||||||
|
Cleanup();
|
||||||
|
|
||||||
_logger.ProgressDebug("Backup zip created");
|
_logger.ProgressDebug("Backup zip created");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ namespace NzbDrone.Core.Datastore
|
|||||||
_updateSql = GetUpdateSql(_properties);
|
_updateSql = GetUpdateSql(_properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual SqlBuilder Builder() => new SqlBuilder();
|
protected virtual SqlBuilder Builder() => new SqlBuilder(_database.DatabaseType);
|
||||||
|
|
||||||
protected virtual List<TModel> Query(SqlBuilder builder) => _database.Query<TModel>(builder).ToList();
|
protected virtual List<TModel> Query(SqlBuilder builder) => _database.Query<TModel>(builder).ToList();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Data;
|
||||||
|
using Dapper;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Converters
|
||||||
|
{
|
||||||
|
public class DapperTimeSpanConverter : SqlMapper.TypeHandler<TimeSpan>
|
||||||
|
{
|
||||||
|
public override void SetValue(IDbDataParameter parameter, TimeSpan value)
|
||||||
|
{
|
||||||
|
parameter.Value = value.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override TimeSpan Parse(object value)
|
||||||
|
{
|
||||||
|
return TimeSpan.Parse((string)value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,21 +42,21 @@ namespace NzbDrone.Core.Datastore
|
|||||||
|
|
||||||
public static SqlBuilder Where<TModel>(this SqlBuilder builder, Expression<Func<TModel, bool>> filter)
|
public static SqlBuilder Where<TModel>(this SqlBuilder builder, Expression<Func<TModel, bool>> filter)
|
||||||
{
|
{
|
||||||
var wb = new WhereBuilder(filter, true, builder.Sequence);
|
var wb = GetWhereBuilder(builder.DatabaseType, filter, true, builder.Sequence);
|
||||||
|
|
||||||
return builder.Where(wb.ToString(), wb.Parameters);
|
return builder.Where(wb.ToString(), wb.Parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SqlBuilder OrWhere<TModel>(this SqlBuilder builder, Expression<Func<TModel, bool>> filter)
|
public static SqlBuilder OrWhere<TModel>(this SqlBuilder builder, Expression<Func<TModel, bool>> filter)
|
||||||
{
|
{
|
||||||
var wb = new WhereBuilder(filter, true, builder.Sequence);
|
var wb = GetWhereBuilder(builder.DatabaseType, filter, true, builder.Sequence);
|
||||||
|
|
||||||
return builder.OrWhere(wb.ToString(), wb.Parameters);
|
return builder.OrWhere(wb.ToString(), wb.Parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SqlBuilder Join<TLeft, TRight>(this SqlBuilder builder, Expression<Func<TLeft, TRight, bool>> filter)
|
public static SqlBuilder Join<TLeft, TRight>(this SqlBuilder builder, Expression<Func<TLeft, TRight, bool>> filter)
|
||||||
{
|
{
|
||||||
var wb = new WhereBuilder(filter, false, builder.Sequence);
|
var wb = GetWhereBuilder(builder.DatabaseType, filter, false, builder.Sequence);
|
||||||
|
|
||||||
var rightTable = TableMapping.Mapper.TableNameMapping(typeof(TRight));
|
var rightTable = TableMapping.Mapper.TableNameMapping(typeof(TRight));
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ namespace NzbDrone.Core.Datastore
|
|||||||
|
|
||||||
public static SqlBuilder LeftJoin<TLeft, TRight>(this SqlBuilder builder, Expression<Func<TLeft, TRight, bool>> filter)
|
public static SqlBuilder LeftJoin<TLeft, TRight>(this SqlBuilder builder, Expression<Func<TLeft, TRight, bool>> filter)
|
||||||
{
|
{
|
||||||
var wb = new WhereBuilder(filter, false, builder.Sequence);
|
var wb = GetWhereBuilder(builder.DatabaseType, filter, false, builder.Sequence);
|
||||||
|
|
||||||
var rightTable = TableMapping.Mapper.TableNameMapping(typeof(TRight));
|
var rightTable = TableMapping.Mapper.TableNameMapping(typeof(TRight));
|
||||||
|
|
||||||
@@ -138,6 +138,18 @@ namespace NzbDrone.Core.Datastore
|
|||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static WhereBuilder GetWhereBuilder(DatabaseType databaseType, Expression filter, bool requireConcrete, int seq)
|
||||||
|
{
|
||||||
|
if (databaseType == DatabaseType.PostgreSQL)
|
||||||
|
{
|
||||||
|
return new WhereBuilderPostgres(filter, requireConcrete, seq);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new WhereBuilderSqlite(filter, requireConcrete, seq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static Dictionary<string, object> ToDictionary(this DynamicParameters dynamicParams)
|
private static Dictionary<string, object> ToDictionary(this DynamicParameters dynamicParams)
|
||||||
{
|
{
|
||||||
var argsDictionary = new Dictionary<string, object>();
|
var argsDictionary = new Dictionary<string, object>();
|
||||||
|
|||||||
@@ -8,9 +8,17 @@ namespace NzbDrone.Core.Datastore
|
|||||||
public class SqlBuilder
|
public class SqlBuilder
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, Clauses> _data = new Dictionary<string, Clauses>();
|
private readonly Dictionary<string, Clauses> _data = new Dictionary<string, Clauses>();
|
||||||
|
private readonly DatabaseType _databaseType;
|
||||||
|
|
||||||
|
public SqlBuilder(DatabaseType databaseType)
|
||||||
|
{
|
||||||
|
_databaseType = databaseType;
|
||||||
|
}
|
||||||
|
|
||||||
public int Sequence { get; private set; }
|
public int Sequence { get; private set; }
|
||||||
|
|
||||||
|
public DatabaseType DatabaseType => _databaseType;
|
||||||
|
|
||||||
public Template AddTemplate(string sql, dynamic parameters = null) =>
|
public Template AddTemplate(string sql, dynamic parameters = null) =>
|
||||||
new Template(this, sql, parameters);
|
new Template(this, sql, parameters);
|
||||||
|
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ namespace NzbDrone.Core.Datastore
|
|||||||
(db, parent) =>
|
(db, parent) =>
|
||||||
{
|
{
|
||||||
var id = childIdSelector(parent);
|
var id = childIdSelector(parent);
|
||||||
return db.Query<TChild>(new SqlBuilder().Where<TChild>(x => x.Id == id)).SingleOrDefault();
|
return db.Query<TChild>(new SqlBuilder(db.DatabaseType).Where<TChild>(x => x.Id == id)).SingleOrDefault();
|
||||||
},
|
},
|
||||||
parent => childIdSelector(parent) > 0);
|
parent => childIdSelector(parent) > 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ namespace NzbDrone.Core.Datastore
|
|||||||
|
|
||||||
SqlMapper.RemoveTypeMap(typeof(DateTime));
|
SqlMapper.RemoveTypeMap(typeof(DateTime));
|
||||||
SqlMapper.AddTypeHandler(new DapperUtcConverter());
|
SqlMapper.AddTypeHandler(new DapperUtcConverter());
|
||||||
|
SqlMapper.AddTypeHandler(new DapperTimeSpanConverter());
|
||||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<Dictionary<string, string>>());
|
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<Dictionary<string, string>>());
|
||||||
SqlMapper.AddTypeHandler(new CookieConverter());
|
SqlMapper.AddTypeHandler(new CookieConverter());
|
||||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<int>>());
|
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<int>>());
|
||||||
|
|||||||
@@ -1,389 +1,9 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Data;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Linq.Expressions;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
|
||||||
using Dapper;
|
using Dapper;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Datastore
|
namespace NzbDrone.Core.Datastore
|
||||||
{
|
{
|
||||||
public class WhereBuilder : ExpressionVisitor
|
public abstract class WhereBuilder : ExpressionVisitor
|
||||||
{
|
{
|
||||||
protected StringBuilder _sb;
|
public DynamicParameters Parameters { get; protected set; }
|
||||||
|
|
||||||
private const DbType EnumerableMultiParameter = (DbType)(-1);
|
|
||||||
private readonly string _paramNamePrefix;
|
|
||||||
private readonly bool _requireConcreteValue = false;
|
|
||||||
private int _paramCount = 0;
|
|
||||||
private bool _gotConcreteValue = false;
|
|
||||||
|
|
||||||
public WhereBuilder(Expression filter, bool requireConcreteValue, int seq)
|
|
||||||
{
|
|
||||||
_paramNamePrefix = string.Format("Clause{0}", seq + 1);
|
|
||||||
_requireConcreteValue = requireConcreteValue;
|
|
||||||
_sb = new StringBuilder();
|
|
||||||
|
|
||||||
Parameters = new DynamicParameters();
|
|
||||||
|
|
||||||
if (filter != null)
|
|
||||||
{
|
|
||||||
Visit(filter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public DynamicParameters Parameters { get; private set; }
|
|
||||||
|
|
||||||
private string AddParameter(object value, DbType? dbType = null)
|
|
||||||
{
|
|
||||||
_gotConcreteValue = true;
|
|
||||||
_paramCount++;
|
|
||||||
var name = _paramNamePrefix + "_P" + _paramCount;
|
|
||||||
Parameters.Add(name, value, dbType);
|
|
||||||
return '@' + name;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Expression VisitBinary(BinaryExpression expression)
|
|
||||||
{
|
|
||||||
_sb.Append('(');
|
|
||||||
|
|
||||||
Visit(expression.Left);
|
|
||||||
|
|
||||||
_sb.AppendFormat(" {0} ", Decode(expression));
|
|
||||||
|
|
||||||
Visit(expression.Right);
|
|
||||||
|
|
||||||
_sb.Append(')');
|
|
||||||
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Expression VisitMethodCall(MethodCallExpression expression)
|
|
||||||
{
|
|
||||||
var method = expression.Method.Name;
|
|
||||||
|
|
||||||
switch (expression.Method.Name)
|
|
||||||
{
|
|
||||||
case "Contains":
|
|
||||||
ParseContainsExpression(expression);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "StartsWith":
|
|
||||||
ParseStartsWith(expression);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "EndsWith":
|
|
||||||
ParseEndsWith(expression);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
var msg = string.Format("'{0}' expressions are not yet implemented in the where clause expression tree parser.", method);
|
|
||||||
throw new NotImplementedException(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Expression VisitMemberAccess(MemberExpression expression)
|
|
||||||
{
|
|
||||||
var tableName = expression?.Expression?.Type != null ? TableMapping.Mapper.TableNameMapping(expression.Expression.Type) : null;
|
|
||||||
var gotValue = TryGetRightValue(expression, out var value);
|
|
||||||
|
|
||||||
// Only use the SQL condition if the expression didn't resolve to an actual value
|
|
||||||
if (tableName != null && !gotValue)
|
|
||||||
{
|
|
||||||
_sb.Append($"\"{tableName}\".\"{expression.Member.Name}\"");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (value != null)
|
|
||||||
{
|
|
||||||
// string is IEnumerable<Char> but we don't want to pick up that case
|
|
||||||
var type = value.GetType();
|
|
||||||
var typeInfo = type.GetTypeInfo();
|
|
||||||
var isEnumerable =
|
|
||||||
type != typeof(string) && (
|
|
||||||
typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) ||
|
|
||||||
(typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>)));
|
|
||||||
|
|
||||||
var paramName = isEnumerable ? AddParameter(value, EnumerableMultiParameter) : AddParameter(value);
|
|
||||||
_sb.Append(paramName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_gotConcreteValue = true;
|
|
||||||
_sb.Append("NULL");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Expression VisitConstant(ConstantExpression expression)
|
|
||||||
{
|
|
||||||
if (expression.Value != null)
|
|
||||||
{
|
|
||||||
var paramName = AddParameter(expression.Value);
|
|
||||||
_sb.Append(paramName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_gotConcreteValue = true;
|
|
||||||
_sb.Append("NULL");
|
|
||||||
}
|
|
||||||
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryGetConstantValue(Expression expression, out object result)
|
|
||||||
{
|
|
||||||
result = null;
|
|
||||||
|
|
||||||
if (expression is ConstantExpression constExp)
|
|
||||||
{
|
|
||||||
result = constExp.Value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryGetPropertyValue(MemberExpression expression, out object result)
|
|
||||||
{
|
|
||||||
result = null;
|
|
||||||
|
|
||||||
if (expression.Expression is MemberExpression nested)
|
|
||||||
{
|
|
||||||
// Value is passed in as a property on a parent entity
|
|
||||||
var container = (nested.Expression as ConstantExpression)?.Value;
|
|
||||||
|
|
||||||
if (container == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var entity = GetFieldValue(container, nested.Member);
|
|
||||||
result = GetFieldValue(entity, expression.Member);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryGetVariableValue(MemberExpression expression, out object result)
|
|
||||||
{
|
|
||||||
result = null;
|
|
||||||
|
|
||||||
// Value is passed in as a variable
|
|
||||||
if (expression.Expression is ConstantExpression nested)
|
|
||||||
{
|
|
||||||
result = GetFieldValue(nested.Value, expression.Member);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryGetRightValue(Expression expression, out object value)
|
|
||||||
{
|
|
||||||
value = null;
|
|
||||||
|
|
||||||
if (TryGetConstantValue(expression, out value))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var memberExp = expression as MemberExpression;
|
|
||||||
|
|
||||||
if (TryGetPropertyValue(memberExp, out value))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryGetVariableValue(memberExp, out value))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private object GetFieldValue(object entity, MemberInfo member)
|
|
||||||
{
|
|
||||||
if (member.MemberType == MemberTypes.Field)
|
|
||||||
{
|
|
||||||
return (member as FieldInfo).GetValue(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (member.MemberType == MemberTypes.Property)
|
|
||||||
{
|
|
||||||
return (member as PropertyInfo).GetValue(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ArgumentException(string.Format("WhereBuilder could not get the value for {0}.{1}.", entity.GetType().Name, member.Name));
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsNullVariable(Expression expression)
|
|
||||||
{
|
|
||||||
if (expression.NodeType == ExpressionType.Constant &&
|
|
||||||
TryGetConstantValue(expression, out var constResult) &&
|
|
||||||
constResult == null)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expression.NodeType == ExpressionType.MemberAccess &&
|
|
||||||
expression is MemberExpression member &&
|
|
||||||
((TryGetPropertyValue(member, out var result) && result == null) ||
|
|
||||||
(TryGetVariableValue(member, out result) && result == null)))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string Decode(BinaryExpression expression)
|
|
||||||
{
|
|
||||||
if (IsNullVariable(expression.Right))
|
|
||||||
{
|
|
||||||
switch (expression.NodeType)
|
|
||||||
{
|
|
||||||
case ExpressionType.Equal: return "IS";
|
|
||||||
case ExpressionType.NotEqual: return "IS NOT";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (expression.NodeType)
|
|
||||||
{
|
|
||||||
case ExpressionType.AndAlso: return "AND";
|
|
||||||
case ExpressionType.And: return "AND";
|
|
||||||
case ExpressionType.Equal: return "=";
|
|
||||||
case ExpressionType.GreaterThan: return ">";
|
|
||||||
case ExpressionType.GreaterThanOrEqual: return ">=";
|
|
||||||
case ExpressionType.LessThan: return "<";
|
|
||||||
case ExpressionType.LessThanOrEqual: return "<=";
|
|
||||||
case ExpressionType.NotEqual: return "<>";
|
|
||||||
case ExpressionType.OrElse: return "OR";
|
|
||||||
case ExpressionType.Or: return "OR";
|
|
||||||
default: throw new NotSupportedException(string.Format("{0} statement is not supported", expression.NodeType.ToString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ParseContainsExpression(MethodCallExpression expression)
|
|
||||||
{
|
|
||||||
var list = expression.Object;
|
|
||||||
|
|
||||||
if (list != null && (list.Type == typeof(string)))
|
|
||||||
{
|
|
||||||
ParseStringContains(expression);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ParseEnumerableContains(expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ParseEnumerableContains(MethodCallExpression body)
|
|
||||||
{
|
|
||||||
// Fish out the list and the item to compare
|
|
||||||
// It's in a different form for arrays and Lists
|
|
||||||
var list = body.Object;
|
|
||||||
Expression item;
|
|
||||||
|
|
||||||
if (list != null)
|
|
||||||
{
|
|
||||||
// Generic collection
|
|
||||||
item = body.Arguments[0];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Static method
|
|
||||||
// Must be Enumerable.Contains(source, item)
|
|
||||||
if (body.Method.DeclaringType != typeof(Enumerable) || body.Arguments.Count != 2)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException("Unexpected form of Enumerable.Contains");
|
|
||||||
}
|
|
||||||
|
|
||||||
list = body.Arguments[0];
|
|
||||||
item = body.Arguments[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
_sb.Append('(');
|
|
||||||
|
|
||||||
Visit(item);
|
|
||||||
|
|
||||||
_sb.Append(" IN ");
|
|
||||||
|
|
||||||
// hardcode the integer list if it exists to bypass parameter limit
|
|
||||||
if (item.Type == typeof(int) && TryGetRightValue(list, out var value))
|
|
||||||
{
|
|
||||||
var items = (IEnumerable<int>)value;
|
|
||||||
_sb.Append('(');
|
|
||||||
_sb.Append(string.Join(", ", items));
|
|
||||||
_sb.Append(')');
|
|
||||||
|
|
||||||
_gotConcreteValue = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Visit(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
_sb.Append(')');
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ParseStringContains(MethodCallExpression body)
|
|
||||||
{
|
|
||||||
_sb.Append('(');
|
|
||||||
|
|
||||||
Visit(body.Object);
|
|
||||||
|
|
||||||
_sb.Append(" LIKE '%' || ");
|
|
||||||
|
|
||||||
Visit(body.Arguments[0]);
|
|
||||||
|
|
||||||
_sb.Append(" || '%')");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ParseStartsWith(MethodCallExpression body)
|
|
||||||
{
|
|
||||||
_sb.Append('(');
|
|
||||||
|
|
||||||
Visit(body.Object);
|
|
||||||
|
|
||||||
_sb.Append(" LIKE ");
|
|
||||||
|
|
||||||
Visit(body.Arguments[0]);
|
|
||||||
|
|
||||||
_sb.Append(" || '%')");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ParseEndsWith(MethodCallExpression body)
|
|
||||||
{
|
|
||||||
_sb.Append('(');
|
|
||||||
|
|
||||||
Visit(body.Object);
|
|
||||||
|
|
||||||
_sb.Append(" LIKE '%' || ");
|
|
||||||
|
|
||||||
Visit(body.Arguments[0]);
|
|
||||||
|
|
||||||
_sb.Append(')');
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
var sql = _sb.ToString();
|
|
||||||
|
|
||||||
if (_requireConcreteValue && !_gotConcreteValue)
|
|
||||||
{
|
|
||||||
var e = new InvalidOperationException("WhereBuilder requires a concrete condition");
|
|
||||||
e.Data.Add("sql", sql);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sql;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,374 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using Dapper;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore
|
||||||
|
{
|
||||||
|
public class WhereBuilderPostgres : WhereBuilder
|
||||||
|
{
|
||||||
|
protected StringBuilder _sb;
|
||||||
|
|
||||||
|
private const DbType EnumerableMultiParameter = (DbType)(-1);
|
||||||
|
private readonly string _paramNamePrefix;
|
||||||
|
private readonly bool _requireConcreteValue = false;
|
||||||
|
private int _paramCount = 0;
|
||||||
|
private bool _gotConcreteValue = false;
|
||||||
|
|
||||||
|
public WhereBuilderPostgres(Expression filter, bool requireConcreteValue, int seq)
|
||||||
|
{
|
||||||
|
_paramNamePrefix = string.Format("Clause{0}", seq + 1);
|
||||||
|
_requireConcreteValue = requireConcreteValue;
|
||||||
|
_sb = new StringBuilder();
|
||||||
|
|
||||||
|
Parameters = new DynamicParameters();
|
||||||
|
|
||||||
|
if (filter != null)
|
||||||
|
{
|
||||||
|
Visit(filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string AddParameter(object value, DbType? dbType = null)
|
||||||
|
{
|
||||||
|
_gotConcreteValue = true;
|
||||||
|
_paramCount++;
|
||||||
|
var name = _paramNamePrefix + "_P" + _paramCount;
|
||||||
|
Parameters.Add(name, value, dbType);
|
||||||
|
return '@' + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Expression VisitBinary(BinaryExpression expression)
|
||||||
|
{
|
||||||
|
_sb.Append('(');
|
||||||
|
|
||||||
|
Visit(expression.Left);
|
||||||
|
|
||||||
|
_sb.AppendFormat(" {0} ", Decode(expression));
|
||||||
|
|
||||||
|
Visit(expression.Right);
|
||||||
|
|
||||||
|
_sb.Append(')');
|
||||||
|
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Expression VisitMethodCall(MethodCallExpression expression)
|
||||||
|
{
|
||||||
|
var method = expression.Method.Name;
|
||||||
|
|
||||||
|
switch (expression.Method.Name)
|
||||||
|
{
|
||||||
|
case "Contains":
|
||||||
|
ParseContainsExpression(expression);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "StartsWith":
|
||||||
|
ParseStartsWith(expression);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "EndsWith":
|
||||||
|
ParseEndsWith(expression);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
var msg = string.Format("'{0}' expressions are not yet implemented in the where clause expression tree parser.", method);
|
||||||
|
throw new NotImplementedException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Expression VisitMemberAccess(MemberExpression expression)
|
||||||
|
{
|
||||||
|
var tableName = expression?.Expression?.Type != null ? TableMapping.Mapper.TableNameMapping(expression.Expression.Type) : null;
|
||||||
|
var gotValue = TryGetRightValue(expression, out var value);
|
||||||
|
|
||||||
|
// Only use the SQL condition if the expression didn't resolve to an actual value
|
||||||
|
if (tableName != null && !gotValue)
|
||||||
|
{
|
||||||
|
_sb.Append($"\"{tableName}\".\"{expression.Member.Name}\"");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
// string is IEnumerable<Char> but we don't want to pick up that case
|
||||||
|
var type = value.GetType();
|
||||||
|
var typeInfo = type.GetTypeInfo();
|
||||||
|
var isEnumerable =
|
||||||
|
type != typeof(string) && (
|
||||||
|
typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) ||
|
||||||
|
(typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>)));
|
||||||
|
|
||||||
|
var paramName = isEnumerable ? AddParameter(value, EnumerableMultiParameter) : AddParameter(value);
|
||||||
|
_sb.Append(paramName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_gotConcreteValue = true;
|
||||||
|
_sb.Append("NULL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Expression VisitConstant(ConstantExpression expression)
|
||||||
|
{
|
||||||
|
if (expression.Value != null)
|
||||||
|
{
|
||||||
|
var paramName = AddParameter(expression.Value);
|
||||||
|
_sb.Append(paramName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_gotConcreteValue = true;
|
||||||
|
_sb.Append("NULL");
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetConstantValue(Expression expression, out object result)
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
|
||||||
|
if (expression is ConstantExpression constExp)
|
||||||
|
{
|
||||||
|
result = constExp.Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetPropertyValue(MemberExpression expression, out object result)
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
|
||||||
|
if (expression.Expression is MemberExpression nested)
|
||||||
|
{
|
||||||
|
// Value is passed in as a property on a parent entity
|
||||||
|
var container = (nested.Expression as ConstantExpression)?.Value;
|
||||||
|
|
||||||
|
if (container == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var entity = GetFieldValue(container, nested.Member);
|
||||||
|
result = GetFieldValue(entity, expression.Member);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetVariableValue(MemberExpression expression, out object result)
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
|
||||||
|
// Value is passed in as a variable
|
||||||
|
if (expression.Expression is ConstantExpression nested)
|
||||||
|
{
|
||||||
|
result = GetFieldValue(nested.Value, expression.Member);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetRightValue(Expression expression, out object value)
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
|
||||||
|
if (TryGetConstantValue(expression, out value))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var memberExp = expression as MemberExpression;
|
||||||
|
|
||||||
|
if (TryGetPropertyValue(memberExp, out value))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryGetVariableValue(memberExp, out value))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private object GetFieldValue(object entity, MemberInfo member)
|
||||||
|
{
|
||||||
|
if (member.MemberType == MemberTypes.Field)
|
||||||
|
{
|
||||||
|
return (member as FieldInfo).GetValue(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (member.MemberType == MemberTypes.Property)
|
||||||
|
{
|
||||||
|
return (member as PropertyInfo).GetValue(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ArgumentException(string.Format("WhereBuilder could not get the value for {0}.{1}.", entity.GetType().Name, member.Name));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsNullVariable(Expression expression)
|
||||||
|
{
|
||||||
|
if (expression.NodeType == ExpressionType.Constant &&
|
||||||
|
TryGetConstantValue(expression, out var constResult) &&
|
||||||
|
constResult == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expression.NodeType == ExpressionType.MemberAccess &&
|
||||||
|
expression is MemberExpression member &&
|
||||||
|
((TryGetPropertyValue(member, out var result) && result == null) ||
|
||||||
|
(TryGetVariableValue(member, out result) && result == null)))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string Decode(BinaryExpression expression)
|
||||||
|
{
|
||||||
|
if (IsNullVariable(expression.Right))
|
||||||
|
{
|
||||||
|
switch (expression.NodeType)
|
||||||
|
{
|
||||||
|
case ExpressionType.Equal: return "IS";
|
||||||
|
case ExpressionType.NotEqual: return "IS NOT";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (expression.NodeType)
|
||||||
|
{
|
||||||
|
case ExpressionType.AndAlso: return "AND";
|
||||||
|
case ExpressionType.And: return "AND";
|
||||||
|
case ExpressionType.Equal: return "=";
|
||||||
|
case ExpressionType.GreaterThan: return ">";
|
||||||
|
case ExpressionType.GreaterThanOrEqual: return ">=";
|
||||||
|
case ExpressionType.LessThan: return "<";
|
||||||
|
case ExpressionType.LessThanOrEqual: return "<=";
|
||||||
|
case ExpressionType.NotEqual: return "<>";
|
||||||
|
case ExpressionType.OrElse: return "OR";
|
||||||
|
case ExpressionType.Or: return "OR";
|
||||||
|
default: throw new NotSupportedException(string.Format("{0} statement is not supported", expression.NodeType.ToString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseContainsExpression(MethodCallExpression expression)
|
||||||
|
{
|
||||||
|
var list = expression.Object;
|
||||||
|
|
||||||
|
if (list != null && (list.Type == typeof(string)))
|
||||||
|
{
|
||||||
|
ParseStringContains(expression);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseEnumerableContains(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseEnumerableContains(MethodCallExpression body)
|
||||||
|
{
|
||||||
|
// Fish out the list and the item to compare
|
||||||
|
// It's in a different form for arrays and Lists
|
||||||
|
var list = body.Object;
|
||||||
|
Expression item;
|
||||||
|
|
||||||
|
if (list != null)
|
||||||
|
{
|
||||||
|
// Generic collection
|
||||||
|
item = body.Arguments[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Static method
|
||||||
|
// Must be Enumerable.Contains(source, item)
|
||||||
|
if (body.Method.DeclaringType != typeof(Enumerable) || body.Arguments.Count != 2)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Unexpected form of Enumerable.Contains");
|
||||||
|
}
|
||||||
|
|
||||||
|
list = body.Arguments[0];
|
||||||
|
item = body.Arguments[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
_sb.Append('(');
|
||||||
|
|
||||||
|
Visit(item);
|
||||||
|
|
||||||
|
_sb.Append(" = ANY (");
|
||||||
|
|
||||||
|
Visit(list);
|
||||||
|
|
||||||
|
_sb.Append("))");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseStringContains(MethodCallExpression body)
|
||||||
|
{
|
||||||
|
_sb.Append('(');
|
||||||
|
|
||||||
|
Visit(body.Object);
|
||||||
|
|
||||||
|
_sb.Append(" LIKE '%' || ");
|
||||||
|
|
||||||
|
Visit(body.Arguments[0]);
|
||||||
|
|
||||||
|
_sb.Append(" || '%')");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseStartsWith(MethodCallExpression body)
|
||||||
|
{
|
||||||
|
_sb.Append('(');
|
||||||
|
|
||||||
|
Visit(body.Object);
|
||||||
|
|
||||||
|
_sb.Append(" LIKE ");
|
||||||
|
|
||||||
|
Visit(body.Arguments[0]);
|
||||||
|
|
||||||
|
_sb.Append(" || '%')");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseEndsWith(MethodCallExpression body)
|
||||||
|
{
|
||||||
|
_sb.Append('(');
|
||||||
|
|
||||||
|
Visit(body.Object);
|
||||||
|
|
||||||
|
_sb.Append(" LIKE '%' || ");
|
||||||
|
|
||||||
|
Visit(body.Arguments[0]);
|
||||||
|
|
||||||
|
_sb.Append(')');
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var sql = _sb.ToString();
|
||||||
|
|
||||||
|
if (_requireConcreteValue && !_gotConcreteValue)
|
||||||
|
{
|
||||||
|
var e = new InvalidOperationException("WhereBuilder requires a concrete condition");
|
||||||
|
e.Data.Add("sql", sql);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,387 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using Dapper;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore
|
||||||
|
{
|
||||||
|
public class WhereBuilderSqlite : WhereBuilder
|
||||||
|
{
|
||||||
|
protected StringBuilder _sb;
|
||||||
|
|
||||||
|
private const DbType EnumerableMultiParameter = (DbType)(-1);
|
||||||
|
private readonly string _paramNamePrefix;
|
||||||
|
private readonly bool _requireConcreteValue = false;
|
||||||
|
private int _paramCount = 0;
|
||||||
|
private bool _gotConcreteValue = false;
|
||||||
|
|
||||||
|
public WhereBuilderSqlite(Expression filter, bool requireConcreteValue, int seq)
|
||||||
|
{
|
||||||
|
_paramNamePrefix = string.Format("Clause{0}", seq + 1);
|
||||||
|
_requireConcreteValue = requireConcreteValue;
|
||||||
|
_sb = new StringBuilder();
|
||||||
|
|
||||||
|
Parameters = new DynamicParameters();
|
||||||
|
|
||||||
|
if (filter != null)
|
||||||
|
{
|
||||||
|
Visit(filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string AddParameter(object value, DbType? dbType = null)
|
||||||
|
{
|
||||||
|
_gotConcreteValue = true;
|
||||||
|
_paramCount++;
|
||||||
|
var name = _paramNamePrefix + "_P" + _paramCount;
|
||||||
|
Parameters.Add(name, value, dbType);
|
||||||
|
return '@' + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Expression VisitBinary(BinaryExpression expression)
|
||||||
|
{
|
||||||
|
_sb.Append('(');
|
||||||
|
|
||||||
|
Visit(expression.Left);
|
||||||
|
|
||||||
|
_sb.AppendFormat(" {0} ", Decode(expression));
|
||||||
|
|
||||||
|
Visit(expression.Right);
|
||||||
|
|
||||||
|
_sb.Append(')');
|
||||||
|
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Expression VisitMethodCall(MethodCallExpression expression)
|
||||||
|
{
|
||||||
|
var method = expression.Method.Name;
|
||||||
|
|
||||||
|
switch (expression.Method.Name)
|
||||||
|
{
|
||||||
|
case "Contains":
|
||||||
|
ParseContainsExpression(expression);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "StartsWith":
|
||||||
|
ParseStartsWith(expression);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "EndsWith":
|
||||||
|
ParseEndsWith(expression);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
var msg = string.Format("'{0}' expressions are not yet implemented in the where clause expression tree parser.", method);
|
||||||
|
throw new NotImplementedException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Expression VisitMemberAccess(MemberExpression expression)
|
||||||
|
{
|
||||||
|
var tableName = expression?.Expression?.Type != null ? TableMapping.Mapper.TableNameMapping(expression.Expression.Type) : null;
|
||||||
|
var gotValue = TryGetRightValue(expression, out var value);
|
||||||
|
|
||||||
|
// Only use the SQL condition if the expression didn't resolve to an actual value
|
||||||
|
if (tableName != null && !gotValue)
|
||||||
|
{
|
||||||
|
_sb.Append($"\"{tableName}\".\"{expression.Member.Name}\"");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
// string is IEnumerable<Char> but we don't want to pick up that case
|
||||||
|
var type = value.GetType();
|
||||||
|
var typeInfo = type.GetTypeInfo();
|
||||||
|
var isEnumerable =
|
||||||
|
type != typeof(string) && (
|
||||||
|
typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) ||
|
||||||
|
(typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>)));
|
||||||
|
|
||||||
|
var paramName = isEnumerable ? AddParameter(value, EnumerableMultiParameter) : AddParameter(value);
|
||||||
|
_sb.Append(paramName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_gotConcreteValue = true;
|
||||||
|
_sb.Append("NULL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Expression VisitConstant(ConstantExpression expression)
|
||||||
|
{
|
||||||
|
if (expression.Value != null)
|
||||||
|
{
|
||||||
|
var paramName = AddParameter(expression.Value);
|
||||||
|
_sb.Append(paramName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_gotConcreteValue = true;
|
||||||
|
_sb.Append("NULL");
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetConstantValue(Expression expression, out object result)
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
|
||||||
|
if (expression is ConstantExpression constExp)
|
||||||
|
{
|
||||||
|
result = constExp.Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetPropertyValue(MemberExpression expression, out object result)
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
|
||||||
|
if (expression.Expression is MemberExpression nested)
|
||||||
|
{
|
||||||
|
// Value is passed in as a property on a parent entity
|
||||||
|
var container = (nested.Expression as ConstantExpression)?.Value;
|
||||||
|
|
||||||
|
if (container == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var entity = GetFieldValue(container, nested.Member);
|
||||||
|
result = GetFieldValue(entity, expression.Member);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetVariableValue(MemberExpression expression, out object result)
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
|
||||||
|
// Value is passed in as a variable
|
||||||
|
if (expression.Expression is ConstantExpression nested)
|
||||||
|
{
|
||||||
|
result = GetFieldValue(nested.Value, expression.Member);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetRightValue(Expression expression, out object value)
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
|
||||||
|
if (TryGetConstantValue(expression, out value))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var memberExp = expression as MemberExpression;
|
||||||
|
|
||||||
|
if (TryGetPropertyValue(memberExp, out value))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryGetVariableValue(memberExp, out value))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private object GetFieldValue(object entity, MemberInfo member)
|
||||||
|
{
|
||||||
|
if (member.MemberType == MemberTypes.Field)
|
||||||
|
{
|
||||||
|
return (member as FieldInfo).GetValue(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (member.MemberType == MemberTypes.Property)
|
||||||
|
{
|
||||||
|
return (member as PropertyInfo).GetValue(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ArgumentException(string.Format("WhereBuilder could not get the value for {0}.{1}.", entity.GetType().Name, member.Name));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsNullVariable(Expression expression)
|
||||||
|
{
|
||||||
|
if (expression.NodeType == ExpressionType.Constant &&
|
||||||
|
TryGetConstantValue(expression, out var constResult) &&
|
||||||
|
constResult == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expression.NodeType == ExpressionType.MemberAccess &&
|
||||||
|
expression is MemberExpression member &&
|
||||||
|
((TryGetPropertyValue(member, out var result) && result == null) ||
|
||||||
|
(TryGetVariableValue(member, out result) && result == null)))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string Decode(BinaryExpression expression)
|
||||||
|
{
|
||||||
|
if (IsNullVariable(expression.Right))
|
||||||
|
{
|
||||||
|
switch (expression.NodeType)
|
||||||
|
{
|
||||||
|
case ExpressionType.Equal: return "IS";
|
||||||
|
case ExpressionType.NotEqual: return "IS NOT";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (expression.NodeType)
|
||||||
|
{
|
||||||
|
case ExpressionType.AndAlso: return "AND";
|
||||||
|
case ExpressionType.And: return "AND";
|
||||||
|
case ExpressionType.Equal: return "=";
|
||||||
|
case ExpressionType.GreaterThan: return ">";
|
||||||
|
case ExpressionType.GreaterThanOrEqual: return ">=";
|
||||||
|
case ExpressionType.LessThan: return "<";
|
||||||
|
case ExpressionType.LessThanOrEqual: return "<=";
|
||||||
|
case ExpressionType.NotEqual: return "<>";
|
||||||
|
case ExpressionType.OrElse: return "OR";
|
||||||
|
case ExpressionType.Or: return "OR";
|
||||||
|
default: throw new NotSupportedException(string.Format("{0} statement is not supported", expression.NodeType.ToString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseContainsExpression(MethodCallExpression expression)
|
||||||
|
{
|
||||||
|
var list = expression.Object;
|
||||||
|
|
||||||
|
if (list != null && (list.Type == typeof(string)))
|
||||||
|
{
|
||||||
|
ParseStringContains(expression);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseEnumerableContains(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseEnumerableContains(MethodCallExpression body)
|
||||||
|
{
|
||||||
|
// Fish out the list and the item to compare
|
||||||
|
// It's in a different form for arrays and Lists
|
||||||
|
var list = body.Object;
|
||||||
|
Expression item;
|
||||||
|
|
||||||
|
if (list != null)
|
||||||
|
{
|
||||||
|
// Generic collection
|
||||||
|
item = body.Arguments[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Static method
|
||||||
|
// Must be Enumerable.Contains(source, item)
|
||||||
|
if (body.Method.DeclaringType != typeof(Enumerable) || body.Arguments.Count != 2)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Unexpected form of Enumerable.Contains");
|
||||||
|
}
|
||||||
|
|
||||||
|
list = body.Arguments[0];
|
||||||
|
item = body.Arguments[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
_sb.Append('(');
|
||||||
|
|
||||||
|
Visit(item);
|
||||||
|
|
||||||
|
_sb.Append(" IN ");
|
||||||
|
|
||||||
|
// hardcode the integer list if it exists to bypass parameter limit
|
||||||
|
if (item.Type == typeof(int) && TryGetRightValue(list, out var value))
|
||||||
|
{
|
||||||
|
var items = (IEnumerable<int>)value;
|
||||||
|
_sb.Append('(');
|
||||||
|
_sb.Append(string.Join(", ", items));
|
||||||
|
_sb.Append(')');
|
||||||
|
|
||||||
|
_gotConcreteValue = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Visit(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
_sb.Append(')');
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseStringContains(MethodCallExpression body)
|
||||||
|
{
|
||||||
|
_sb.Append('(');
|
||||||
|
|
||||||
|
Visit(body.Object);
|
||||||
|
|
||||||
|
_sb.Append(" LIKE '%' || ");
|
||||||
|
|
||||||
|
Visit(body.Arguments[0]);
|
||||||
|
|
||||||
|
_sb.Append(" || '%')");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseStartsWith(MethodCallExpression body)
|
||||||
|
{
|
||||||
|
_sb.Append('(');
|
||||||
|
|
||||||
|
Visit(body.Object);
|
||||||
|
|
||||||
|
_sb.Append(" LIKE ");
|
||||||
|
|
||||||
|
Visit(body.Arguments[0]);
|
||||||
|
|
||||||
|
_sb.Append(" || '%')");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseEndsWith(MethodCallExpression body)
|
||||||
|
{
|
||||||
|
_sb.Append('(');
|
||||||
|
|
||||||
|
Visit(body.Object);
|
||||||
|
|
||||||
|
_sb.Append(" LIKE '%' || ");
|
||||||
|
|
||||||
|
Visit(body.Arguments[0]);
|
||||||
|
|
||||||
|
_sb.Append(')');
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var sql = _sb.ToString();
|
||||||
|
|
||||||
|
if (_requireConcreteValue && !_gotConcreteValue)
|
||||||
|
{
|
||||||
|
var e = new InvalidOperationException("WhereBuilder requires a concrete condition");
|
||||||
|
e.Data.Add("sql", sql);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -100,11 +100,13 @@ namespace NzbDrone.Core.History
|
|||||||
|
|
||||||
public int CountSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes)
|
public int CountSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes)
|
||||||
{
|
{
|
||||||
var builder = new SqlBuilder()
|
var intEvents = eventTypes.Select(t => (int)t).ToList();
|
||||||
|
|
||||||
|
var builder = new SqlBuilder(_database.DatabaseType)
|
||||||
.SelectCount()
|
.SelectCount()
|
||||||
.Where<History>(x => x.IndexerId == indexerId)
|
.Where<History>(x => x.IndexerId == indexerId)
|
||||||
.Where<History>(x => x.Date >= date)
|
.Where<History>(x => x.Date >= date)
|
||||||
.Where<History>(x => eventTypes.Contains(x.EventType));
|
.Where<History>(x => intEvents.Contains((int)x.EventType));
|
||||||
|
|
||||||
var sql = builder.AddPageCountTemplate(typeof(History));
|
var sql = builder.AddPageCountTemplate(typeof(History));
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using Dapper;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
|
{
|
||||||
|
public class CleanupOrphanedApplicationStatus : IHousekeepingTask
|
||||||
|
{
|
||||||
|
private readonly IMainDatabase _database;
|
||||||
|
|
||||||
|
public CleanupOrphanedApplicationStatus(IMainDatabase database)
|
||||||
|
{
|
||||||
|
_database = database;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clean()
|
||||||
|
{
|
||||||
|
var mapper = _database.OpenConnection();
|
||||||
|
|
||||||
|
mapper.Execute(@"DELETE FROM ""ApplicationStatus""
|
||||||
|
WHERE ""Id"" IN (
|
||||||
|
SELECT ""ApplicationStatus"".""Id"" FROM ""ApplicationStatus""
|
||||||
|
LEFT OUTER JOIN ""Applications""
|
||||||
|
ON ""ApplicationStatus"".""ProviderId"" = ""Applications"".""Id""
|
||||||
|
WHERE ""Applications"".""Id"" IS NULL)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -91,10 +91,12 @@ namespace NzbDrone.Core.IndexerSearch
|
|||||||
r.IndexerFlags == null ? null : from f in r.IndexerFlags select GetNabElement("tag", f.Name, protocol),
|
r.IndexerFlags == null ? null : from f in r.IndexerFlags select GetNabElement("tag", f.Name, protocol),
|
||||||
r.Languages == null ? null : from c in r.Languages select GetNabElement("language", c.Id, protocol),
|
r.Languages == null ? null : from c in r.Languages select GetNabElement("language", c.Id, protocol),
|
||||||
r.Subs == null ? null : from c in r.Subs select GetNabElement("subs", c.Id, protocol),
|
r.Subs == null ? null : from c in r.Subs select GetNabElement("subs", c.Id, protocol),
|
||||||
|
r.Genres == null ? null : GetNabElement("genre", string.Join(", ", r.Genres), protocol),
|
||||||
GetNabElement("rageid", r.TvRageId, protocol),
|
GetNabElement("rageid", r.TvRageId, protocol),
|
||||||
GetNabElement("tvdbid", r.TvdbId, protocol),
|
GetNabElement("tvdbid", r.TvdbId, protocol),
|
||||||
GetNabElement("imdb", r.ImdbId.ToString("D7"), protocol),
|
GetNabElement("imdb", r.ImdbId.ToString("D7"), protocol),
|
||||||
GetNabElement("tmdbid", r.TmdbId, protocol),
|
GetNabElement("tmdbid", r.TmdbId, protocol),
|
||||||
|
GetNabElement("traktid", r.TraktId, protocol),
|
||||||
GetNabElement("seeders", t.Seeders, protocol),
|
GetNabElement("seeders", t.Seeders, protocol),
|
||||||
GetNabElement("files", r.Files, protocol),
|
GetNabElement("files", r.Files, protocol),
|
||||||
GetNabElement("grabs", r.Grabs, protocol),
|
GetNabElement("grabs", r.Grabs, protocol),
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.IndexerVersions
|
|||||||
/* Update Service will fall back if version # does not exist for an indexer per Ta */
|
/* Update Service will fall back if version # does not exist for an indexer per Ta */
|
||||||
|
|
||||||
private const string DEFINITION_BRANCH = "master";
|
private const string DEFINITION_BRANCH = "master";
|
||||||
private const int DEFINITION_VERSION = 3;
|
private const int DEFINITION_VERSION = 5;
|
||||||
|
|
||||||
//Used when moving yml to C#
|
//Used when moving yml to C#
|
||||||
private readonly List<string> _defintionBlocklist = new List<string>()
|
private readonly List<string> _defintionBlocklist = new List<string>()
|
||||||
@@ -204,7 +204,7 @@ namespace NzbDrone.Core.IndexerVersions
|
|||||||
var dbDefs = _versionService.All();
|
var dbDefs = _versionService.All();
|
||||||
|
|
||||||
//Check to ensure it's in versioned defs before we go to web
|
//Check to ensure it's in versioned defs before we go to web
|
||||||
if (dbDefs.Count > 0 && dbDefs.Any(x => x.File == fileKey))
|
if (dbDefs.Count > 0 && !dbDefs.Any(x => x.File == fileKey))
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(fileKey));
|
throw new ArgumentNullException(nameof(fileKey));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,14 +48,17 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVAnime, "Anime Series");
|
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVAnime, "Anime Series");
|
||||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Audio, "Anime Musik/OST");
|
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Audio, "Anime Musik/OST");
|
||||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.PCGames, "Anime Spiele");
|
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.PCGames, "Anime Spiele");
|
||||||
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.XXX, "Hentai");
|
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.XXX, "Anime Hentai");
|
||||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.PCGames, "Spiele Linux");
|
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.PCGames, "Software");
|
||||||
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.Other, "Sonstiges");
|
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.Other, "Sonstiges");
|
||||||
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.Movies, "Filme");
|
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.Movies, "Filme");
|
||||||
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.TV, "Serien");
|
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.TV, "Serien");
|
||||||
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.PCGames, "Spiele");
|
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.PCGames, "Spiele");
|
||||||
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.Audio, "Musik");
|
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.Audio, "Musik");
|
||||||
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.BooksComics, "Mangas");
|
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.BooksComics, "Mangas");
|
||||||
|
caps.Categories.AddCategoryMapping(13, NewznabStandardCategory.Movies, "Cartoon Filme");
|
||||||
|
caps.Categories.AddCategoryMapping(14, NewznabStandardCategory.TV, "Cartoon Serie");
|
||||||
|
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.XXX, "H-Manga / Doujinshi");
|
||||||
|
|
||||||
return caps;
|
return caps;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
{ "order_way", "desc" },
|
{ "order_way", "desc" },
|
||||||
{ "action", "basic" },
|
{ "action", "basic" },
|
||||||
{ "searchsubmit", "1" },
|
{ "searchsubmit", "1" },
|
||||||
{ "searchstr", imdbId.IsNotNullOrWhiteSpace() ? imdbId : term }
|
{ "searchstr", imdbId.IsNotNullOrWhiteSpace() ? imdbId : term.Replace(".", " ") }
|
||||||
};
|
};
|
||||||
|
|
||||||
var catList = Capabilities.Categories.MapTorznabCapsToTrackers(categories);
|
var catList = Capabilities.Categories.MapTorznabCapsToTrackers(categories);
|
||||||
|
|||||||
@@ -46,6 +46,11 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
|||||||
public List<AvistazRelease> Data { get; set; }
|
public List<AvistazRelease> Data { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class AvistazErrorResponse
|
||||||
|
{
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class AvistazIdInfo
|
public class AvistazIdInfo
|
||||||
{
|
{
|
||||||
public string Tmdb { get; set; }
|
public string Tmdb { get; set; }
|
||||||
|
|||||||
@@ -89,6 +89,22 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
|||||||
{
|
{
|
||||||
await GetToken();
|
await GetToken();
|
||||||
}
|
}
|
||||||
|
catch (HttpException ex)
|
||||||
|
{
|
||||||
|
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
_logger.Warn(ex, "Unauthorized request to indexer");
|
||||||
|
|
||||||
|
var jsonResponse = new HttpResponse<AvistazErrorResponse>(ex.Response);
|
||||||
|
return new ValidationFailure(string.Empty, jsonResponse.Resource?.Message ?? "Unauthorized request to indexer");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Warn(ex, "Unable to connect to indexer");
|
||||||
|
|
||||||
|
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log for more details");
|
||||||
|
}
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Warn(ex, "Unable to connect to indexer");
|
_logger.Warn(ex, "Unable to connect to indexer");
|
||||||
|
|||||||
@@ -182,18 +182,23 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||||
{
|
{
|
||||||
var torrentInfos = new List<TorrentInfo>();
|
var torrentInfos = new List<TorrentInfo>();
|
||||||
|
var indexerHttpResponse = indexerResponse.HttpResponse;
|
||||||
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
|
if (indexerHttpResponse.StatusCode != HttpStatusCode.OK)
|
||||||
{
|
{
|
||||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
|
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerHttpResponse.StatusCode} code from API request");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!indexerResponse.HttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
|
if (!indexerHttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
|
||||||
{
|
{
|
||||||
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from API request, expected {HttpAccept.Json.Value}");
|
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerHttpResponse.Headers.ContentType} from API request, expected {HttpAccept.Json.Value}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var jsonResponse = new HttpResponse<BeyondHDResponse>(indexerResponse.HttpResponse);
|
if (indexerResponse.Content.ContainsIgnoreCase("Invalid API Key"))
|
||||||
|
{
|
||||||
|
throw new IndexerAuthException("API Key invalid or not authorized");
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonResponse = new HttpResponse<BeyondHDResponse>(indexerHttpResponse);
|
||||||
|
|
||||||
foreach (var row in jsonResponse.Resource.Results)
|
foreach (var row in jsonResponse.Resource.Results)
|
||||||
{
|
{
|
||||||
@@ -213,7 +218,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
Grabs = row.Grabs,
|
Grabs = row.Grabs,
|
||||||
Seeders = row.Seeders,
|
Seeders = row.Seeders,
|
||||||
ImdbId = ParseUtil.GetImdbID(row.ImdbId).GetValueOrDefault(),
|
ImdbId = ParseUtil.GetImdbID(row.ImdbId).GetValueOrDefault(),
|
||||||
TmdbId = row.TmdbId.IsNullOrWhiteSpace() ? 0 : ParseUtil.CoerceInt(row.TmdbId.Split("/")[1]),
|
TmdbId = row.TmdbId.IsNullOrWhiteSpace() ? 0 : (int)ParseUtil.CoerceLong(row.TmdbId.Split("/")[1]),
|
||||||
Peers = row.Leechers + row.Seeders,
|
Peers = row.Leechers + row.Seeders,
|
||||||
DownloadVolumeFactor = row.Freeleech || row.Limited ? 0 : row.Promo75 ? 0.25 : row.Promo50 ? 0.5 : row.Promo25 ? 0.75 : 1,
|
DownloadVolumeFactor = row.Freeleech || row.Limited ? 0 : row.Promo75 ? 0.25 : row.Promo50 ? 0.5 : row.Promo25 ? 0.75 : 1,
|
||||||
UploadVolumeFactor = 1,
|
UploadVolumeFactor = 1,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Http;
|
using NzbDrone.Common.Http;
|
||||||
@@ -16,6 +17,7 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
|
|||||||
public override bool SupportsSearch => true;
|
public override bool SupportsSearch => true;
|
||||||
public override int PageSize => 100;
|
public override int PageSize => 100;
|
||||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||||
|
public override TimeSpan RateLimit => TimeSpan.FromSeconds(5);
|
||||||
|
|
||||||
public override string[] IndexerUrls => new string[] { "http://api.broadcasthe.net/" };
|
public override string[] IndexerUrls => new string[] { "http://api.broadcasthe.net/" };
|
||||||
public override string Description => "BroadcasTheNet (BTN) is an invite-only torrent tracker focused on TV shows";
|
public override string Description => "BroadcasTheNet (BTN) is an invite-only torrent tracker focused on TV shows";
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Common.Http;
|
using NzbDrone.Common.Http;
|
||||||
using NzbDrone.Core.Indexers.Exceptions;
|
using NzbDrone.Core.Indexers.Exceptions;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
@@ -25,8 +26,9 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
|
|||||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||||
{
|
{
|
||||||
var results = new List<ReleaseInfo>();
|
var results = new List<ReleaseInfo>();
|
||||||
|
var indexerHttpResponse = indexerResponse.HttpResponse;
|
||||||
|
|
||||||
switch (indexerResponse.HttpResponse.StatusCode)
|
switch (indexerHttpResponse.StatusCode)
|
||||||
{
|
{
|
||||||
case HttpStatusCode.Unauthorized:
|
case HttpStatusCode.Unauthorized:
|
||||||
throw new IndexerAuthException("API Key invalid or not authorized");
|
throw new IndexerAuthException("API Key invalid or not authorized");
|
||||||
@@ -35,25 +37,30 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
|
|||||||
case HttpStatusCode.ServiceUnavailable:
|
case HttpStatusCode.ServiceUnavailable:
|
||||||
throw new RequestLimitReachedException(indexerResponse, "Cannot do more than 150 API requests per hour.");
|
throw new RequestLimitReachedException(indexerResponse, "Cannot do more than 150 API requests per hour.");
|
||||||
default:
|
default:
|
||||||
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
|
if (indexerHttpResponse.StatusCode != HttpStatusCode.OK)
|
||||||
{
|
{
|
||||||
throw new IndexerException(indexerResponse, "Indexer API call returned an unexpected StatusCode [{0}]", indexerResponse.HttpResponse.StatusCode);
|
throw new IndexerException(indexerResponse, "Indexer API call returned an unexpected StatusCode [{0}]", indexerHttpResponse.StatusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (indexerResponse.HttpResponse.Headers.ContentType != null && indexerResponse.HttpResponse.Headers.ContentType.Contains("text/html"))
|
if (indexerHttpResponse.Headers.ContentType != null && indexerHttpResponse.Headers.ContentType.Contains("text/html"))
|
||||||
{
|
{
|
||||||
throw new IndexerException(indexerResponse, "Indexer responded with html content. Site is likely blocked or unavailable.");
|
throw new IndexerException(indexerResponse, "Indexer responded with html content. Site is likely blocked or unavailable.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (indexerResponse.Content.ContainsIgnoreCase("Call Limit Exceeded"))
|
||||||
|
{
|
||||||
|
throw new RequestLimitReachedException(indexerResponse, "Cannot do more than 150 API requests per hour.");
|
||||||
|
}
|
||||||
|
|
||||||
if (indexerResponse.Content == "Query execution was interrupted")
|
if (indexerResponse.Content == "Query execution was interrupted")
|
||||||
{
|
{
|
||||||
throw new IndexerException(indexerResponse, "Indexer API returned an internal server error");
|
throw new IndexerException(indexerResponse, "Indexer API returned an internal server error");
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonRpcResponse<BroadcastheNetTorrents> jsonResponse = new HttpResponse<JsonRpcResponse<BroadcastheNetTorrents>>(indexerResponse.HttpResponse).Resource;
|
JsonRpcResponse<BroadcastheNetTorrents> jsonResponse = new HttpResponse<JsonRpcResponse<BroadcastheNetTorrents>>(indexerHttpResponse).Resource;
|
||||||
|
|
||||||
if (jsonResponse.Error != null || jsonResponse.Result == null)
|
if (jsonResponse.Error != null || jsonResponse.Result == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
|||||||
protected static readonly Regex _LogicFunctionRegex = new Regex(
|
protected static readonly Regex _LogicFunctionRegex = new Regex(
|
||||||
$@"\b({string.Join("|", _SupportedLogicFunctions.Select(Regex.Escape))})(?:\s+(\(?\.[^\)\s]+\)?|""[^""]+"")){{2,}}");
|
$@"\b({string.Join("|", _SupportedLogicFunctions.Select(Regex.Escape))})(?:\s+(\(?\.[^\)\s]+\)?|""[^""]+"")){{2,}}");
|
||||||
|
|
||||||
|
// Matches CSS selectors for the JSON parser
|
||||||
|
protected static readonly Regex _jsonSelectorRegex = new Regex(@"\:(?<filter>.+?)\((?<key>.+?)\)(?=:|\z)", RegexOptions.Compiled);
|
||||||
|
|
||||||
public CardigannSettings Settings { get; set; }
|
public CardigannSettings Settings { get; set; }
|
||||||
|
|
||||||
public CardigannBase(IConfigService configService,
|
public CardigannBase(IConfigService configService,
|
||||||
@@ -234,13 +237,20 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
|||||||
|
|
||||||
if (selector.Selector != null)
|
if (selector.Selector != null)
|
||||||
{
|
{
|
||||||
var selector_Selector = ApplyGoTemplateText(selector.Selector.TrimStart('.'), variables);
|
var selectorSelector = ApplyGoTemplateText(selector.Selector.TrimStart('.'), variables);
|
||||||
var selection = parentObj.SelectToken(selector_Selector);
|
selectorSelector = JsonParseFieldSelector(parentObj, selectorSelector);
|
||||||
|
|
||||||
|
JToken selection = null;
|
||||||
|
if (selectorSelector != null)
|
||||||
|
{
|
||||||
|
selection = parentObj.SelectToken(selectorSelector);
|
||||||
|
}
|
||||||
|
|
||||||
if (selection == null)
|
if (selection == null)
|
||||||
{
|
{
|
||||||
if (required)
|
if (required)
|
||||||
{
|
{
|
||||||
throw new Exception(string.Format("Selector \"{0}\" didn't match {1}", selector_Selector, parentObj.ToString()));
|
throw new Exception(string.Format("Selector \"{0}\" didn't match {1}", selectorSelector, parentObj.ToString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -375,6 +385,22 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ICollection<IndexerCategory> MapTrackerCatDescToNewznab(string trackerCategoryDesc)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(trackerCategoryDesc))
|
||||||
|
{
|
||||||
|
return new List<IndexerCategory>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var cats = _categoryMapping
|
||||||
|
.Where(m =>
|
||||||
|
!string.IsNullOrWhiteSpace(m.TrackerCategoryDesc) &&
|
||||||
|
string.Equals(m.TrackerCategoryDesc, trackerCategoryDesc, StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
.Select(c => NewznabStandardCategory.AllCats.FirstOrDefault(n => n.Id == c.NewzNabCategory) ?? new IndexerCategory { Id = c.NewzNabCategory })
|
||||||
|
.ToList();
|
||||||
|
return cats;
|
||||||
|
}
|
||||||
|
|
||||||
protected delegate string TemplateTextModifier(string str);
|
protected delegate string TemplateTextModifier(string str);
|
||||||
|
|
||||||
protected string ApplyGoTemplateText(string template, Dictionary<string, object> variables = null, TemplateTextModifier modifier = null)
|
protected string ApplyGoTemplateText(string template, Dictionary<string, object> variables = null, TemplateTextModifier modifier = null)
|
||||||
@@ -816,5 +842,108 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
|||||||
{
|
{
|
||||||
return new Uri(currentUrl ?? new Uri(SiteLink), path);
|
return new Uri(currentUrl ?? new Uri(SiteLink), path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected string ResolveSiteLink()
|
||||||
|
{
|
||||||
|
var settingsBaseUrl = Settings?.BaseUrl;
|
||||||
|
var defaultLink = _definition.Links.First();
|
||||||
|
|
||||||
|
if (settingsBaseUrl == null)
|
||||||
|
{
|
||||||
|
return defaultLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_definition?.Legacylinks?.Contains(settingsBaseUrl) ?? false)
|
||||||
|
{
|
||||||
|
_logger.Trace("Changing legacy site link from {0} to {1}", settingsBaseUrl, defaultLink);
|
||||||
|
return defaultLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
return settingsBaseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected JArray JsonParseRowsSelector(JToken parsedJson, string rowSelector)
|
||||||
|
{
|
||||||
|
var selector = rowSelector.Split(':')[0];
|
||||||
|
var rowsObj = parsedJson.SelectToken(selector).Value<JArray>();
|
||||||
|
return new JArray(rowsObj.Where(t =>
|
||||||
|
JsonParseFieldSelector(t.Value<JObject>(), rowSelector.Remove(0, selector.Length)) != null));
|
||||||
|
}
|
||||||
|
|
||||||
|
private string JsonParseFieldSelector(JToken parsedJson, string rowSelector)
|
||||||
|
{
|
||||||
|
var selector = rowSelector.Split(':')[0];
|
||||||
|
JToken parsedObject;
|
||||||
|
if (string.IsNullOrWhiteSpace(selector))
|
||||||
|
{
|
||||||
|
parsedObject = parsedJson;
|
||||||
|
}
|
||||||
|
else if (parsedJson.SelectToken(selector) != null)
|
||||||
|
{
|
||||||
|
parsedObject = parsedJson.SelectToken(selector);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Match match in _jsonSelectorRegex.Matches(rowSelector))
|
||||||
|
{
|
||||||
|
var filter = match.Result("${filter}");
|
||||||
|
var key = match.Result("${key}");
|
||||||
|
Match innerMatch;
|
||||||
|
switch (filter)
|
||||||
|
{
|
||||||
|
case "has":
|
||||||
|
innerMatch = _jsonSelectorRegex.Match(key);
|
||||||
|
if (innerMatch.Success)
|
||||||
|
{
|
||||||
|
if (JsonParseFieldSelector(parsedObject, key) == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (parsedObject.SelectToken(key) == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "not":
|
||||||
|
innerMatch = _jsonSelectorRegex.Match(key);
|
||||||
|
if (innerMatch.Success)
|
||||||
|
{
|
||||||
|
if (JsonParseFieldSelector(parsedObject, key) != null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (parsedObject.SelectToken(key) != null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "contains":
|
||||||
|
if (!parsedObject.ToString().Contains(key))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
_logger.Error(string.Format("CardigannIndexer ({0}): Unsupported selector: {1}", _definition.Id, rowSelector));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return selector;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
|||||||
public int After { get; set; }
|
public int After { get; set; }
|
||||||
public SelectorBlock Dateheaders { get; set; }
|
public SelectorBlock Dateheaders { get; set; }
|
||||||
public SelectorBlock Count { get; set; }
|
public SelectorBlock Count { get; set; }
|
||||||
|
public bool Multiple { get; set; } = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SearchPathBlock : RequestBlock
|
public class SearchPathBlock : RequestBlock
|
||||||
@@ -200,8 +201,6 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
|||||||
public class ResponseBlock
|
public class ResponseBlock
|
||||||
{
|
{
|
||||||
public string Type { get; set; }
|
public string Type { get; set; }
|
||||||
public string Attribute { get; set; }
|
|
||||||
public bool Multiple { get; set; }
|
|
||||||
public string NoResultsMessage { get; set; }
|
public string NoResultsMessage { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
|||||||
{
|
{
|
||||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||||
|
|
||||||
protected override string SiteLink => Settings?.BaseUrl ?? _definition.Links.First();
|
protected override string SiteLink => ResolveSiteLink();
|
||||||
|
|
||||||
public CardigannParser(IConfigService configService,
|
public CardigannParser(IConfigService configService,
|
||||||
CardigannDefinition definition,
|
CardigannDefinition definition,
|
||||||
@@ -83,16 +83,16 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var rowsObj = parsedJson.SelectToken(search.Rows.Selector);
|
var rowsArray = JsonParseRowsSelector(parsedJson, search.Rows.Selector);
|
||||||
if (rowsObj == null)
|
if (rowsArray == null)
|
||||||
{
|
{
|
||||||
throw new IndexerException(indexerResponse, "Error Parsing Rows Selector");
|
throw new IndexerException(indexerResponse, "Error Parsing Rows Selector");
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var row in rowsObj.Value<JArray>())
|
foreach (var row in rowsArray)
|
||||||
{
|
{
|
||||||
var selObj = request.SearchPath.Response.Attribute != null ? row.SelectToken(request.SearchPath.Response.Attribute).Value<JToken>() : row;
|
var selObj = search.Rows.Attribute != null ? row.SelectToken(search.Rows.Attribute).Value<JToken>() : row;
|
||||||
var mulRows = request.SearchPath.Response.Multiple == true ? selObj.Values<JObject>() : new List<JObject> { selObj.Value<JObject>() };
|
var mulRows = search.Rows.Multiple ? selObj.Values<JObject>() : new List<JObject> { selObj.Value<JObject>() };
|
||||||
|
|
||||||
foreach (var mulRow in mulRows)
|
foreach (var mulRow in mulRows)
|
||||||
{
|
{
|
||||||
@@ -464,6 +464,22 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
value = release.Categories.ToString();
|
||||||
|
break;
|
||||||
|
case "categorydesc":
|
||||||
|
var catsDesc = MapTrackerCatDescToNewznab(value);
|
||||||
|
if (catsDesc.Any())
|
||||||
|
{
|
||||||
|
if (release.Categories == null || fieldModifiers.Contains("noappend"))
|
||||||
|
{
|
||||||
|
release.Categories = catsDesc;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
release.Categories = release.Categories.Union(catsDesc).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
value = release.Categories.ToString();
|
value = release.Categories.ToString();
|
||||||
break;
|
break;
|
||||||
case "size":
|
case "size":
|
||||||
@@ -545,6 +561,13 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
|||||||
release.TvRageId = (int)ParseUtil.CoerceLong(rageID);
|
release.TvRageId = (int)ParseUtil.CoerceLong(rageID);
|
||||||
value = release.TvRageId.ToString();
|
value = release.TvRageId.ToString();
|
||||||
break;
|
break;
|
||||||
|
case "traktid":
|
||||||
|
var traktIDRegEx = new Regex(@"(\d+)", RegexOptions.Compiled);
|
||||||
|
var traktIDMatch = traktIDRegEx.Match(value);
|
||||||
|
var traktID = traktIDMatch.Groups[1].Value;
|
||||||
|
release.TraktId = (int)ParseUtil.CoerceLong(traktID);
|
||||||
|
value = release.TraktId.ToString();
|
||||||
|
break;
|
||||||
case "tvdbid":
|
case "tvdbid":
|
||||||
var tvdbIdRegEx = new Regex(@"(\d+)", RegexOptions.Compiled);
|
var tvdbIdRegEx = new Regex(@"(\d+)", RegexOptions.Compiled);
|
||||||
var tvdbIdMatch = tvdbIdRegEx.Match(value);
|
var tvdbIdMatch = tvdbIdRegEx.Match(value);
|
||||||
@@ -561,6 +584,14 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
|||||||
|
|
||||||
value = release.PosterUrl;
|
value = release.PosterUrl;
|
||||||
break;
|
break;
|
||||||
|
case "genre":
|
||||||
|
release.Genres = release.Genres.Union(value.Split(',')).ToList();
|
||||||
|
value = release.Genres.ToString();
|
||||||
|
break;
|
||||||
|
case "year":
|
||||||
|
release.Year = ParseUtil.CoerceInt(value);
|
||||||
|
value = release.Year.ToString();
|
||||||
|
break;
|
||||||
case "author":
|
case "author":
|
||||||
release.Author = value;
|
release.Author = value;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -8,13 +8,6 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
|||||||
public Dictionary<string, object> Variables { get; private set; }
|
public Dictionary<string, object> Variables { get; private set; }
|
||||||
public SearchPathBlock SearchPath { get; private set; }
|
public SearchPathBlock SearchPath { get; private set; }
|
||||||
|
|
||||||
public CardigannRequest(string url, HttpAccept httpAccept, Dictionary<string, object> variables, SearchPathBlock searchPath)
|
|
||||||
: base(url, httpAccept)
|
|
||||||
{
|
|
||||||
Variables = variables;
|
|
||||||
SearchPath = searchPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CardigannRequest(HttpRequest httpRequest, Dictionary<string, object> variables, SearchPathBlock searchPath)
|
public CardigannRequest(HttpRequest httpRequest, Dictionary<string, object> variables, SearchPathBlock searchPath)
|
||||||
: base(httpRequest)
|
: base(httpRequest)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
|||||||
public IDictionary<string, string> Cookies { get; set; }
|
public IDictionary<string, string> Cookies { get; set; }
|
||||||
protected HttpResponse landingResult;
|
protected HttpResponse landingResult;
|
||||||
protected IHtmlDocument landingResultDocument;
|
protected IHtmlDocument landingResultDocument;
|
||||||
protected override string SiteLink => Settings?.BaseUrl ?? _definition.Links.First();
|
protected override string SiteLink => ResolveSiteLink();
|
||||||
|
|
||||||
public CardigannRequestGenerator(IConfigService configService,
|
public CardigannRequestGenerator(IConfigService configService,
|
||||||
CardigannDefinition definition,
|
CardigannDefinition definition,
|
||||||
@@ -41,7 +41,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
|||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||||
{
|
{
|
||||||
_logger.Trace("Getting search");
|
_logger.Trace("Getting Movie search");
|
||||||
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
var pageableRequests = new IndexerPageableRequestChain();
|
||||||
|
|
||||||
@@ -61,6 +61,8 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
|||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||||
{
|
{
|
||||||
|
_logger.Trace("Getting Music search");
|
||||||
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
var pageableRequests = new IndexerPageableRequestChain();
|
||||||
|
|
||||||
var variables = GetQueryVariableDefaults(searchCriteria);
|
var variables = GetQueryVariableDefaults(searchCriteria);
|
||||||
@@ -77,6 +79,8 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
|||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||||
{
|
{
|
||||||
|
_logger.Trace("Getting TV search");
|
||||||
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
var pageableRequests = new IndexerPageableRequestChain();
|
||||||
|
|
||||||
var variables = GetQueryVariableDefaults(searchCriteria);
|
var variables = GetQueryVariableDefaults(searchCriteria);
|
||||||
@@ -99,6 +103,8 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
|||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
||||||
{
|
{
|
||||||
|
_logger.Trace("Getting Book search");
|
||||||
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
var pageableRequests = new IndexerPageableRequestChain();
|
||||||
|
|
||||||
var variables = GetQueryVariableDefaults(searchCriteria);
|
var variables = GetQueryVariableDefaults(searchCriteria);
|
||||||
@@ -113,6 +119,8 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
|||||||
|
|
||||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
||||||
{
|
{
|
||||||
|
_logger.Trace("Getting Basic search");
|
||||||
|
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
var pageableRequests = new IndexerPageableRequestChain();
|
||||||
|
|
||||||
var variables = GetQueryVariableDefaults(searchCriteria);
|
var variables = GetQueryVariableDefaults(searchCriteria);
|
||||||
@@ -647,10 +655,13 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
|||||||
|
|
||||||
protected string GetRedirectDomainHint(string requestUrl, string redirectUrl)
|
protected string GetRedirectDomainHint(string requestUrl, string redirectUrl)
|
||||||
{
|
{
|
||||||
if (requestUrl.StartsWith(SiteLink) && !redirectUrl.StartsWith(SiteLink))
|
var siteLinkUri = new HttpUri(SiteLink);
|
||||||
|
var requestUri = new HttpUri(requestUrl);
|
||||||
|
var redirectUri = new HttpUri(redirectUrl);
|
||||||
|
|
||||||
|
if (requestUri.Host.StartsWith(siteLinkUri.Host) && !redirectUri.Host.StartsWith(siteLinkUri.Host))
|
||||||
{
|
{
|
||||||
var uri = new HttpUri(redirectUrl);
|
return redirectUri.Scheme + "://" + redirectUri.Host + "/";
|
||||||
return uri.Scheme + "://" + uri.Host + "/";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ using NzbDrone.Core.Validation;
|
|||||||
|
|
||||||
namespace NzbDrone.Core.Indexers.Definitions
|
namespace NzbDrone.Core.Indexers.Definitions
|
||||||
{
|
{
|
||||||
|
[Obsolete("Moved to YML")]
|
||||||
public class DanishBytes : TorrentIndexerBase<DanishBytesSettings>
|
public class DanishBytes : TorrentIndexerBase<DanishBytesSettings>
|
||||||
{
|
{
|
||||||
public override string Name => "DanishBytes";
|
public override string Name => "DanishBytes";
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Indexers.Gazelle
|
namespace NzbDrone.Core.Indexers.Gazelle
|
||||||
{
|
{
|
||||||
@@ -59,6 +60,7 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
|||||||
public int TotalSeeders { get; set; }
|
public int TotalSeeders { get; set; }
|
||||||
public int TotalSnatched { get; set; }
|
public int TotalSnatched { get; set; }
|
||||||
public long MaxSize { get; set; }
|
public long MaxSize { get; set; }
|
||||||
|
[JsonConverter(typeof(GazelleTimestampConverter))]
|
||||||
public long GroupTime { get; set; }
|
public long GroupTime { get; set; }
|
||||||
public List<GazelleTorrent> Torrents { get; set; }
|
public List<GazelleTorrent> Torrents { get; set; }
|
||||||
public bool IsFreeLeech { get; set; }
|
public bool IsFreeLeech { get; set; }
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
|||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(searchString))
|
if (!string.IsNullOrWhiteSpace(searchString))
|
||||||
{
|
{
|
||||||
parameters += string.Format("&searchstr={0}", searchString);
|
parameters += string.Format("&searchstr={0}", searchString.Replace(".", " "));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (categories != null)
|
if (categories != null)
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Indexers.Gazelle
|
||||||
|
{
|
||||||
|
public class GazelleTimestampConverter : JsonConverter
|
||||||
|
{
|
||||||
|
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
writer.WriteNull();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteValue(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
if (objectType == typeof(long))
|
||||||
|
{
|
||||||
|
return Convert.ToInt64(reader.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (objectType == typeof(string))
|
||||||
|
{
|
||||||
|
var date = DateTimeOffset.Parse(reader.Value.ToString());
|
||||||
|
return date.ToUnixTimeSeconds();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new JsonSerializationException("Can't convert type " + existingValue.GetType().FullName + " to timestamp");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanConvert(Type objectType)
|
||||||
|
{
|
||||||
|
return objectType == typeof(long) || objectType == typeof(string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,29 +23,29 @@ namespace NzbDrone.Core.Indexers.HDBits
|
|||||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||||
{
|
{
|
||||||
var torrentInfos = new List<ReleaseInfo>();
|
var torrentInfos = new List<ReleaseInfo>();
|
||||||
|
var indexerHttpResponse = indexerResponse.HttpResponse;
|
||||||
|
|
||||||
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
|
if (indexerHttpResponse.StatusCode == HttpStatusCode.Forbidden)
|
||||||
{
|
{
|
||||||
throw new IndexerException(indexerResponse,
|
throw new RequestLimitReachedException(indexerResponse, "HDBits Query Limit Reached. Please try again later.");
|
||||||
"Unexpected response status {0} code from API request",
|
}
|
||||||
indexerResponse.HttpResponse.StatusCode);
|
|
||||||
|
if (indexerHttpResponse.StatusCode != HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
throw new IndexerException(indexerResponse, "Unexpected response status {0} code from API request", indexerHttpResponse.StatusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
var jsonResponse = JsonConvert.DeserializeObject<HDBitsResponse>(indexerResponse.Content);
|
var jsonResponse = JsonConvert.DeserializeObject<HDBitsResponse>(indexerResponse.Content);
|
||||||
|
|
||||||
if (jsonResponse.Status != StatusCode.Success)
|
if (jsonResponse.Status != StatusCode.Success)
|
||||||
{
|
{
|
||||||
throw new IndexerException(indexerResponse,
|
throw new IndexerException(indexerResponse, "HDBits API request returned status code {0}: {1}", jsonResponse.Status, jsonResponse.Message ?? string.Empty);
|
||||||
"HDBits API request returned status code {0}: {1}",
|
|
||||||
jsonResponse.Status,
|
|
||||||
jsonResponse.Message ?? string.Empty);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var responseData = jsonResponse.Data as JArray;
|
var responseData = jsonResponse.Data as JArray;
|
||||||
if (responseData == null)
|
if (responseData == null)
|
||||||
{
|
{
|
||||||
throw new IndexerException(indexerResponse,
|
throw new IndexerException(indexerResponse, "Indexer API call response missing result data");
|
||||||
"Indexer API call response missing result data");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var queryResults = responseData.ToObject<TorrentQueryResponse[]>();
|
var queryResults = responseData.ToObject<TorrentQueryResponse[]>();
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
queryCollection.Add("options", "0");
|
queryCollection.Add("options", "0");
|
||||||
queryCollection.Add("search", term);
|
queryCollection.Add("search", term.Replace(".", " "));
|
||||||
}
|
}
|
||||||
|
|
||||||
searchUrl += queryCollection.GetQueryString();
|
searchUrl += queryCollection.GetQueryString();
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
{
|
{
|
||||||
public override string Name => "HD-Torrents";
|
public override string Name => "HD-Torrents";
|
||||||
|
|
||||||
public override string[] IndexerUrls => new string[] { "https://hdts.ru/" };
|
public override string[] IndexerUrls => new string[] { "https://hdts.ru/", "https://hd-torrents.org/" };
|
||||||
public override string Description => "HD-Torrents is a private torrent website with HD torrents and strict rules on their content.";
|
public override string Description => "HD-Torrents is a private torrent website with HD torrents and strict rules on their content.";
|
||||||
private string LoginUrl => Settings.BaseUrl + "login.php";
|
private string LoginUrl => Settings.BaseUrl + "login.php";
|
||||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||||
|
|||||||
@@ -170,16 +170,21 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
|
|
||||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null)
|
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null)
|
||||||
{
|
{
|
||||||
var searchUrl = Settings.BaseUrl + "browse.php";
|
var searchUrl = Settings.BaseUrl + "browse.php?";
|
||||||
|
|
||||||
if (term.IsNotNullOrWhiteSpace())
|
if (term.IsNotNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
searchUrl += string.Format("?do=search&keywords={0}&search_type=t_name&category=0&include_dead_torrents=no", WebUtility.UrlEncode(term));
|
searchUrl += string.Format("do=search&keywords={0}&search_type=t_name&category=0&include_dead_torrents=no", WebUtility.UrlEncode(term));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (categories != null && categories.Length > 0)
|
if (categories != null && categories.Length > 0)
|
||||||
{
|
{
|
||||||
searchUrl += "&selectedcats2=" + string.Join(",", Capabilities.Categories.MapTorznabCapsToTrackers(categories));
|
if (term.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
searchUrl += "&";
|
||||||
|
}
|
||||||
|
|
||||||
|
searchUrl += "selectedcats2=" + string.Join(",", Capabilities.Categories.MapTorznabCapsToTrackers(categories));
|
||||||
}
|
}
|
||||||
|
|
||||||
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
|
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
|
||||||
|
|||||||
@@ -150,14 +150,14 @@ public class MoreThanTVRequestGenerator : IIndexerRequestGenerator
|
|||||||
qc.Add("filter_cat[2]", "1"); // SD Movies
|
qc.Add("filter_cat[2]", "1"); // SD Movies
|
||||||
break;
|
break;
|
||||||
case TvSearchCriteria:
|
case TvSearchCriteria:
|
||||||
qc.Add("filter_cat[3]", "1"); // HD EPISODE
|
qc.Add("filter_cat[3]", "1"); // HD Episode
|
||||||
qc.Add("filter_cat[4]", "1"); // SD Episode
|
qc.Add("filter_cat[4]", "1"); // SD Episode
|
||||||
qc.Add("filter_cat[5]", "1"); // HD Season
|
qc.Add("filter_cat[5]", "1"); // HD Season
|
||||||
qc.Add("filter_cat[6]", "1"); // SD Season
|
qc.Add("filter_cat[6]", "1"); // SD Season
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $"{Settings.BaseUrl}torrents.php?{qc.GetQueryString()}";
|
return $"{Settings.BaseUrl}torrents/browse?{qc.GetQueryString()}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetSearchString(string input)
|
private string GetSearchString(string input)
|
||||||
@@ -188,28 +188,25 @@ public class MoreThanTVParser : IParseIndexerResponse
|
|||||||
foreach (var torrent in torrents)
|
foreach (var torrent in torrents)
|
||||||
{
|
{
|
||||||
// Parse required data
|
// Parse required data
|
||||||
var torrentGroup = torrent.QuerySelectorAll("table a[href^=\"/torrents.php?action=download\"]");
|
var downloadAnchor = torrent.QuerySelector("span a[href^=\"/torrents.php?action=download\"]");
|
||||||
foreach (var downloadAnchor in torrentGroup)
|
var title = downloadAnchor.ParentElement.ParentElement.ParentElement.QuerySelector("a[class=\"overlay_torrent\"]").TextContent.Trim();
|
||||||
|
title = CleanUpTitle(title);
|
||||||
|
|
||||||
|
var category = torrent.QuerySelector(".cats_col div").GetAttribute("title");
|
||||||
|
|
||||||
|
// default to Other
|
||||||
|
var indexerCategory = NewznabStandardCategory.Other;
|
||||||
|
|
||||||
|
if (movies.Any(category.Contains))
|
||||||
{
|
{
|
||||||
var title = downloadAnchor.ParentElement.ParentElement.ParentElement.TextContent.Trim();
|
indexerCategory = NewznabStandardCategory.Movies;
|
||||||
title = CleanUpTitle(title);
|
|
||||||
|
|
||||||
var category = torrent.QuerySelector(".cats_col div").GetAttribute("title");
|
|
||||||
|
|
||||||
// default to Other
|
|
||||||
var indexerCategory = NewznabStandardCategory.Other;
|
|
||||||
|
|
||||||
if (movies.Any(category.Contains))
|
|
||||||
{
|
|
||||||
indexerCategory = NewznabStandardCategory.Movies;
|
|
||||||
}
|
|
||||||
else if (tv.Any(category.Contains))
|
|
||||||
{
|
|
||||||
indexerCategory = NewznabStandardCategory.TV;
|
|
||||||
}
|
|
||||||
|
|
||||||
releases.Add(GetReleaseInfo(torrent, downloadAnchor, title, indexerCategory));
|
|
||||||
}
|
}
|
||||||
|
else if (tv.Any(category.Contains))
|
||||||
|
{
|
||||||
|
indexerCategory = NewznabStandardCategory.TV;
|
||||||
|
}
|
||||||
|
|
||||||
|
releases.Add(GetReleaseInfo(torrent, downloadAnchor, title, indexerCategory));
|
||||||
}
|
}
|
||||||
|
|
||||||
return releases;
|
return releases;
|
||||||
@@ -231,7 +228,7 @@ public class MoreThanTVParser : IParseIndexerResponse
|
|||||||
private ReleaseInfo GetReleaseInfo(IElement row, IElement downloadAnchor, string title, IndexerCategory category)
|
private ReleaseInfo GetReleaseInfo(IElement row, IElement downloadAnchor, string title, IndexerCategory category)
|
||||||
{
|
{
|
||||||
// count from bottom
|
// count from bottom
|
||||||
const int FILES_COL = 8;
|
const int FILES_COL = 7;
|
||||||
/*const int COMMENTS_COL = 7;*/
|
/*const int COMMENTS_COL = 7;*/
|
||||||
const int DATE_COL = 6;
|
const int DATE_COL = 6;
|
||||||
const int FILESIZE_COL = 5;
|
const int FILESIZE_COL = 5;
|
||||||
|
|||||||
@@ -25,26 +25,32 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
|||||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||||
{
|
{
|
||||||
var torrentInfos = new List<ReleaseInfo>();
|
var torrentInfos = new List<ReleaseInfo>();
|
||||||
|
var indexerHttpResponse = indexerResponse.HttpResponse;
|
||||||
|
|
||||||
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
|
if (indexerHttpResponse.StatusCode != HttpStatusCode.OK)
|
||||||
{
|
{
|
||||||
// Remove cookie cache
|
// Remove cookie cache
|
||||||
if (indexerResponse.HttpResponse.HasHttpRedirect && indexerResponse.HttpResponse.RedirectUrl
|
if (indexerHttpResponse.HasHttpRedirect && indexerHttpResponse.RedirectUrl
|
||||||
.ContainsIgnoreCase("login.php"))
|
.ContainsIgnoreCase("login.php"))
|
||||||
{
|
{
|
||||||
CookiesUpdater(null, null);
|
CookiesUpdater(null, null);
|
||||||
throw new IndexerException(indexerResponse, "We are being redirected to the PTP login page. Most likely your session expired or was killed. Try testing the indexer in the settings.");
|
throw new IndexerAuthException("We are being redirected to the PTP login page. Most likely your session expired or was killed. Try testing the indexer in the settings.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indexerHttpResponse.StatusCode == HttpStatusCode.Forbidden)
|
||||||
|
{
|
||||||
|
throw new RequestLimitReachedException(indexerResponse, "PTP Query Limit Reached. Please try again later.");
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
|
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (indexerResponse.HttpResponse.Headers.ContentType != HttpAccept.Json.Value)
|
if (indexerHttpResponse.Headers.ContentType != HttpAccept.Json.Value)
|
||||||
{
|
{
|
||||||
if (indexerResponse.HttpResponse.Request.Url.Path.ContainsIgnoreCase("login.php"))
|
if (indexerHttpResponse.Request.Url.Path.ContainsIgnoreCase("login.php"))
|
||||||
{
|
{
|
||||||
CookiesUpdater(null, null);
|
CookiesUpdater(null, null);
|
||||||
throw new IndexerException(indexerResponse, "We are currently on the login page. Most likely your session expired or was killed. Try testing the indexer in the settings.");
|
throw new IndexerAuthException("We are currently on the login page. Most likely your session expired or was killed. Try testing the indexer in the settings.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove cookie cache
|
// Remove cookie cache
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
public override string[] IndexerUrls => new string[] { "https://pornolab.net/" };
|
public override string[] IndexerUrls => new string[] { "https://pornolab.net/" };
|
||||||
private string LoginUrl => Settings.BaseUrl + "forum/login.php";
|
private string LoginUrl => Settings.BaseUrl + "forum/login.php";
|
||||||
public override string Description => "PornoLab is a Semi-Private Russian site for Adult content";
|
public override string Description => "PornoLab is a Semi-Private Russian site for Adult content";
|
||||||
public override string Language => "ru-ru";
|
public override string Language => "ru-RU";
|
||||||
public override Encoding Encoding => Encoding.GetEncoding("windows-1251");
|
public override Encoding Encoding => Encoding.GetEncoding("windows-1251");
|
||||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||||
public override IndexerPrivacy Privacy => IndexerPrivacy.SemiPrivate;
|
public override IndexerPrivacy Privacy => IndexerPrivacy.SemiPrivate;
|
||||||
|
|||||||
@@ -73,8 +73,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.PC, "Applications");
|
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.PC, "Applications");
|
||||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.BooksEBook, "E-Books");
|
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.BooksEBook, "E-Books");
|
||||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.AudioAudiobook, "Audiobooks");
|
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.AudioAudiobook, "Audiobooks");
|
||||||
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.MoviesOther, "E-Learning Videos");
|
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.Other, "E-Learning Videos");
|
||||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.TVOther, "Comedy");
|
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.Other, "Comedy");
|
||||||
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.BooksComics, "Comics");
|
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.BooksComics, "Comics");
|
||||||
|
|
||||||
return caps;
|
return caps;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,7 @@ using NzbDrone.Core.Messaging.Events;
|
|||||||
|
|
||||||
namespace NzbDrone.Core.Indexers.Definitions.Xthor
|
namespace NzbDrone.Core.Indexers.Definitions.Xthor
|
||||||
{
|
{
|
||||||
|
[Obsolete("Moved to YML for Cardigann v5")]
|
||||||
public class Xthor : TorrentIndexerBase<XthorSettings>
|
public class Xthor : TorrentIndexerBase<XthorSettings>
|
||||||
{
|
{
|
||||||
public override string Name => "Xthor";
|
public override string Name => "Xthor";
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ namespace NzbDrone.Core.Indexers
|
|||||||
{
|
{
|
||||||
private static readonly IndexerCommonSettingsValidator Validator = new IndexerCommonSettingsValidator();
|
private static readonly IndexerCommonSettingsValidator Validator = new IndexerCommonSettingsValidator();
|
||||||
|
|
||||||
[FieldDefinition(1, Type = FieldType.Number, Label = "Query Limit", HelpText = "The number of queries per day Prowlarr will allow to the site", Advanced = true)]
|
[FieldDefinition(1, Type = FieldType.Number, Label = "Query Limit", HelpText = "The number of queries within a rolling 24 hour period Prowlarr will allow to the site", Advanced = true)]
|
||||||
public int? QueryLimit { get; set; }
|
public int? QueryLimit { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(2, Type = FieldType.Number, Label = "Grab Limit", HelpText = "The number of grabs per day Prowlarr will allow to the site", Advanced = true)]
|
[FieldDefinition(2, Type = FieldType.Number, Label = "Grab Limit", HelpText = "The number of grabs within a rolling 24 hour period Prowlarr will allow to the site", Advanced = true)]
|
||||||
public int? GrabLimit { get; set; }
|
public int? GrabLimit { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,8 @@
|
|||||||
"About": "نبدة عن",
|
"About": "نبدة عن",
|
||||||
"AcceptConfirmationModal": "نموذج قبول التأكيد",
|
"AcceptConfirmationModal": "نموذج قبول التأكيد",
|
||||||
"Actions": "أجراءات",
|
"Actions": "أجراءات",
|
||||||
"MinutesHundredTwenty": "120 دقيقة: {0}",
|
|
||||||
"MonoVersion": "نسخة أحادية",
|
|
||||||
"MoreInfo": "مزيد من المعلومات",
|
"MoreInfo": "مزيد من المعلومات",
|
||||||
"BranchUpdateMechanism": "يستخدم الفرع بواسطة آلية التحديث الخارجية",
|
"BranchUpdateMechanism": "يستخدم الفرع بواسطة آلية التحديث الخارجية",
|
||||||
"CloneIndexer": "مفهرس استنساخ",
|
|
||||||
"Added": "مضاف",
|
"Added": "مضاف",
|
||||||
"AddIndexer": "أضف مفهرس",
|
"AddIndexer": "أضف مفهرس",
|
||||||
"AddingTag": "مضيفا العلامة",
|
"AddingTag": "مضيفا العلامة",
|
||||||
@@ -18,7 +15,6 @@
|
|||||||
"AreYouSureYouWantToResetYourAPIKey": "هل أنت متأكد أنك تريد إعادة تعيين مفتاح API الخاص بك؟",
|
"AreYouSureYouWantToResetYourAPIKey": "هل أنت متأكد أنك تريد إعادة تعيين مفتاح API الخاص بك؟",
|
||||||
"Authentication": "المصادقة",
|
"Authentication": "المصادقة",
|
||||||
"BackupIntervalHelpText": "الفاصل الزمني بين النسخ الاحتياطية التلقائية",
|
"BackupIntervalHelpText": "الفاصل الزمني بين النسخ الاحتياطية التلقائية",
|
||||||
"Languages": "اللغات",
|
|
||||||
"BackupNow": "اعمل نسخة احتياطية الان",
|
"BackupNow": "اعمل نسخة احتياطية الان",
|
||||||
"BackupRetentionHelpText": "سيتم تنظيف النسخ الاحتياطية التلقائية الأقدم من فترة الاحتفاظ تلقائيًا",
|
"BackupRetentionHelpText": "سيتم تنظيف النسخ الاحتياطية التلقائية الأقدم من فترة الاحتفاظ تلقائيًا",
|
||||||
"Backups": "النسخ الاحتياطية",
|
"Backups": "النسخ الاحتياطية",
|
||||||
@@ -32,18 +28,11 @@
|
|||||||
"DeleteApplicationMessageText": "هل تريد بالتأكيد حذف الإشعار \"{0}\"؟",
|
"DeleteApplicationMessageText": "هل تريد بالتأكيد حذف الإشعار \"{0}\"؟",
|
||||||
"DeleteDownloadClient": "حذف Download Client",
|
"DeleteDownloadClient": "حذف Download Client",
|
||||||
"DeleteDownloadClientMessageText": "هل أنت متأكد أنك تريد حذف برنامج التنزيل \"{0}\"؟",
|
"DeleteDownloadClientMessageText": "هل أنت متأكد أنك تريد حذف برنامج التنزيل \"{0}\"؟",
|
||||||
"DeleteIndexer": "حذف المفهرس",
|
|
||||||
"DeleteIndexerMessageText": "هل أنت متأكد أنك تريد حذف المفهرس \"{0}\"؟",
|
|
||||||
"EnableMediaInfoHelpText": "استخراج معلومات الفيديو مثل الدقة ووقت التشغيل ومعلومات الترميز من الملفات. يتطلب هذا من Radarr قراءة أجزاء من الملف والتي قد تسبب نشاطًا كبيرًا على القرص أو الشبكة أثناء عمليات الفحص.",
|
|
||||||
"Filename": "اسم الملف",
|
"Filename": "اسم الملف",
|
||||||
"HomePage": "الصفحة الرئيسية",
|
"HomePage": "الصفحة الرئيسية",
|
||||||
"IndexerProxyStatusCheckSingleClientMessage": "المفهرسات غير متاحة بسبب الإخفاقات: {0}",
|
"IndexerProxyStatusCheckSingleClientMessage": "المفهرسات غير متاحة بسبب الإخفاقات: {0}",
|
||||||
"Language": "لغة",
|
"Language": "لغة",
|
||||||
"MinutesNinety": "90 دقيقة: {0}",
|
|
||||||
"Usenet": "يوزنت",
|
"Usenet": "يوزنت",
|
||||||
"MinutesSixty": "60 دقيقة: {0}",
|
|
||||||
"MonoVersionCheckUpgradeRecommendedMessage": "يتم دعم الإصدار Mono المثبت حاليًا {0} ولكن يوصى بالترقية إلى {1}.",
|
|
||||||
"MovieDetailsNextMovie": "تفاصيل الفيلم: الفيلم القادم",
|
|
||||||
"MovieIndexScrollTop": "فهرس الفيلم: قم بالتمرير لأعلى",
|
"MovieIndexScrollTop": "فهرس الفيلم: قم بالتمرير لأعلى",
|
||||||
"New": "جديد",
|
"New": "جديد",
|
||||||
"NoLinks": "لا روابط",
|
"NoLinks": "لا روابط",
|
||||||
@@ -66,7 +55,6 @@
|
|||||||
"ProxyPasswordHelpText": "ما عليك سوى إدخال اسم مستخدم وكلمة مرور إذا كان أحدهما مطلوبًا. اتركها فارغة وإلا.",
|
"ProxyPasswordHelpText": "ما عليك سوى إدخال اسم مستخدم وكلمة مرور إذا كان أحدهما مطلوبًا. اتركها فارغة وإلا.",
|
||||||
"ProxyUsernameHelpText": "ما عليك سوى إدخال اسم مستخدم وكلمة مرور إذا كان أحدهما مطلوبًا. اتركها فارغة وإلا.",
|
"ProxyUsernameHelpText": "ما عليك سوى إدخال اسم مستخدم وكلمة مرور إذا كان أحدهما مطلوبًا. اتركها فارغة وإلا.",
|
||||||
"PtpOldSettingsCheckMessage": "مفهرسات PassThePopcorn التالية بها إعدادات مهملة ويجب تحديثها: {0}",
|
"PtpOldSettingsCheckMessage": "مفهرسات PassThePopcorn التالية بها إعدادات مهملة ويجب تحديثها: {0}",
|
||||||
"QualitySettings": "إعدادات الجودة",
|
|
||||||
"Queue": "طابور",
|
"Queue": "طابور",
|
||||||
"ReadTheWikiForMoreInformation": "اقرأ Wiki لمزيد من المعلومات",
|
"ReadTheWikiForMoreInformation": "اقرأ Wiki لمزيد من المعلومات",
|
||||||
"Reddit": "رديت",
|
"Reddit": "رديت",
|
||||||
@@ -83,19 +71,12 @@
|
|||||||
"UnableToAddANewDownloadClientPleaseTryAgain": "غير قادر على إضافة عميل تنزيل جديد ، يرجى المحاولة مرة أخرى.",
|
"UnableToAddANewDownloadClientPleaseTryAgain": "غير قادر على إضافة عميل تنزيل جديد ، يرجى المحاولة مرة أخرى.",
|
||||||
"UnableToAddANewIndexerPleaseTryAgain": "غير قادر على إضافة مفهرس جديد ، يرجى المحاولة مرة أخرى.",
|
"UnableToAddANewIndexerPleaseTryAgain": "غير قادر على إضافة مفهرس جديد ، يرجى المحاولة مرة أخرى.",
|
||||||
"UnableToLoadBackups": "تعذر تحميل النسخ الاحتياطية",
|
"UnableToLoadBackups": "تعذر تحميل النسخ الاحتياطية",
|
||||||
"UnableToLoadIndexers": "تعذر تحميل المفهرسات",
|
|
||||||
"UnableToLoadQualityDefinitions": "تعذر تحميل تعريفات الجودة",
|
|
||||||
"UnsavedChanges": "التغييرات غير المحفوظة",
|
"UnsavedChanges": "التغييرات غير المحفوظة",
|
||||||
"UpdateCheckUINotWritableMessage": "لا يمكن تثبيت التحديث لأن مجلد واجهة المستخدم '{0}' غير قابل للكتابة بواسطة المستخدم '{1}'",
|
"UpdateCheckUINotWritableMessage": "لا يمكن تثبيت التحديث لأن مجلد واجهة المستخدم '{0}' غير قابل للكتابة بواسطة المستخدم '{1}'",
|
||||||
"UpdateScriptPathHelpText": "المسار إلى برنامج نصي مخصص يأخذ حزمة تحديث مستخرجة ويتعامل مع ما تبقى من عملية التحديث",
|
"UpdateScriptPathHelpText": "المسار إلى برنامج نصي مخصص يأخذ حزمة تحديث مستخرجة ويتعامل مع ما تبقى من عملية التحديث",
|
||||||
"Username": "اسم المستخدم",
|
"Username": "اسم المستخدم",
|
||||||
"Warn": "حذر",
|
"Warn": "حذر",
|
||||||
"Yesterday": "في الامس",
|
"Yesterday": "في الامس",
|
||||||
"EnableColorImpairedMode": "تفعيل وضع ضعف الألوان",
|
|
||||||
"EnableColorImpairedModeHelpText": "تم تغيير النمط للسماح للمستخدمين الذين يعانون من ضعف الألوان بتمييز المعلومات المشفرة بالألوان بشكل أفضل",
|
|
||||||
"EnableCompletedDownloadHandlingHelpText": "استيراد التنزيلات المكتملة تلقائيًا من عميل التنزيل",
|
|
||||||
"EnabledHelpText": "قم بتمكين هذه القائمة للاستخدام في Radarr",
|
|
||||||
"EnableHelpText": "تفعيل إنشاء ملف البيانات الوصفية لنوع البيانات الوصفية هذا",
|
|
||||||
"EnableInteractiveSearch": "تمكين البحث التفاعلي",
|
"EnableInteractiveSearch": "تمكين البحث التفاعلي",
|
||||||
"Source": "مصدر",
|
"Source": "مصدر",
|
||||||
"SSLCertPassword": "كلمة مرور شهادة SSL",
|
"SSLCertPassword": "كلمة مرور شهادة SSL",
|
||||||
@@ -107,19 +88,15 @@
|
|||||||
"CouldNotConnectSignalR": "تعذر الاتصال بـ SignalR ، لن يتم تحديث واجهة المستخدم",
|
"CouldNotConnectSignalR": "تعذر الاتصال بـ SignalR ، لن يتم تحديث واجهة المستخدم",
|
||||||
"Dates": "تواريخ",
|
"Dates": "تواريخ",
|
||||||
"DBMigration": "ترحيل DB",
|
"DBMigration": "ترحيل DB",
|
||||||
"DelayProfile": "ملف التأخير",
|
|
||||||
"Delete": "حذف",
|
"Delete": "حذف",
|
||||||
"Details": "تفاصيل",
|
"Details": "تفاصيل",
|
||||||
"Donations": "التبرعات",
|
"Donations": "التبرعات",
|
||||||
"DownloadClient": "تحميل العميل",
|
"DownloadClient": "تحميل العميل",
|
||||||
"EnableAutoHelpText": "في حالة التمكين ، ستتم إضافة الأفلام تلقائيًا إلى Radarr من هذه القائمة",
|
|
||||||
"IndexerStatusCheckAllClientMessage": "جميع المفهرسات غير متوفرة بسبب الفشل",
|
"IndexerStatusCheckAllClientMessage": "جميع المفهرسات غير متوفرة بسبب الفشل",
|
||||||
"IndexerStatusCheckSingleClientMessage": "المفهرسات غير متاحة بسبب الإخفاقات: {0}",
|
"IndexerStatusCheckSingleClientMessage": "المفهرسات غير متاحة بسبب الإخفاقات: {0}",
|
||||||
"Info": "معلومات",
|
"Info": "معلومات",
|
||||||
"Interval": "فترة",
|
"Interval": "فترة",
|
||||||
"Manual": "كتيب",
|
"Manual": "كتيب",
|
||||||
"MaximumLimits": "الحدود القصوى",
|
|
||||||
"NoLimitForAnyRuntime": "لا يوجد حد لأي وقت تشغيل",
|
|
||||||
"General": "جنرال لواء",
|
"General": "جنرال لواء",
|
||||||
"GeneralSettings": "الاعدادات العامة",
|
"GeneralSettings": "الاعدادات العامة",
|
||||||
"Grabbed": "اقتطف",
|
"Grabbed": "اقتطف",
|
||||||
@@ -168,7 +145,6 @@
|
|||||||
"IndexerProxyStatusCheckAllClientMessage": "جميع المفهرسات غير متوفرة بسبب الفشل",
|
"IndexerProxyStatusCheckAllClientMessage": "جميع المفهرسات غير متوفرة بسبب الفشل",
|
||||||
"NoChange": "لا تغيير",
|
"NoChange": "لا تغيير",
|
||||||
"NoLogFiles": "لا توجد ملفات سجل",
|
"NoLogFiles": "لا توجد ملفات سجل",
|
||||||
"NoMinimumForAnyRuntime": "لا يوجد حد أدنى لأي وقت تشغيل",
|
|
||||||
"NoTagsHaveBeenAddedYet": "لم يتم إضافة أي علامات حتى الان",
|
"NoTagsHaveBeenAddedYet": "لم يتم إضافة أي علامات حتى الان",
|
||||||
"NotificationTriggers": "مشغلات الإخطار",
|
"NotificationTriggers": "مشغلات الإخطار",
|
||||||
"NoUpdatesAreAvailable": "لا توجد تحديثات متوفرة",
|
"NoUpdatesAreAvailable": "لا توجد تحديثات متوفرة",
|
||||||
@@ -195,10 +171,8 @@
|
|||||||
"Clear": "واضح",
|
"Clear": "واضح",
|
||||||
"ClientPriority": "أولوية العميل",
|
"ClientPriority": "أولوية العميل",
|
||||||
"Tomorrow": "غدا",
|
"Tomorrow": "غدا",
|
||||||
"DownloadClientCheckUnableToCommunicateMessage": "تعذر الاتصال بـ {0}.",
|
|
||||||
"Torrent": "السيول",
|
"Torrent": "السيول",
|
||||||
"Torrents": "السيول",
|
"Torrents": "السيول",
|
||||||
"ExistingMovies": "فيلم (أفلام) موجود",
|
|
||||||
"ExistingTag": "علامة موجودة",
|
"ExistingTag": "علامة موجودة",
|
||||||
"Failed": "فشل",
|
"Failed": "فشل",
|
||||||
"FeatureRequests": "طلبات مخصصة",
|
"FeatureRequests": "طلبات مخصصة",
|
||||||
@@ -210,23 +184,18 @@
|
|||||||
"History": "التاريخ",
|
"History": "التاريخ",
|
||||||
"Hostname": "اسم المضيف",
|
"Hostname": "اسم المضيف",
|
||||||
"IgnoredAddresses": "العناوين التي تم تجاهلها",
|
"IgnoredAddresses": "العناوين التي تم تجاهلها",
|
||||||
"Importing": "استيراد",
|
|
||||||
"UILanguage": "لغة واجهة المستخدم",
|
"UILanguage": "لغة واجهة المستخدم",
|
||||||
"UILanguageHelpText": "اللغة التي سيستخدمها Radarr لواجهة المستخدم",
|
"UILanguageHelpText": "اللغة التي سيستخدمها Radarr لواجهة المستخدم",
|
||||||
"UILanguageHelpTextWarning": "يلزم إعادة تحميل المتصفح",
|
"UILanguageHelpTextWarning": "يلزم إعادة تحميل المتصفح",
|
||||||
"KeyboardShortcuts": "اختصارات لوحة المفاتيح",
|
"KeyboardShortcuts": "اختصارات لوحة المفاتيح",
|
||||||
"LogFiles": "ملفات الدخول",
|
"LogFiles": "ملفات الدخول",
|
||||||
"LogLevelTraceHelpTextWarning": "يجب تمكين تسجيل التتبع مؤقتًا فقط",
|
"LogLevelTraceHelpTextWarning": "يجب تمكين تسجيل التتبع مؤقتًا فقط",
|
||||||
"MonoTlsCheckMessage": "لا يزال الحل البديل لـ Radarr Mono 4.x tls ممكّنًا ، فجرّب إزالة MONO_TLS_PROVIDER = خيار البيئة القديمة",
|
|
||||||
"Movies": "أفلام",
|
|
||||||
"Name": "اسم",
|
"Name": "اسم",
|
||||||
"NetCore": ".شبكة",
|
"NetCore": ".شبكة",
|
||||||
"Password": "كلمه السر",
|
"Password": "كلمه السر",
|
||||||
"PreferredSize": "الحجم المفضل",
|
|
||||||
"Proxy": "الوكيل",
|
"Proxy": "الوكيل",
|
||||||
"ProxyCheckResolveIpMessage": "فشل حل عنوان IP لمضيف الخادم الوكيل المكون {0}",
|
"ProxyCheckResolveIpMessage": "فشل حل عنوان IP لمضيف الخادم الوكيل المكون {0}",
|
||||||
"ProxyType": "نوع الوكيل",
|
"ProxyType": "نوع الوكيل",
|
||||||
"QualityDefinitions": "تعريفات الجودة",
|
|
||||||
"ReleaseStatus": "حالة الإصدار",
|
"ReleaseStatus": "حالة الإصدار",
|
||||||
"Search": "بحث",
|
"Search": "بحث",
|
||||||
"SettingsShortDateFormat": "تنسيق التاريخ القصير",
|
"SettingsShortDateFormat": "تنسيق التاريخ القصير",
|
||||||
@@ -254,16 +223,11 @@
|
|||||||
"DownloadClients": "تحميل العملاء",
|
"DownloadClients": "تحميل العملاء",
|
||||||
"DownloadClientStatusCheckAllClientMessage": "جميع عملاء التنزيل غير متاحين بسبب الفشل",
|
"DownloadClientStatusCheckAllClientMessage": "جميع عملاء التنزيل غير متاحين بسبب الفشل",
|
||||||
"DownloadClientStatusCheckSingleClientMessage": "برامج التنزيل غير متاحة بسبب الإخفاقات: {0}",
|
"DownloadClientStatusCheckSingleClientMessage": "برامج التنزيل غير متاحة بسبب الإخفاقات: {0}",
|
||||||
"DownloadClientUnavailable": "عميل التنزيل غير متوفر",
|
|
||||||
"Downloading": "جارى التحميل",
|
|
||||||
"Edit": "تعديل",
|
"Edit": "تعديل",
|
||||||
"EditIndexer": "تحرير المفهرس",
|
"EditIndexer": "تحرير المفهرس",
|
||||||
"Enable": "ممكن",
|
"Enable": "ممكن",
|
||||||
"EnableAutomaticAdd": "تمكين إضافة تلقائية",
|
|
||||||
"EnableAutomaticSearch": "تمكين البحث التلقائي",
|
"EnableAutomaticSearch": "تمكين البحث التلقائي",
|
||||||
"EnableAutomaticSearchHelpTextWarning": "سيتم استخدامها عند استخدام البحث التفاعلي",
|
|
||||||
"Enabled": "ممكن",
|
"Enabled": "ممكن",
|
||||||
"EnableInteractiveSearchHelpTextWarning": "البحث غير معتمد مع هذا المفهرس",
|
|
||||||
"IndexerPriority": "أولوية المفهرس",
|
"IndexerPriority": "أولوية المفهرس",
|
||||||
"Port": "ميناء",
|
"Port": "ميناء",
|
||||||
"SettingsTimeFormat": "تنسيق الوقت",
|
"SettingsTimeFormat": "تنسيق الوقت",
|
||||||
@@ -292,12 +256,10 @@
|
|||||||
"SettingsLongDateFormat": "تنسيق التاريخ الطويل",
|
"SettingsLongDateFormat": "تنسيق التاريخ الطويل",
|
||||||
"ConnectionLost": "انقطع الاتصال",
|
"ConnectionLost": "انقطع الاتصال",
|
||||||
"Connections": "روابط",
|
"Connections": "روابط",
|
||||||
"MovieDetailsPreviousMovie": "تفاصيل الفيلم: الفيلم السابق",
|
|
||||||
"MovieIndexScrollBottom": "فهرس الفيلم: التمرير لأسفل",
|
"MovieIndexScrollBottom": "فهرس الفيلم: التمرير لأسفل",
|
||||||
"NoBackupsAreAvailable": "لا توجد نسخ احتياطية متاحة",
|
"NoBackupsAreAvailable": "لا توجد نسخ احتياطية متاحة",
|
||||||
"NoChanges": "لا تغيرات",
|
"NoChanges": "لا تغيرات",
|
||||||
"NoLeaveIt": "لا ، اتركها",
|
"NoLeaveIt": "لا ، اتركها",
|
||||||
"Pending": "قيد الانتظار",
|
|
||||||
"PendingChangesDiscardChanges": "تجاهل التغييرات واترك",
|
"PendingChangesDiscardChanges": "تجاهل التغييرات واترك",
|
||||||
"RSS": "RSS",
|
"RSS": "RSS",
|
||||||
"System": "النظام",
|
"System": "النظام",
|
||||||
@@ -328,7 +290,6 @@
|
|||||||
"Mechanism": "آلية",
|
"Mechanism": "آلية",
|
||||||
"Message": "رسالة",
|
"Message": "رسالة",
|
||||||
"MIA": "MIA",
|
"MIA": "MIA",
|
||||||
"MinimumLimits": "الحد الأدنى",
|
|
||||||
"RefreshMovie": "تحديث الفيلم",
|
"RefreshMovie": "تحديث الفيلم",
|
||||||
"EnableAutomaticSearchHelpText": "سيتم استخدامه عند إجراء عمليات البحث التلقائي عبر واجهة المستخدم أو بواسطة Radarr",
|
"EnableAutomaticSearchHelpText": "سيتم استخدامه عند إجراء عمليات البحث التلقائي عبر واجهة المستخدم أو بواسطة Radarr",
|
||||||
"Status": "الحالة",
|
"Status": "الحالة",
|
||||||
@@ -336,7 +297,6 @@
|
|||||||
"ApplyTagsHelpTexts4": "استبدال: استبدل العلامات بالعلامات التي تم إدخالها (لا تدخل أي علامات لمسح جميع العلامات)",
|
"ApplyTagsHelpTexts4": "استبدال: استبدل العلامات بالعلامات التي تم إدخالها (لا تدخل أي علامات لمسح جميع العلامات)",
|
||||||
"AuthenticationMethodHelpText": "طلب اسم المستخدم وكلمة المرور للوصول إلى Radarr",
|
"AuthenticationMethodHelpText": "طلب اسم المستخدم وكلمة المرور للوصول إلى Radarr",
|
||||||
"Automatic": "تلقائي",
|
"Automatic": "تلقائي",
|
||||||
"DownloadClientCheckNoneAvailableMessage": "لا يوجد عميل تنزيل متاح",
|
|
||||||
"Mode": "الوضع",
|
"Mode": "الوضع",
|
||||||
"Options": "خيارات",
|
"Options": "خيارات",
|
||||||
"Ok": "موافق",
|
"Ok": "موافق",
|
||||||
@@ -347,7 +307,6 @@
|
|||||||
"RestartRequiredHelpTextWarning": "يتطلب إعادة التشغيل ليصبح ساري المفعول",
|
"RestartRequiredHelpTextWarning": "يتطلب إعادة التشغيل ليصبح ساري المفعول",
|
||||||
"Restore": "استعادة",
|
"Restore": "استعادة",
|
||||||
"RestoreBackup": "استرجاع النسخة الاحتياطية",
|
"RestoreBackup": "استرجاع النسخة الاحتياطية",
|
||||||
"Restrictions": "قيود",
|
|
||||||
"Result": "نتيجة",
|
"Result": "نتيجة",
|
||||||
"Retention": "احتفاظ",
|
"Retention": "احتفاظ",
|
||||||
"SaveChanges": "حفظ التغييرات",
|
"SaveChanges": "حفظ التغييرات",
|
||||||
@@ -358,5 +317,12 @@
|
|||||||
"Test": "اختبار",
|
"Test": "اختبار",
|
||||||
"TestAll": "اختبار الكل",
|
"TestAll": "اختبار الكل",
|
||||||
"TestAllClients": "اختبر كل العملاء",
|
"TestAllClients": "اختبر كل العملاء",
|
||||||
"UnableToAddANewApplicationPleaseTryAgain": "تعذر إضافة إشعار جديد ، يرجى المحاولة مرة أخرى."
|
"UnableToAddANewApplicationPleaseTryAgain": "تعذر إضافة إشعار جديد ، يرجى المحاولة مرة أخرى.",
|
||||||
|
"MaintenanceRelease": "إصدار الصيانة: إصلاحات الأخطاء والتحسينات الأخرى. راجع Github Commit History لمزيد من التفاصيل",
|
||||||
|
"Filters": "منقي",
|
||||||
|
"HistoryCleanupDaysHelpText": "اضبط على 0 لتعطيل التنظيف التلقائي",
|
||||||
|
"HistoryCleanupDaysHelpTextWarning": "سيتم تنظيف الملفات الموجودة في سلة المحذوفات الأقدم من عدد الأيام المحدد تلقائيًا",
|
||||||
|
"OnGrab": "عند الاستيلاء",
|
||||||
|
"OnHealthIssue": "في قضية الصحة",
|
||||||
|
"TestAllIndexers": "اختبار كافة المفهرسات"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,11 +42,9 @@
|
|||||||
"Delete": "Изтрий",
|
"Delete": "Изтрий",
|
||||||
"DeleteDownloadClient": "Изтриване на клиент за изтегляне",
|
"DeleteDownloadClient": "Изтриване на клиент за изтегляне",
|
||||||
"DeleteDownloadClientMessageText": "Наистина ли искате да изтриете клиента за изтегляне '{0}'?",
|
"DeleteDownloadClientMessageText": "Наистина ли искате да изтриете клиента за изтегляне '{0}'?",
|
||||||
"DeleteIndexer": "Изтрийте Indexer",
|
|
||||||
"DeleteNotification": "Изтриване на известието",
|
"DeleteNotification": "Изтриване на известието",
|
||||||
"DeleteNotificationMessageText": "Наистина ли искате да изтриете известието '{0}'?",
|
"DeleteNotificationMessageText": "Наистина ли искате да изтриете известието '{0}'?",
|
||||||
"Donations": "Дарения",
|
"Donations": "Дарения",
|
||||||
"DownloadClientCheckUnableToCommunicateMessage": "Не може да се комуникира с {0}.",
|
|
||||||
"About": "относно",
|
"About": "относно",
|
||||||
"AcceptConfirmationModal": "Приемете модал за потвърждение",
|
"AcceptConfirmationModal": "Приемете модал за потвърждение",
|
||||||
"AddDownloadClient": "Добавяне на клиент за изтегляне",
|
"AddDownloadClient": "Добавяне на клиент за изтегляне",
|
||||||
@@ -66,20 +64,15 @@
|
|||||||
"Connections": "Връзки",
|
"Connections": "Връзки",
|
||||||
"CouldNotConnectSignalR": "Не можах да се свържа със SignalR, потребителският интерфейс няма да се актуализира",
|
"CouldNotConnectSignalR": "Не можах да се свържа със SignalR, потребителският интерфейс няма да се актуализира",
|
||||||
"DeleteBackupMessageText": "Наистина ли искате да изтриете резервното копие '{0}'?",
|
"DeleteBackupMessageText": "Наистина ли искате да изтриете резервното копие '{0}'?",
|
||||||
"DeleteIndexerMessageText": "Наистина ли искате да изтриете индексатора '{0}'?",
|
|
||||||
"DeleteTag": "Изтриване на маркера",
|
"DeleteTag": "Изтриване на маркера",
|
||||||
"DeleteTagMessageText": "Наистина ли искате да изтриете маркера '{0}'?",
|
"DeleteTagMessageText": "Наистина ли искате да изтриете маркера '{0}'?",
|
||||||
"DownloadClientCheckNoneAvailableMessage": "Няма наличен клиент за изтегляне",
|
|
||||||
"IndexerProxyStatusCheckAllClientMessage": "Всички списъци са недостъпни поради неуспехи",
|
"IndexerProxyStatusCheckAllClientMessage": "Всички списъци са недостъпни поради неуспехи",
|
||||||
"LastWriteTime": "Време за последно писане",
|
"LastWriteTime": "Време за последно писане",
|
||||||
"Columns": "Колони",
|
"Columns": "Колони",
|
||||||
"EnableRss": "Активиране на RSS",
|
"EnableRss": "Активиране на RSS",
|
||||||
"Downloading": "Изтегляне",
|
|
||||||
"IndexerProxyStatusCheckSingleClientMessage": "Списъци, недостъпни поради неуспехи: {0}",
|
"IndexerProxyStatusCheckSingleClientMessage": "Списъци, недостъпни поради неуспехи: {0}",
|
||||||
"Languages": "Езици",
|
|
||||||
"LogLevel": "Log Level",
|
"LogLevel": "Log Level",
|
||||||
"MovieIndexScrollTop": "Индекс на филма: Превъртете отгоре",
|
"MovieIndexScrollTop": "Индекс на филма: Превъртете отгоре",
|
||||||
"Movies": "Филми",
|
|
||||||
"Queue": "Опашка",
|
"Queue": "Опашка",
|
||||||
"ReadTheWikiForMoreInformation": "Прочетете Wiki за повече информация",
|
"ReadTheWikiForMoreInformation": "Прочетете Wiki за повече информация",
|
||||||
"Reddit": "Reddit",
|
"Reddit": "Reddit",
|
||||||
@@ -89,7 +82,6 @@
|
|||||||
"RemovedFromTaskQueue": "Премахнато от опашката на задачите",
|
"RemovedFromTaskQueue": "Премахнато от опашката на задачите",
|
||||||
"RemoveFilter": "Отстранете филтъра",
|
"RemoveFilter": "Отстранете филтъра",
|
||||||
"RestoreBackup": "Възстанови архива",
|
"RestoreBackup": "Възстанови архива",
|
||||||
"Restrictions": "Ограничения",
|
|
||||||
"Result": "Резултат",
|
"Result": "Резултат",
|
||||||
"Save": "Запазете",
|
"Save": "Запазете",
|
||||||
"SaveChanges": "Запазите промените",
|
"SaveChanges": "Запазите промените",
|
||||||
@@ -114,7 +106,6 @@
|
|||||||
"UnableToAddANewIndexerPleaseTryAgain": "Не може да се добави нов индексатор, моля, опитайте отново.",
|
"UnableToAddANewIndexerPleaseTryAgain": "Не може да се добави нов индексатор, моля, опитайте отново.",
|
||||||
"UnableToAddANewIndexerProxyPleaseTryAgain": "Не може да се добави нов индексатор, моля, опитайте отново.",
|
"UnableToAddANewIndexerProxyPleaseTryAgain": "Не може да се добави нов индексатор, моля, опитайте отново.",
|
||||||
"UnableToAddANewNotificationPleaseTryAgain": "Не може да се добави ново известие, моля, опитайте отново.",
|
"UnableToAddANewNotificationPleaseTryAgain": "Не може да се добави ново известие, моля, опитайте отново.",
|
||||||
"UnableToLoadQualityDefinitions": "Определенията за качество не могат да се заредят",
|
|
||||||
"UpdateAutomaticallyHelpText": "Автоматично изтегляне и инсталиране на актуализации. Все още ще можете да инсталирате от System: Updates",
|
"UpdateAutomaticallyHelpText": "Автоматично изтегляне и инсталиране на актуализации. Все още ще можете да инсталирате от System: Updates",
|
||||||
"UpdateCheckStartupTranslocationMessage": "Не може да се инсталира актуализация, защото стартовата папка „{0}“ е в папка за преместване на приложения.",
|
"UpdateCheckStartupTranslocationMessage": "Не може да се инсталира актуализация, защото стартовата папка „{0}“ е в папка за преместване на приложения.",
|
||||||
"ReleaseStatus": "Състояние на освобождаване",
|
"ReleaseStatus": "Състояние на освобождаване",
|
||||||
@@ -129,7 +120,6 @@
|
|||||||
"StartupDirectory": "Стартова директория",
|
"StartupDirectory": "Стартова директория",
|
||||||
"Disabled": "хора с увреждания",
|
"Disabled": "хора с увреждания",
|
||||||
"EditIndexer": "Редактиране на Indexer",
|
"EditIndexer": "Редактиране на Indexer",
|
||||||
"EnableAutomaticSearchHelpTextWarning": "Ще се използва, когато се използва интерактивно търсене",
|
|
||||||
"Enabled": "Активирано",
|
"Enabled": "Активирано",
|
||||||
"General": "Общ",
|
"General": "Общ",
|
||||||
"Grabbed": "Грабната",
|
"Grabbed": "Грабната",
|
||||||
@@ -148,21 +138,17 @@
|
|||||||
"Language": "Език",
|
"Language": "Език",
|
||||||
"LogLevelTraceHelpTextWarning": "Регистрацията на проследяване трябва да бъде разрешена само временно",
|
"LogLevelTraceHelpTextWarning": "Регистрацията на проследяване трябва да бъде разрешена само временно",
|
||||||
"Manual": "Ръчно",
|
"Manual": "Ръчно",
|
||||||
"MaximumLimits": "Максимални граници",
|
|
||||||
"Mechanism": "Механизъм",
|
"Mechanism": "Механизъм",
|
||||||
"Message": "Съобщение",
|
"Message": "Съобщение",
|
||||||
"MIA": "МВР",
|
"MIA": "МВР",
|
||||||
"MinutesHundredTwenty": "120 минути: {0}",
|
|
||||||
"NoChange": "Няма промяна",
|
"NoChange": "Няма промяна",
|
||||||
"NoChanges": "Без промени",
|
"NoChanges": "Без промени",
|
||||||
"NoLeaveIt": "Не, оставете го",
|
"NoLeaveIt": "Не, оставете го",
|
||||||
"NoLogFiles": "Няма регистрационни файлове",
|
"NoLogFiles": "Няма регистрационни файлове",
|
||||||
"NoMinimumForAnyRuntime": "Няма минимум за всяко време на изпълнение",
|
|
||||||
"RestartRequiredHelpTextWarning": "Изисква рестартиране, за да влезе в сила",
|
"RestartRequiredHelpTextWarning": "Изисква рестартиране, за да влезе в сила",
|
||||||
"SettingsShortDateFormat": "Формат на кратка дата",
|
"SettingsShortDateFormat": "Формат на кратка дата",
|
||||||
"SSLCertPath": "SSL Cert Path",
|
"SSLCertPath": "SSL Cert Path",
|
||||||
"URLBase": "URL база",
|
"URLBase": "URL база",
|
||||||
"CloneIndexer": "Clone Indexer",
|
|
||||||
"CloneProfile": "Профил на клониране",
|
"CloneProfile": "Профил на клониране",
|
||||||
"EnableInteractiveSearch": "Активирайте интерактивно търсене",
|
"EnableInteractiveSearch": "Активирайте интерактивно търсене",
|
||||||
"TagIsNotUsedAndCanBeDeleted": "Етикетът не се използва и може да бъде изтрит",
|
"TagIsNotUsedAndCanBeDeleted": "Етикетът не се използва и може да бъде изтрит",
|
||||||
@@ -175,7 +161,6 @@
|
|||||||
"UnableToLoadBackups": "Архивите не могат да се заредят",
|
"UnableToLoadBackups": "Архивите не могат да се заредят",
|
||||||
"AllIndexersHiddenDueToFilter": "Всички филми са скрити поради приложен филтър.",
|
"AllIndexersHiddenDueToFilter": "Всички филми са скрити поради приложен филтър.",
|
||||||
"Level": "Ниво",
|
"Level": "Ниво",
|
||||||
"MinimumLimits": "Минимални лимити",
|
|
||||||
"ApplicationStatusCheckAllClientMessage": "Всички списъци са недостъпни поради неуспехи",
|
"ApplicationStatusCheckAllClientMessage": "Всички списъци са недостъпни поради неуспехи",
|
||||||
"ApplicationStatusCheckSingleClientMessage": "Списъци, недостъпни поради неуспехи: {0}",
|
"ApplicationStatusCheckSingleClientMessage": "Списъци, недостъпни поради неуспехи: {0}",
|
||||||
"Close": "Близо",
|
"Close": "Близо",
|
||||||
@@ -204,7 +189,6 @@
|
|||||||
"PackageVersion": "Версия на пакета",
|
"PackageVersion": "Версия на пакета",
|
||||||
"PageSize": "Размер на страницата",
|
"PageSize": "Размер на страницата",
|
||||||
"PendingChangesStayReview": "Останете и прегледайте промените",
|
"PendingChangesStayReview": "Останете и прегледайте промените",
|
||||||
"PreferredSize": "Предпочитан размер",
|
|
||||||
"Presets": "Предварителни настройки",
|
"Presets": "Предварителни настройки",
|
||||||
"Priority": "Приоритет",
|
"Priority": "Приоритет",
|
||||||
"PrioritySettings": "Приоритет",
|
"PrioritySettings": "Приоритет",
|
||||||
@@ -212,13 +196,8 @@
|
|||||||
"SettingsLongDateFormat": "Формат с дълга дата",
|
"SettingsLongDateFormat": "Формат с дълга дата",
|
||||||
"SettingsShowRelativeDates": "Показване на относителни дати",
|
"SettingsShowRelativeDates": "Показване на относителни дати",
|
||||||
"UnableToLoadDownloadClients": "Клиентите за изтегляне не могат да се заредят",
|
"UnableToLoadDownloadClients": "Клиентите за изтегляне не могат да се заредят",
|
||||||
"DelayProfile": "Профил за забавяне",
|
|
||||||
"Logging": "Регистрация",
|
"Logging": "Регистрация",
|
||||||
"Exception": "Изключение",
|
"Exception": "Изключение",
|
||||||
"MinutesNinety": "90 минути: {0}",
|
|
||||||
"MinutesSixty": "60 минути: {0}",
|
|
||||||
"MovieDetailsNextMovie": "Подробности за филма: Следващ филм",
|
|
||||||
"MovieDetailsPreviousMovie": "Подробности за филма: Предишен филм",
|
|
||||||
"MovieIndexScrollBottom": "Индекс на филма: Превъртане отдолу",
|
"MovieIndexScrollBottom": "Индекс на филма: Превъртане отдолу",
|
||||||
"Name": "Име",
|
"Name": "Име",
|
||||||
"New": "Ново",
|
"New": "Ново",
|
||||||
@@ -233,12 +212,8 @@
|
|||||||
"ProxyCheckResolveIpMessage": "Неуспешно разрешаване на IP адреса за конфигурирания прокси хост {0}",
|
"ProxyCheckResolveIpMessage": "Неуспешно разрешаване на IP адреса за конфигурирания прокси хост {0}",
|
||||||
"ProxyPasswordHelpText": "Трябва само да въведете потребителско име и парола, ако е необходимо. В противен случай ги оставете празни.",
|
"ProxyPasswordHelpText": "Трябва само да въведете потребителско име и парола, ако е необходимо. В противен случай ги оставете празни.",
|
||||||
"ProxyType": "Тип прокси",
|
"ProxyType": "Тип прокси",
|
||||||
"ExistingMovies": "Съществуващи филми",
|
|
||||||
"ExistingTag": "Съществуващ маркер",
|
"ExistingTag": "Съществуващ маркер",
|
||||||
"ProxyUsernameHelpText": "Трябва само да въведете потребителско име и парола, ако е необходимо. В противен случай ги оставете празни.",
|
"ProxyUsernameHelpText": "Трябва само да въведете потребителско име и парола, ако е необходимо. В противен случай ги оставете празни.",
|
||||||
"QualityDefinitions": "Определения за качество",
|
|
||||||
"QualitySettings": "Настройки за качество",
|
|
||||||
"Importing": "Импортиране",
|
|
||||||
"ReleaseBranchCheckOfficialBranchMessage": "Клон {0} не е валиден клон за издаване на Radarr, няма да получавате актуализации",
|
"ReleaseBranchCheckOfficialBranchMessage": "Клон {0} не е валиден клон за издаване на Radarr, няма да получавате актуализации",
|
||||||
"IncludeHealthWarningsHelpText": "Включете здравни предупреждения",
|
"IncludeHealthWarningsHelpText": "Включете здравни предупреждения",
|
||||||
"SaveSettings": "Запазване на настройките",
|
"SaveSettings": "Запазване на настройките",
|
||||||
@@ -275,8 +250,6 @@
|
|||||||
"UpdateCheckStartupNotWritableMessage": "Не може да се инсталира актуализация, тъй като стартовата папка „{0}“ не може да се записва от потребителя „{1}“.",
|
"UpdateCheckStartupNotWritableMessage": "Не може да се инсталира актуализация, тъй като стартовата папка „{0}“ не може да се записва от потребителя „{1}“.",
|
||||||
"UpdateCheckUINotWritableMessage": "Не може да се инсталира актуализация, защото папката „{0}“ на потребителския интерфейс не може да се записва от потребителя „{1}“.",
|
"UpdateCheckUINotWritableMessage": "Не може да се инсталира актуализация, защото папката „{0}“ на потребителския интерфейс не може да се записва от потребителя „{1}“.",
|
||||||
"Enable": "Активиране",
|
"Enable": "Активиране",
|
||||||
"EnableColorImpairedMode": "Активирайте режима с увредени цветове",
|
|
||||||
"EnableColorImpairedModeHelpText": "Променен стил, за да позволи на потребителите с увредени цветове да разграничат по-добре информацията, кодирана в цвят",
|
|
||||||
"EventType": "Тип на събитието",
|
"EventType": "Тип на събитието",
|
||||||
"Failed": "Се провали",
|
"Failed": "Се провали",
|
||||||
"FeatureRequests": "Заявки за функции",
|
"FeatureRequests": "Заявки за функции",
|
||||||
@@ -298,10 +271,6 @@
|
|||||||
"Indexers": "Индексатори",
|
"Indexers": "Индексатори",
|
||||||
"IndexerStatusCheckAllClientMessage": "Всички индексатори са недостъпни поради грешки",
|
"IndexerStatusCheckAllClientMessage": "Всички индексатори са недостъпни поради грешки",
|
||||||
"Mode": "Режим",
|
"Mode": "Режим",
|
||||||
"MonoTlsCheckMessage": "Работното решение на Radarr Mono 4.x tls все още е активирано, помислете за премахване на MONO_TLS_PROVIDER = опция за наследствена среда",
|
|
||||||
"MonoVersion": "Моно версия",
|
|
||||||
"MonoVersionCheckUpgradeRecommendedMessage": "Понастоящем инсталираната моно версия {0} се поддържа, но се препоръчва надстройка до {1}.",
|
|
||||||
"NoLimitForAnyRuntime": "Няма ограничение за всяко време на изпълнение",
|
|
||||||
"TagsSettingsSummary": "Вижте всички тагове и как се използват. Неизползваните маркери могат да бъдат премахнати",
|
"TagsSettingsSummary": "Вижте всички тагове и как се използват. Неизползваните маркери могат да бъдат премахнати",
|
||||||
"TestAllClients": "Тествайте всички клиенти",
|
"TestAllClients": "Тествайте всички клиенти",
|
||||||
"UnableToAddANewDownloadClientPleaseTryAgain": "Не може да се добави нов клиент за изтегляне, моля, опитайте отново.",
|
"UnableToAddANewDownloadClientPleaseTryAgain": "Не може да се добави нов клиент за изтегляне, моля, опитайте отново.",
|
||||||
@@ -315,17 +284,9 @@
|
|||||||
"DownloadClientSettings": "Изтеглете настройките на клиента",
|
"DownloadClientSettings": "Изтеглете настройките на клиента",
|
||||||
"DownloadClientStatusCheckAllClientMessage": "Всички клиенти за изтегляне са недостъпни поради неуспехи",
|
"DownloadClientStatusCheckAllClientMessage": "Всички клиенти за изтегляне са недостъпни поради неуспехи",
|
||||||
"DownloadClientStatusCheckSingleClientMessage": "Клиентите за изтегляне са недостъпни поради грешки: {0}",
|
"DownloadClientStatusCheckSingleClientMessage": "Клиентите за изтегляне са недостъпни поради грешки: {0}",
|
||||||
"DownloadClientUnavailable": "Клиентът за изтегляне не е наличен",
|
|
||||||
"Edit": "редактиране",
|
"Edit": "редактиране",
|
||||||
"EnableAutoHelpText": "Ако е активирано, Филмите ще бъдат автоматично добавени към Radarr от този списък",
|
|
||||||
"EnableAutomaticAdd": "Активирайте автоматичното добавяне",
|
|
||||||
"EnableAutomaticSearch": "Активирайте автоматичното търсене",
|
"EnableAutomaticSearch": "Активирайте автоматичното търсене",
|
||||||
"EnableAutomaticSearchHelpText": "Ще се използва, когато се извършват автоматични търсения чрез потребителския интерфейс или от Radarr",
|
"EnableAutomaticSearchHelpText": "Ще се използва, когато се извършват автоматични търсения чрез потребителския интерфейс или от Radarr",
|
||||||
"EnableCompletedDownloadHandlingHelpText": "Автоматично импортирайте завършени изтегляния от клиент за изтегляне",
|
|
||||||
"EnabledHelpText": "Активирайте този списък за използване в Radarr",
|
|
||||||
"EnableHelpText": "Активирайте създаването на файл с метаданни за този тип метаданни",
|
|
||||||
"EnableInteractiveSearchHelpTextWarning": "Търсенето не се поддържа с този индексатор",
|
|
||||||
"EnableMediaInfoHelpText": "Извличайте видео информация като резолюция, време на работа и информация за кодеци от файлове. Това изисква от Radarr да чете части от файла, които могат да причинят висока активност на диска или мрежата по време на сканиране.",
|
|
||||||
"EnableSSL": "Активирайте SSL",
|
"EnableSSL": "Активирайте SSL",
|
||||||
"EnableSslHelpText": " Изисква рестартиране, изпълнено като администратор, за да влезе в сила",
|
"EnableSslHelpText": " Изисква рестартиране, изпълнено като администратор, за да влезе в сила",
|
||||||
"Error": "Грешка",
|
"Error": "Грешка",
|
||||||
@@ -350,12 +311,17 @@
|
|||||||
"PageSizeHelpText": "Брой елементи за показване на всяка страница",
|
"PageSizeHelpText": "Брой елементи за показване на всяка страница",
|
||||||
"Password": "Парола",
|
"Password": "Парола",
|
||||||
"Peers": "Връстници",
|
"Peers": "Връстници",
|
||||||
"Pending": "В очакване",
|
|
||||||
"PendingChangesDiscardChanges": "Изхвърлете промените и оставете",
|
"PendingChangesDiscardChanges": "Изхвърлете промените и оставете",
|
||||||
"PriorityHelpText": "Приоритизирайте множество клиенти за изтегляне. Round-Robin се използва за клиенти със същия приоритет.",
|
"PriorityHelpText": "Приоритизирайте множество клиенти за изтегляне. Round-Robin се използва за клиенти със същия приоритет.",
|
||||||
"Protocol": "Протокол",
|
"Protocol": "Протокол",
|
||||||
"UnableToLoadGeneralSettings": "Не може да се заредят общи настройки",
|
"UnableToLoadGeneralSettings": "Не може да се заредят общи настройки",
|
||||||
"UnableToLoadHistory": "Историята не може да се зареди",
|
"UnableToLoadHistory": "Историята не може да се зареди",
|
||||||
"UnableToLoadIndexers": "Индексаторите не могат да се заредят",
|
"UnableToLoadNotifications": "Известията не могат да се заредят",
|
||||||
"UnableToLoadNotifications": "Известията не могат да се заредят"
|
"MaintenanceRelease": "Издание за поддръжка: поправки на грешки и други подобрения. Вижте История на комисиите на Github за повече подробности",
|
||||||
|
"Filters": "Филтър",
|
||||||
|
"HistoryCleanupDaysHelpText": "Задайте на 0, за да деактивирате автоматичното почистване",
|
||||||
|
"HistoryCleanupDaysHelpTextWarning": "Файловете в кошчето, по-стари от избрания брой дни, ще се почистват автоматично",
|
||||||
|
"OnGrab": "На Граб",
|
||||||
|
"OnHealthIssue": "По здравен въпрос",
|
||||||
|
"TestAllIndexers": "Тествайте всички индексатори"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@@ -4,7 +4,6 @@
|
|||||||
"Actions": "Accions",
|
"Actions": "Accions",
|
||||||
"AcceptConfirmationModal": "Accepta el mètode de confirmació",
|
"AcceptConfirmationModal": "Accepta el mètode de confirmació",
|
||||||
"About": "Quant a",
|
"About": "Quant a",
|
||||||
"Movies": "Pel·lícula",
|
|
||||||
"New": "Nou",
|
"New": "Nou",
|
||||||
"Reload": "Recarregar",
|
"Reload": "Recarregar",
|
||||||
"Queue": "Cua",
|
"Queue": "Cua",
|
||||||
|
|||||||
@@ -6,19 +6,16 @@
|
|||||||
"About": "O",
|
"About": "O",
|
||||||
"Component": "Součástka",
|
"Component": "Součástka",
|
||||||
"Info": "Info",
|
"Info": "Info",
|
||||||
"Languages": "Jazyky",
|
|
||||||
"LogFiles": "Záznam souborů",
|
"LogFiles": "Záznam souborů",
|
||||||
"PrioritySettings": "Přednost",
|
"PrioritySettings": "Přednost",
|
||||||
"Logs": "Protokoly",
|
"Logs": "Protokoly",
|
||||||
"ProxyBypassFilterHelpText": "Použijte ',' jako oddělovač a '*.' jako zástupný znak pro subdomény",
|
"ProxyBypassFilterHelpText": "Použijte ',' jako oddělovač a '*.' jako zástupný znak pro subdomény",
|
||||||
"QualityDefinitions": "Definice kvality",
|
|
||||||
"SaveSettings": "Uložit nastavení",
|
"SaveSettings": "Uložit nastavení",
|
||||||
"Scheduled": "Naplánováno",
|
"Scheduled": "Naplánováno",
|
||||||
"ScriptPath": "Cesta skriptu",
|
"ScriptPath": "Cesta skriptu",
|
||||||
"SetTags": "Nastavit značky",
|
"SetTags": "Nastavit značky",
|
||||||
"Settings": "Nastavení",
|
"Settings": "Nastavení",
|
||||||
"StartTypingOrSelectAPathBelow": "Začněte psát nebo vyberte cestu níže",
|
"StartTypingOrSelectAPathBelow": "Začněte psát nebo vyberte cestu níže",
|
||||||
"UnableToLoadIndexers": "Nelze načíst indexery",
|
|
||||||
"Usenet": "Usenet",
|
"Usenet": "Usenet",
|
||||||
"AddDownloadClient": "Přidat staženého klienta",
|
"AddDownloadClient": "Přidat staženého klienta",
|
||||||
"Backups": "Zálohy",
|
"Backups": "Zálohy",
|
||||||
@@ -26,10 +23,8 @@
|
|||||||
"MovieIndexScrollBottom": "Rejstřík filmů: Posun dolů",
|
"MovieIndexScrollBottom": "Rejstřík filmů: Posun dolů",
|
||||||
"ProxyType": "Typ serveru proxy",
|
"ProxyType": "Typ serveru proxy",
|
||||||
"Reddit": "Reddit",
|
"Reddit": "Reddit",
|
||||||
"DownloadClientUnavailable": "Stahovací klient není k dispozici",
|
|
||||||
"ErrorLoadingContents": "Chyba při načítání obsahu",
|
"ErrorLoadingContents": "Chyba při načítání obsahu",
|
||||||
"IndexerLongTermStatusCheckAllClientMessage": "Všechny indexery nejsou k dispozici z důvodu selhání po dobu delší než 6 hodin",
|
"IndexerLongTermStatusCheckAllClientMessage": "Všechny indexery nejsou k dispozici z důvodu selhání po dobu delší než 6 hodin",
|
||||||
"MonoVersion": "Mono verze",
|
|
||||||
"RemovedFromTaskQueue": "Odebráno z fronty úkolů",
|
"RemovedFromTaskQueue": "Odebráno z fronty úkolů",
|
||||||
"ResetAPIKey": "Resetovat klíč API",
|
"ResetAPIKey": "Resetovat klíč API",
|
||||||
"SSLCertPassword": "Heslo SSL Cert",
|
"SSLCertPassword": "Heslo SSL Cert",
|
||||||
@@ -49,7 +44,6 @@
|
|||||||
"DownloadClientSettings": "Stáhněte si nastavení klienta",
|
"DownloadClientSettings": "Stáhněte si nastavení klienta",
|
||||||
"DownloadClientStatusCheckAllClientMessage": "Všichni klienti pro stahování nejsou kvůli chybám k dispozici",
|
"DownloadClientStatusCheckAllClientMessage": "Všichni klienti pro stahování nejsou kvůli chybám k dispozici",
|
||||||
"DownloadClientStatusCheckSingleClientMessage": "Stahování klientů není k dispozici z důvodu selhání: {0}",
|
"DownloadClientStatusCheckSingleClientMessage": "Stahování klientů není k dispozici z důvodu selhání: {0}",
|
||||||
"Downloading": "Stahování",
|
|
||||||
"Folder": "Složka",
|
"Folder": "Složka",
|
||||||
"Grabs": "Urvat",
|
"Grabs": "Urvat",
|
||||||
"HealthNoIssues": "Žádné problémy s vaší konfigurací",
|
"HealthNoIssues": "Žádné problémy s vaší konfigurací",
|
||||||
@@ -68,12 +62,10 @@
|
|||||||
"Level": "Úroveň",
|
"Level": "Úroveň",
|
||||||
"LogLevel": "Úroveň protokolu",
|
"LogLevel": "Úroveň protokolu",
|
||||||
"Manual": "Manuál",
|
"Manual": "Manuál",
|
||||||
"MaximumLimits": "Maximální limity",
|
|
||||||
"Message": "Zpráva",
|
"Message": "Zpráva",
|
||||||
"MIA": "MIA",
|
"MIA": "MIA",
|
||||||
"Mode": "Režim",
|
"Mode": "Režim",
|
||||||
"NoTagsHaveBeenAddedYet": "Zatím nebyly přidány žádné značky",
|
"NoTagsHaveBeenAddedYet": "Zatím nebyly přidány žádné značky",
|
||||||
"NoMinimumForAnyRuntime": "Žádné minimum za běhu",
|
|
||||||
"Ok": "OK",
|
"Ok": "OK",
|
||||||
"SendAnonymousUsageData": "Odesílejte anonymní údaje o používání",
|
"SendAnonymousUsageData": "Odesílejte anonymní údaje o používání",
|
||||||
"UnselectAll": "Odznačit vše",
|
"UnselectAll": "Odznačit vše",
|
||||||
@@ -91,10 +83,6 @@
|
|||||||
"Branch": "Větev",
|
"Branch": "Větev",
|
||||||
"BranchUpdate": "Pobočka, která se má použít k aktualizaci Radarr",
|
"BranchUpdate": "Pobočka, která se má použít k aktualizaci Radarr",
|
||||||
"EditIndexer": "Upravit indexátor",
|
"EditIndexer": "Upravit indexátor",
|
||||||
"EnableColorImpairedModeHelpText": "Upravený styl umožňující uživatelům s barevným postižením lépe rozlišovat barevně kódované informace",
|
|
||||||
"EnableCompletedDownloadHandlingHelpText": "Automaticky importovat dokončená stahování z klienta pro stahování",
|
|
||||||
"EnabledHelpText": "Povolte tento seznam pro použití v Radarru",
|
|
||||||
"EnableHelpText": "Povolit vytváření souborů metadat pro tento typ metadat",
|
|
||||||
"ForMoreInformationOnTheIndividualDownloadClients": "Další informace o jednotlivých klientech pro stahování získáte kliknutím na informační tlačítka.",
|
"ForMoreInformationOnTheIndividualDownloadClients": "Další informace o jednotlivých klientech pro stahování získáte kliknutím na informační tlačítka.",
|
||||||
"General": "Všeobecné",
|
"General": "Všeobecné",
|
||||||
"ApplyTagsHelpTexts3": "Odebrat: Odebere zadané značky",
|
"ApplyTagsHelpTexts3": "Odebrat: Odebere zadané značky",
|
||||||
@@ -103,7 +91,6 @@
|
|||||||
"ConnectionLost": "Spojení ztraceno",
|
"ConnectionLost": "Spojení ztraceno",
|
||||||
"ConnectSettings": "Připojit nastavení",
|
"ConnectSettings": "Připojit nastavení",
|
||||||
"Custom": "Zvyk",
|
"Custom": "Zvyk",
|
||||||
"EnableAutomaticAdd": "Povolit automatické přidání",
|
|
||||||
"Error": "Chyba",
|
"Error": "Chyba",
|
||||||
"Failed": "Selhalo",
|
"Failed": "Selhalo",
|
||||||
"FeatureRequests": "Žádosti o funkce",
|
"FeatureRequests": "Žádosti o funkce",
|
||||||
@@ -127,8 +114,6 @@
|
|||||||
"ApplyTags": "Použít značky",
|
"ApplyTags": "Použít značky",
|
||||||
"MoreInfo": "Více informací",
|
"MoreInfo": "Více informací",
|
||||||
"System": "Systém",
|
"System": "Systém",
|
||||||
"EnableAutomaticSearchHelpTextWarning": "Bude použito při použití interaktivního vyhledávání",
|
|
||||||
"EnableColorImpairedMode": "Aktivujte režim se sníženou barevností",
|
|
||||||
"Enabled": "Povoleno",
|
"Enabled": "Povoleno",
|
||||||
"IgnoredAddresses": "Ignorované adresy",
|
"IgnoredAddresses": "Ignorované adresy",
|
||||||
"AcceptConfirmationModal": "Přijměte potvrzení Modal",
|
"AcceptConfirmationModal": "Přijměte potvrzení Modal",
|
||||||
@@ -138,16 +123,7 @@
|
|||||||
"LaunchBrowserHelpText": " Otevřete webový prohlížeč a při spuštění aplikace přejděte na domovskou stránku Radarr.",
|
"LaunchBrowserHelpText": " Otevřete webový prohlížeč a při spuštění aplikace přejděte na domovskou stránku Radarr.",
|
||||||
"Logging": "Protokolování",
|
"Logging": "Protokolování",
|
||||||
"Mechanism": "Mechanismus",
|
"Mechanism": "Mechanismus",
|
||||||
"MinutesNinety": "90 minut: {0}",
|
|
||||||
"MinutesSixty": "60 minut: {0}",
|
|
||||||
"MonoTlsCheckMessage": "Řešení Radarr Mono 4.x tls je stále povoleno, zvažte odebrání MONO_TLS_PROVIDER = možnost staršího prostředí",
|
|
||||||
"MonoVersionCheckUpgradeRecommendedMessage": "Aktuálně nainstalovaná mono verze {0} je podporována, ale doporučuje se upgradovat na {1}.",
|
|
||||||
"MovieDetailsNextMovie": "Detaily filmu: Další film",
|
|
||||||
"MovieDetailsPreviousMovie": "Detaily filmu: Předchozí film",
|
|
||||||
"Movies": "Filmy",
|
|
||||||
"NoLimitForAnyRuntime": "Žádné omezení za běhu",
|
|
||||||
"NoLinks": "Žádné odkazy",
|
"NoLinks": "Žádné odkazy",
|
||||||
"PreferredSize": "Preferovaná velikost",
|
|
||||||
"Presets": "Předvolby",
|
"Presets": "Předvolby",
|
||||||
"Priority": "Přednost",
|
"Priority": "Přednost",
|
||||||
"PriorityHelpText": "Upřednostněte více klientů pro stahování. Round-Robin se používá pro klienty se stejnou prioritou.",
|
"PriorityHelpText": "Upřednostněte více klientů pro stahování. Round-Robin se používá pro klienty se stejnou prioritou.",
|
||||||
@@ -160,7 +136,6 @@
|
|||||||
"ProxyPasswordHelpText": "Musíte pouze zadat uživatelské jméno a heslo, pokud je požadováno. Jinak je nechte prázdné.",
|
"ProxyPasswordHelpText": "Musíte pouze zadat uživatelské jméno a heslo, pokud je požadováno. Jinak je nechte prázdné.",
|
||||||
"ProxyUsernameHelpText": "Musíte pouze zadat uživatelské jméno a heslo, pokud je požadováno. Jinak je nechte prázdné.",
|
"ProxyUsernameHelpText": "Musíte pouze zadat uživatelské jméno a heslo, pokud je požadováno. Jinak je nechte prázdné.",
|
||||||
"PtpOldSettingsCheckMessage": "Následující indexovače PassThePopcorn mají zastaralá nastavení a měla by být aktualizována: {0}",
|
"PtpOldSettingsCheckMessage": "Následující indexovače PassThePopcorn mají zastaralá nastavení a měla by být aktualizována: {0}",
|
||||||
"QualitySettings": "Nastavení kvality",
|
|
||||||
"Queue": "Fronta",
|
"Queue": "Fronta",
|
||||||
"ReadTheWikiForMoreInformation": "Další informace najdete na Wiki",
|
"ReadTheWikiForMoreInformation": "Další informace najdete na Wiki",
|
||||||
"Refresh": "Obnovit",
|
"Refresh": "Obnovit",
|
||||||
@@ -193,7 +168,6 @@
|
|||||||
"UnableToAddANewIndexerPleaseTryAgain": "Nelze přidat nový indexer, zkuste to znovu.",
|
"UnableToAddANewIndexerPleaseTryAgain": "Nelze přidat nový indexer, zkuste to znovu.",
|
||||||
"UnableToAddANewIndexerProxyPleaseTryAgain": "Nelze přidat nový indexer, zkuste to znovu.",
|
"UnableToAddANewIndexerProxyPleaseTryAgain": "Nelze přidat nový indexer, zkuste to znovu.",
|
||||||
"UnableToLoadNotifications": "Nelze načíst oznámení",
|
"UnableToLoadNotifications": "Nelze načíst oznámení",
|
||||||
"UnableToLoadQualityDefinitions": "Nelze načíst definice kvality",
|
|
||||||
"UpdateCheckStartupTranslocationMessage": "Aktualizaci nelze nainstalovat, protože spouštěcí složka „{0}“ je ve složce Translocation aplikace.",
|
"UpdateCheckStartupTranslocationMessage": "Aktualizaci nelze nainstalovat, protože spouštěcí složka „{0}“ je ve složce Translocation aplikace.",
|
||||||
"UpdateCheckUINotWritableMessage": "Aktualizaci nelze nainstalovat, protože uživatelská složka „{0}“ není zapisovatelná uživatelem „{1}“.",
|
"UpdateCheckUINotWritableMessage": "Aktualizaci nelze nainstalovat, protože uživatelská složka „{0}“ není zapisovatelná uživatelem „{1}“.",
|
||||||
"UpdateMechanismHelpText": "Použijte vestavěný aktualizátor Radarr nebo skript",
|
"UpdateMechanismHelpText": "Použijte vestavěný aktualizátor Radarr nebo skript",
|
||||||
@@ -226,7 +200,6 @@
|
|||||||
"Restart": "Restartujte",
|
"Restart": "Restartujte",
|
||||||
"RestartNow": "Restartovat nyní",
|
"RestartNow": "Restartovat nyní",
|
||||||
"RestoreBackup": "Obnovit zálohu",
|
"RestoreBackup": "Obnovit zálohu",
|
||||||
"Restrictions": "Omezení",
|
|
||||||
"RSSIsNotSupportedWithThisIndexer": "RSS není u tohoto indexeru podporováno",
|
"RSSIsNotSupportedWithThisIndexer": "RSS není u tohoto indexeru podporováno",
|
||||||
"Save": "Uložit",
|
"Save": "Uložit",
|
||||||
"SSLCertPasswordHelpText": "Heslo pro soubor pfx",
|
"SSLCertPasswordHelpText": "Heslo pro soubor pfx",
|
||||||
@@ -235,8 +208,6 @@
|
|||||||
"UnableToLoadBackups": "Nelze načíst zálohy",
|
"UnableToLoadBackups": "Nelze načíst zálohy",
|
||||||
"UnableToLoadDownloadClients": "Nelze načíst klienty pro stahování",
|
"UnableToLoadDownloadClients": "Nelze načíst klienty pro stahování",
|
||||||
"UnableToLoadGeneralSettings": "Nelze načíst obecná nastavení",
|
"UnableToLoadGeneralSettings": "Nelze načíst obecná nastavení",
|
||||||
"DeleteIndexer": "Odstranit indexer",
|
|
||||||
"DeleteIndexerMessageText": "Opravdu chcete odstranit indexer „{0}“?",
|
|
||||||
"DeleteNotification": "Smazat oznámení",
|
"DeleteNotification": "Smazat oznámení",
|
||||||
"EnableAutomaticSearch": "Povolit automatické vyhledávání",
|
"EnableAutomaticSearch": "Povolit automatické vyhledávání",
|
||||||
"EnableInteractiveSearchHelpText": "Bude použito při použití interaktivního vyhledávání",
|
"EnableInteractiveSearchHelpText": "Bude použito při použití interaktivního vyhledávání",
|
||||||
@@ -245,8 +216,6 @@
|
|||||||
"Interval": "Interval",
|
"Interval": "Interval",
|
||||||
"KeyboardShortcuts": "Klávesové zkratky",
|
"KeyboardShortcuts": "Klávesové zkratky",
|
||||||
"Language": "Jazyk",
|
"Language": "Jazyk",
|
||||||
"MinimumLimits": "Minimální limity",
|
|
||||||
"MinutesHundredTwenty": "120 minut: {0}",
|
|
||||||
"NoLogFiles": "Žádné soubory protokolu",
|
"NoLogFiles": "Žádné soubory protokolu",
|
||||||
"NoBackupsAreAvailable": "Nejsou k dispozici žádné zálohy",
|
"NoBackupsAreAvailable": "Nejsou k dispozici žádné zálohy",
|
||||||
"NoChanges": "Žádné změny",
|
"NoChanges": "Žádné změny",
|
||||||
@@ -278,7 +247,6 @@
|
|||||||
"ChangeHasNotBeenSavedYet": "Změna ještě nebyla uložena",
|
"ChangeHasNotBeenSavedYet": "Změna ještě nebyla uložena",
|
||||||
"Clear": "Průhledná",
|
"Clear": "Průhledná",
|
||||||
"ClientPriority": "Priorita klienta",
|
"ClientPriority": "Priorita klienta",
|
||||||
"CloneIndexer": "Klonovat indexátor",
|
|
||||||
"CloneProfile": "Klonovat profil",
|
"CloneProfile": "Klonovat profil",
|
||||||
"Close": "Zavřít",
|
"Close": "Zavřít",
|
||||||
"ConnectionLostAutomaticMessage": "Radarr se pokusí připojit automaticky, nebo můžete kliknout na znovu načíst níže.",
|
"ConnectionLostAutomaticMessage": "Radarr se pokusí připojit automaticky, nebo můžete kliknout na znovu načíst níže.",
|
||||||
@@ -287,7 +255,6 @@
|
|||||||
"Date": "datum",
|
"Date": "datum",
|
||||||
"Dates": "Termíny",
|
"Dates": "Termíny",
|
||||||
"DBMigration": "Migrace databáze",
|
"DBMigration": "Migrace databáze",
|
||||||
"DelayProfile": "Zpožděný profil",
|
|
||||||
"Delete": "Vymazat",
|
"Delete": "Vymazat",
|
||||||
"DeleteApplicationMessageText": "Opravdu chcete smazat oznámení „{0}“?",
|
"DeleteApplicationMessageText": "Opravdu chcete smazat oznámení „{0}“?",
|
||||||
"DeleteBackup": "Odstranit zálohu",
|
"DeleteBackup": "Odstranit zálohu",
|
||||||
@@ -296,25 +263,18 @@
|
|||||||
"DeleteTagMessageText": "Opravdu chcete smazat značku „{0}“?",
|
"DeleteTagMessageText": "Opravdu chcete smazat značku „{0}“?",
|
||||||
"Discord": "Svár",
|
"Discord": "Svár",
|
||||||
"DownloadClient": "Stáhnout klienta",
|
"DownloadClient": "Stáhnout klienta",
|
||||||
"DownloadClientCheckNoneAvailableMessage": "Není k dispozici žádný klient pro stahování",
|
|
||||||
"DownloadClientCheckUnableToCommunicateMessage": "S uživatelem {0} nelze komunikovat.",
|
|
||||||
"DownloadClients": "Stáhnout klienty",
|
"DownloadClients": "Stáhnout klienty",
|
||||||
"Edit": "Upravit",
|
"Edit": "Upravit",
|
||||||
"Enable": "Umožnit",
|
"Enable": "Umožnit",
|
||||||
"EnableAutoHelpText": "Pokud je tato možnost povolena, filmy budou automaticky přidány do Radarru z tohoto seznamu",
|
|
||||||
"EnableAutomaticSearchHelpText": "Použije se, když se automatické vyhledávání provádí pomocí uživatelského rozhraní nebo Radarr",
|
"EnableAutomaticSearchHelpText": "Použije se, když se automatické vyhledávání provádí pomocí uživatelského rozhraní nebo Radarr",
|
||||||
"EnableInteractiveSearch": "Povolit interaktivní vyhledávání",
|
"EnableInteractiveSearch": "Povolit interaktivní vyhledávání",
|
||||||
"EnableInteractiveSearchHelpTextWarning": "Vyhledávání není u tohoto indexeru podporováno",
|
|
||||||
"EnableMediaInfoHelpText": "Extrahujte ze souborů informace o videu, jako je rozlišení, runtime a informace o kodeku. To vyžaduje, aby Radarr četl části souboru, což může během skenování způsobit vysokou aktivitu disku nebo sítě.",
|
|
||||||
"EnableSSL": "Povolit SSL",
|
"EnableSSL": "Povolit SSL",
|
||||||
"EnableSslHelpText": " Vyžaduje restartování spuštěné jako správce, aby se projevilo",
|
"EnableSslHelpText": " Vyžaduje restartování spuštěné jako správce, aby se projevilo",
|
||||||
"Events": "Události",
|
"Events": "Události",
|
||||||
"EventType": "Typ události",
|
"EventType": "Typ události",
|
||||||
"Exception": "Výjimka",
|
"Exception": "Výjimka",
|
||||||
"ExistingMovies": "Stávající filmy",
|
|
||||||
"ExistingTag": "Stávající značka",
|
"ExistingTag": "Stávající značka",
|
||||||
"IllRestartLater": "Restartuji později",
|
"IllRestartLater": "Restartuji později",
|
||||||
"Importing": "Import",
|
|
||||||
"IndexerLongTermStatusCheckSingleClientMessage": "Indexery nedostupné z důvodu selhání po dobu delší než 6 hodin: {0}",
|
"IndexerLongTermStatusCheckSingleClientMessage": "Indexery nedostupné z důvodu selhání po dobu delší než 6 hodin: {0}",
|
||||||
"IndexerStatusCheckSingleClientMessage": "Indexery nedostupné z důvodu selhání: {0}",
|
"IndexerStatusCheckSingleClientMessage": "Indexery nedostupné z důvodu selhání: {0}",
|
||||||
"SettingsTimeFormat": "Časový formát",
|
"SettingsTimeFormat": "Časový formát",
|
||||||
@@ -335,7 +295,6 @@
|
|||||||
"PageSizeHelpText": "Počet položek, které se mají zobrazit na každé stránce",
|
"PageSizeHelpText": "Počet položek, které se mají zobrazit na každé stránce",
|
||||||
"Password": "Heslo",
|
"Password": "Heslo",
|
||||||
"Peers": "Vrstevníci",
|
"Peers": "Vrstevníci",
|
||||||
"Pending": "čekající",
|
|
||||||
"PendingChangesDiscardChanges": "Zahodit změny a odejít",
|
"PendingChangesDiscardChanges": "Zahodit změny a odejít",
|
||||||
"PortNumber": "Číslo portu",
|
"PortNumber": "Číslo portu",
|
||||||
"Result": "Výsledek",
|
"Result": "Výsledek",
|
||||||
@@ -357,5 +316,14 @@
|
|||||||
"UISettings": "Nastavení uživatelského rozhraní",
|
"UISettings": "Nastavení uživatelského rozhraní",
|
||||||
"UnableToLoadUISettings": "Nelze načíst nastavení uživatelského rozhraní",
|
"UnableToLoadUISettings": "Nelze načíst nastavení uživatelského rozhraní",
|
||||||
"UnsavedChanges": "Neuložené změny",
|
"UnsavedChanges": "Neuložené změny",
|
||||||
"UpdateAutomaticallyHelpText": "Automaticky stahovat a instalovat aktualizace. Stále budete moci instalovat ze systému: Aktualizace"
|
"UpdateAutomaticallyHelpText": "Automaticky stahovat a instalovat aktualizace. Stále budete moci instalovat ze systému: Aktualizace",
|
||||||
|
"NetCore": ".NET Core",
|
||||||
|
"Filters": "Filtr",
|
||||||
|
"ConnectionLostMessage": "Radarr ztratil spojení s back-endem a pro obnovení funkčnosti bude nutné jej znovu načíst.",
|
||||||
|
"HistoryCleanupDaysHelpText": "Nastavením na 0 zakážete automatické čištění",
|
||||||
|
"HistoryCleanupDaysHelpTextWarning": "Soubory v koši starší než vybraný počet dní budou automaticky vyčištěny",
|
||||||
|
"MaintenanceRelease": "Údržbové vydání: opravy chyb a další vylepšení. Další podrobnosti najdete v GitHub Commit History",
|
||||||
|
"OnGrab": "Chyť",
|
||||||
|
"OnHealthIssue": "K otázce zdraví",
|
||||||
|
"TestAllIndexers": "Vyzkoušejte všechny indexery"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"LastWriteTime": "Sidste Skrive Tid",
|
"LastWriteTime": "Sidste Skrive Tid",
|
||||||
"Languages": "Sprog",
|
|
||||||
"Language": "Sprog",
|
"Language": "Sprog",
|
||||||
"KeyboardShortcuts": "Keyboard Genveje",
|
"KeyboardShortcuts": "Keyboard Genveje",
|
||||||
"Info": "Information",
|
"Info": "Information",
|
||||||
@@ -26,8 +25,6 @@
|
|||||||
"DownloadClientStatusCheckAllClientMessage": "Alle download klienter er utilgængelige på grund af fejl",
|
"DownloadClientStatusCheckAllClientMessage": "Alle download klienter er utilgængelige på grund af fejl",
|
||||||
"DownloadClientsSettingsSummary": "Download klienter, download håndtering og remote path mappings",
|
"DownloadClientsSettingsSummary": "Download klienter, download håndtering og remote path mappings",
|
||||||
"DownloadClients": "Download Klienter",
|
"DownloadClients": "Download Klienter",
|
||||||
"DownloadClientCheckUnableToCommunicateMessage": "Ude af stand til at kommunikere med {0}.",
|
|
||||||
"DownloadClientCheckNoneAvailableMessage": "Ingen download klient tilgængelig",
|
|
||||||
"DownloadClient": "Download Klient",
|
"DownloadClient": "Download Klient",
|
||||||
"Details": "Detaljer",
|
"Details": "Detaljer",
|
||||||
"Delete": "Slet",
|
"Delete": "Slet",
|
||||||
@@ -59,10 +56,7 @@
|
|||||||
"Add": "Tilføj",
|
"Add": "Tilføj",
|
||||||
"AddDownloadClient": "Tilføj downloadklient",
|
"AddDownloadClient": "Tilføj downloadklient",
|
||||||
"DBMigration": "DB Migration",
|
"DBMigration": "DB Migration",
|
||||||
"DelayProfile": "Udskyd Profiler",
|
|
||||||
"MIA": "MIA",
|
"MIA": "MIA",
|
||||||
"MovieDetailsNextMovie": "Filmoplysninger: Næste film",
|
|
||||||
"MovieDetailsPreviousMovie": "Filmdetaljer: Forrige film",
|
|
||||||
"ResetAPIKey": "Nulstil API-nøgle",
|
"ResetAPIKey": "Nulstil API-nøgle",
|
||||||
"SettingsTimeFormat": "Tidsformat",
|
"SettingsTimeFormat": "Tidsformat",
|
||||||
"SystemTimeCheckMessage": "Systemtiden er slukket mere end 1 dag. Planlagte opgaver kører muligvis ikke korrekt, før tiden er rettet",
|
"SystemTimeCheckMessage": "Systemtiden er slukket mere end 1 dag. Planlagte opgaver kører muligvis ikke korrekt, før tiden er rettet",
|
||||||
@@ -109,13 +103,10 @@
|
|||||||
"BypassProxyForLocalAddresses": "Bypass-proxy til lokale adresser",
|
"BypassProxyForLocalAddresses": "Bypass-proxy til lokale adresser",
|
||||||
"CancelPendingTask": "Er du sikker på, at du vil annullere denne afventende opgave?",
|
"CancelPendingTask": "Er du sikker på, at du vil annullere denne afventende opgave?",
|
||||||
"CertificateValidation": "Validering af certifikat",
|
"CertificateValidation": "Validering af certifikat",
|
||||||
"CloneIndexer": "Klonindekser",
|
|
||||||
"CloseCurrentModal": "Luk Nuværende Modal",
|
"CloseCurrentModal": "Luk Nuværende Modal",
|
||||||
"CouldNotConnectSignalR": "Kunne ikke oprette forbindelse til SignalR, UI opdateres ikke",
|
"CouldNotConnectSignalR": "Kunne ikke oprette forbindelse til SignalR, UI opdateres ikke",
|
||||||
"DeleteApplicationMessageText": "Er du sikker på, at du vil slette underretningen '{0}'?",
|
"DeleteApplicationMessageText": "Er du sikker på, at du vil slette underretningen '{0}'?",
|
||||||
"DeleteDownloadClientMessageText": "Er du sikker på, at du vil slette downloadklienten '{0}'?",
|
"DeleteDownloadClientMessageText": "Er du sikker på, at du vil slette downloadklienten '{0}'?",
|
||||||
"DeleteIndexer": "Slet Indexer",
|
|
||||||
"DeleteIndexerMessageText": "Er du sikker på, at du vil slette indeksøren '{0}'?",
|
|
||||||
"DeleteIndexerProxyMessageText": "Er du sikker på, at du vil slette tagget '{0}'?",
|
"DeleteIndexerProxyMessageText": "Er du sikker på, at du vil slette tagget '{0}'?",
|
||||||
"DeleteNotification": "Slet underretning",
|
"DeleteNotification": "Slet underretning",
|
||||||
"DeleteNotificationMessageText": "Er du sikker på, at du vil slette underretningen '{0}'?",
|
"DeleteNotificationMessageText": "Er du sikker på, at du vil slette underretningen '{0}'?",
|
||||||
@@ -125,27 +116,16 @@
|
|||||||
"Docker": "Docker",
|
"Docker": "Docker",
|
||||||
"Donations": "Donationer",
|
"Donations": "Donationer",
|
||||||
"DownloadClientSettings": "Download klientindstillinger",
|
"DownloadClientSettings": "Download klientindstillinger",
|
||||||
"DownloadClientUnavailable": "Downloadklienten er ikke tilgængelig",
|
|
||||||
"Downloading": "Downloader",
|
|
||||||
"EditIndexer": "Rediger indekser",
|
"EditIndexer": "Rediger indekser",
|
||||||
"Enable": "Aktiver",
|
"Enable": "Aktiver",
|
||||||
"EnableAutoHelpText": "Hvis det er aktiveret, føjes film automatisk til Radarr fra denne liste",
|
|
||||||
"EnableAutomaticAdd": "Aktivér automatisk tilføjelse",
|
|
||||||
"EnableAutomaticSearch": "Aktivér automatisk søgning",
|
"EnableAutomaticSearch": "Aktivér automatisk søgning",
|
||||||
"EnableAutomaticSearchHelpText": "Bruges, når der foretages automatiske søgninger via brugergrænsefladen eller af Radarr",
|
"EnableAutomaticSearchHelpText": "Bruges, når der foretages automatiske søgninger via brugergrænsefladen eller af Radarr",
|
||||||
"EnableAutomaticSearchHelpTextWarning": "Bruges, når der bruges interaktiv søgning",
|
|
||||||
"EnableColorImpairedMode": "Aktivér farve-nedsat tilstand",
|
|
||||||
"EnableColorImpairedModeHelpText": "Ændret stil for at give farvehæmmede brugere bedre at skelne mellem farvekodede oplysninger",
|
|
||||||
"EnableCompletedDownloadHandlingHelpText": "Importer automatisk afsluttede downloads fra downloadklienten",
|
|
||||||
"Enabled": "Aktiveret",
|
"Enabled": "Aktiveret",
|
||||||
"EnableHelpText": "Aktivér oprettelse af metadatafiler for denne metadatatype",
|
|
||||||
"EnableInteractiveSearch": "Aktivér interaktiv søgning",
|
"EnableInteractiveSearch": "Aktivér interaktiv søgning",
|
||||||
"EnableMediaInfoHelpText": "Uddrag videoinformation såsom opløsning, runtime og codec-oplysninger fra filer. Dette kræver, at Radarr læser dele af filen, som kan forårsage høj disk- eller netværksaktivitet under scanninger.",
|
|
||||||
"EnableRss": "Aktivér RSS",
|
"EnableRss": "Aktivér RSS",
|
||||||
"EnableSslHelpText": " Kræver genstart, der kører som administrator for at træde i kraft",
|
"EnableSslHelpText": " Kræver genstart, der kører som administrator for at træde i kraft",
|
||||||
"ErrorLoadingContents": "Fejl ved indlæsning af indhold",
|
"ErrorLoadingContents": "Fejl ved indlæsning af indhold",
|
||||||
"Exception": "Undtagelse",
|
"Exception": "Undtagelse",
|
||||||
"ExistingMovies": "Eksisterende film",
|
|
||||||
"ExistingTag": "Eksisterende mærke",
|
"ExistingTag": "Eksisterende mærke",
|
||||||
"FeatureRequests": "Funktionsanmodninger",
|
"FeatureRequests": "Funktionsanmodninger",
|
||||||
"Filter": "Filter",
|
"Filter": "Filter",
|
||||||
@@ -158,7 +138,6 @@
|
|||||||
"Hostname": "Værtsnavn",
|
"Hostname": "Værtsnavn",
|
||||||
"IgnoredAddresses": "Ignorerede adresser",
|
"IgnoredAddresses": "Ignorerede adresser",
|
||||||
"IllRestartLater": "Jeg genstarter senere",
|
"IllRestartLater": "Jeg genstarter senere",
|
||||||
"Importing": "Importerer",
|
|
||||||
"IncludeHealthWarningsHelpText": "Inkluder sundhedsadvarsler",
|
"IncludeHealthWarningsHelpText": "Inkluder sundhedsadvarsler",
|
||||||
"Indexer": "Indekser",
|
"Indexer": "Indekser",
|
||||||
"IndexerLongTermStatusCheckAllClientMessage": "Alle indeksatorer er ikke tilgængelige på grund af fejl i mere end 6 timer",
|
"IndexerLongTermStatusCheckAllClientMessage": "Alle indeksatorer er ikke tilgængelige på grund af fejl i mere end 6 timer",
|
||||||
@@ -173,17 +152,9 @@
|
|||||||
"LogLevelTraceHelpTextWarning": "Sporlogning bør kun aktiveres midlertidigt",
|
"LogLevelTraceHelpTextWarning": "Sporlogning bør kun aktiveres midlertidigt",
|
||||||
"Logs": "Logfiler",
|
"Logs": "Logfiler",
|
||||||
"Manual": "brugervejledning",
|
"Manual": "brugervejledning",
|
||||||
"MaximumLimits": "Maksimale grænser",
|
|
||||||
"Mechanism": "Mekanisme",
|
"Mechanism": "Mekanisme",
|
||||||
"Message": "Besked",
|
"Message": "Besked",
|
||||||
"MinimumLimits": "Minimumsgrænser",
|
|
||||||
"MinutesHundredTwenty": "120 minutter: {0}",
|
|
||||||
"MinutesNinety": "90 minutter: {0}",
|
|
||||||
"MinutesSixty": "60 minutter: {0}",
|
|
||||||
"Mode": "Mode",
|
"Mode": "Mode",
|
||||||
"MonoTlsCheckMessage": "Radarr Mono 4.x tls-løsning er stadig aktiveret, overvej at fjerne MONO_TLS_PROVIDER = ældre miljømulighed",
|
|
||||||
"MonoVersion": "Mono-version",
|
|
||||||
"MonoVersionCheckUpgradeRecommendedMessage": "Aktuelt installeret Mono-version {0} understøttes, men det anbefales at opgradere til {1}.",
|
|
||||||
"MovieIndexScrollBottom": "Filmindeks: Rul ned",
|
"MovieIndexScrollBottom": "Filmindeks: Rul ned",
|
||||||
"MovieIndexScrollTop": "Filmindeks: Scroll Top",
|
"MovieIndexScrollTop": "Filmindeks: Scroll Top",
|
||||||
"Name": "Navn",
|
"Name": "Navn",
|
||||||
@@ -191,23 +162,19 @@
|
|||||||
"NoBackupsAreAvailable": "Ingen sikkerhedskopier er tilgængelige",
|
"NoBackupsAreAvailable": "Ingen sikkerhedskopier er tilgængelige",
|
||||||
"NoChanges": "Ingen ændringer",
|
"NoChanges": "Ingen ændringer",
|
||||||
"NoLeaveIt": "Nej, lad det være",
|
"NoLeaveIt": "Nej, lad det være",
|
||||||
"NoLimitForAnyRuntime": "Ingen grænse for nogen runtime",
|
|
||||||
"NoLinks": "Ingen links",
|
"NoLinks": "Ingen links",
|
||||||
"NoLogFiles": "Ingen logfiler",
|
"NoLogFiles": "Ingen logfiler",
|
||||||
"NoMinimumForAnyRuntime": "Intet minimum for enhver driftstid",
|
|
||||||
"NoTagsHaveBeenAddedYet": "Der er ikke tilføjet nogen tags endnu",
|
"NoTagsHaveBeenAddedYet": "Der er ikke tilføjet nogen tags endnu",
|
||||||
"NotificationTriggers": "Meddelelsesudløsere",
|
"NotificationTriggers": "Meddelelsesudløsere",
|
||||||
"OnHealthIssueHelpText": "Om sundhedsspørgsmål",
|
"OnHealthIssueHelpText": "Om sundhedsspørgsmål",
|
||||||
"OpenThisModal": "Åbn denne modal",
|
"OpenThisModal": "Åbn denne modal",
|
||||||
"PackageVersion": "Pakkeversion",
|
"PackageVersion": "Pakkeversion",
|
||||||
"PageSize": "Sidestørrelse",
|
"PageSize": "Sidestørrelse",
|
||||||
"Pending": "Verserende",
|
|
||||||
"PendingChangesDiscardChanges": "Kassér ændringer og gå ud",
|
"PendingChangesDiscardChanges": "Kassér ændringer og gå ud",
|
||||||
"PendingChangesMessage": "Du har ikke gemte ændringer. Er du sikker på, at du vil forlade denne side?",
|
"PendingChangesMessage": "Du har ikke gemte ændringer. Er du sikker på, at du vil forlade denne side?",
|
||||||
"PendingChangesStayReview": "Bliv og gennemgå ændringer",
|
"PendingChangesStayReview": "Bliv og gennemgå ændringer",
|
||||||
"Port": "Havn",
|
"Port": "Havn",
|
||||||
"PortNumber": "Portnummer",
|
"PortNumber": "Portnummer",
|
||||||
"PreferredSize": "Foretrukken størrelse",
|
|
||||||
"Priority": "Prioritet",
|
"Priority": "Prioritet",
|
||||||
"Protocol": "Protokol",
|
"Protocol": "Protokol",
|
||||||
"ProxyBypassFilterHelpText": "Brug ',' som en separator og '*.' som et jokertegn for underdomæner",
|
"ProxyBypassFilterHelpText": "Brug ',' som en separator og '*.' som et jokertegn for underdomæner",
|
||||||
@@ -217,8 +184,6 @@
|
|||||||
"ProxyPasswordHelpText": "Du skal kun indtaste et brugernavn og en adgangskode, hvis der kræves et. Lad dem være tomme ellers.",
|
"ProxyPasswordHelpText": "Du skal kun indtaste et brugernavn og en adgangskode, hvis der kræves et. Lad dem være tomme ellers.",
|
||||||
"ProxyUsernameHelpText": "Du skal kun indtaste et brugernavn og en adgangskode, hvis der kræves et. Lad dem være tomme ellers.",
|
"ProxyUsernameHelpText": "Du skal kun indtaste et brugernavn og en adgangskode, hvis der kræves et. Lad dem være tomme ellers.",
|
||||||
"PtpOldSettingsCheckMessage": "Følgende PassThePopcorn-indeksatorer har forældede indstillinger og skal opdateres: {0}",
|
"PtpOldSettingsCheckMessage": "Følgende PassThePopcorn-indeksatorer har forældede indstillinger og skal opdateres: {0}",
|
||||||
"QualityDefinitions": "Kvalitetsdefinitioner",
|
|
||||||
"QualitySettings": "Kvalitetsindstillinger",
|
|
||||||
"ReadTheWikiForMoreInformation": "Læs Wiki for mere information",
|
"ReadTheWikiForMoreInformation": "Læs Wiki for mere information",
|
||||||
"Reddit": "Reddit",
|
"Reddit": "Reddit",
|
||||||
"Refresh": "Opdater",
|
"Refresh": "Opdater",
|
||||||
@@ -275,7 +240,6 @@
|
|||||||
"UnableToAddANewIndexerPleaseTryAgain": "Kunne ikke tilføje en ny indekser. Prøv igen.",
|
"UnableToAddANewIndexerPleaseTryAgain": "Kunne ikke tilføje en ny indekser. Prøv igen.",
|
||||||
"UnableToLoadBackups": "Kunne ikke indlæse sikkerhedskopier",
|
"UnableToLoadBackups": "Kunne ikke indlæse sikkerhedskopier",
|
||||||
"UnableToLoadGeneralSettings": "Kan ikke indlæse generelle indstillinger",
|
"UnableToLoadGeneralSettings": "Kan ikke indlæse generelle indstillinger",
|
||||||
"UnableToLoadIndexers": "Kan ikke indlæse indeksatorer",
|
|
||||||
"UnableToLoadNotifications": "Kunne ikke indlæse meddelelser",
|
"UnableToLoadNotifications": "Kunne ikke indlæse meddelelser",
|
||||||
"UnableToLoadTags": "Kan ikke indlæse tags",
|
"UnableToLoadTags": "Kan ikke indlæse tags",
|
||||||
"UnableToLoadUISettings": "UI-indstillingerne kunne ikke indlæses",
|
"UnableToLoadUISettings": "UI-indstillingerne kunne ikke indlæses",
|
||||||
@@ -297,7 +261,6 @@
|
|||||||
"ReleaseStatus": "Frigør status",
|
"ReleaseStatus": "Frigør status",
|
||||||
"Restore": "Gendan",
|
"Restore": "Gendan",
|
||||||
"RestoreBackup": "Gendan sikkerhedskopi",
|
"RestoreBackup": "Gendan sikkerhedskopi",
|
||||||
"Restrictions": "Begrænsninger",
|
|
||||||
"RSS": "RSS",
|
"RSS": "RSS",
|
||||||
"RSSIsNotSupportedWithThisIndexer": "RSS understøttes ikke med denne indekseringsenhed",
|
"RSSIsNotSupportedWithThisIndexer": "RSS understøttes ikke med denne indekseringsenhed",
|
||||||
"Save": "Gemme",
|
"Save": "Gemme",
|
||||||
@@ -310,12 +273,10 @@
|
|||||||
"StartupDirectory": "Startmappe",
|
"StartupDirectory": "Startmappe",
|
||||||
"Status": "Status",
|
"Status": "Status",
|
||||||
"UnableToLoadDownloadClients": "Kunne ikke indlæse downloadklienter",
|
"UnableToLoadDownloadClients": "Kunne ikke indlæse downloadklienter",
|
||||||
"UnableToLoadQualityDefinitions": "Kunne ikke indlæse kvalitetsdefinitioner",
|
|
||||||
"UpdateCheckStartupTranslocationMessage": "Kan ikke installere opdatering, fordi startmappen '{0}' er i en App Translocation-mappe.",
|
"UpdateCheckStartupTranslocationMessage": "Kan ikke installere opdatering, fordi startmappen '{0}' er i en App Translocation-mappe.",
|
||||||
"UpdateMechanismHelpText": "Brug Radarrs indbyggede opdatering eller et script",
|
"UpdateMechanismHelpText": "Brug Radarrs indbyggede opdatering eller et script",
|
||||||
"View": "Udsigt",
|
"View": "Udsigt",
|
||||||
"Warn": "Advare",
|
"Warn": "Advare",
|
||||||
"Movies": "Film",
|
|
||||||
"AddingTag": "Tilføjer tag",
|
"AddingTag": "Tilføjer tag",
|
||||||
"ApplicationStatusCheckAllClientMessage": "Alle lister er utilgængelige på grund af fejl",
|
"ApplicationStatusCheckAllClientMessage": "Alle lister er utilgængelige på grund af fejl",
|
||||||
"ApplicationStatusCheckSingleClientMessage": "Lister utilgængelige på grund af fejl: {0}",
|
"ApplicationStatusCheckSingleClientMessage": "Lister utilgængelige på grund af fejl: {0}",
|
||||||
@@ -340,7 +301,6 @@
|
|||||||
"ClientPriority": "Kundens prioritet",
|
"ClientPriority": "Kundens prioritet",
|
||||||
"CloneProfile": "Klonprofil",
|
"CloneProfile": "Klonprofil",
|
||||||
"EnableInteractiveSearchHelpText": "Bruges, når der bruges interaktiv søgning",
|
"EnableInteractiveSearchHelpText": "Bruges, når der bruges interaktiv søgning",
|
||||||
"EnableInteractiveSearchHelpTextWarning": "Søgning understøttes ikke med denne indekser",
|
|
||||||
"UISettings": "UI-indstillinger",
|
"UISettings": "UI-indstillinger",
|
||||||
"NoUpdatesAreAvailable": "Ingen opdateringer er tilgængelige",
|
"NoUpdatesAreAvailable": "Ingen opdateringer er tilgængelige",
|
||||||
"OAuthPopupMessage": "Pop-ups blokeres af din browser",
|
"OAuthPopupMessage": "Pop-ups blokeres af din browser",
|
||||||
@@ -357,9 +317,15 @@
|
|||||||
"AppDataDirectory": "AppData-bibliotek",
|
"AppDataDirectory": "AppData-bibliotek",
|
||||||
"CertificateValidationHelpText": "Skift, hvor streng HTTPS-certificering er",
|
"CertificateValidationHelpText": "Skift, hvor streng HTTPS-certificering er",
|
||||||
"ChangeHasNotBeenSavedYet": "Ændring er endnu ikke gemt",
|
"ChangeHasNotBeenSavedYet": "Ændring er endnu ikke gemt",
|
||||||
"EnabledHelpText": "Aktivér denne liste til brug i Radarr",
|
|
||||||
"ConnectSettings": "Forbind indstillinger",
|
"ConnectSettings": "Forbind indstillinger",
|
||||||
"DeleteBackup": "Slet sikkerhedskopi",
|
"DeleteBackup": "Slet sikkerhedskopi",
|
||||||
"DeleteBackupMessageText": "Er du sikker på, at du vil slette sikkerhedskopien '{0}'?",
|
"DeleteBackupMessageText": "Er du sikker på, at du vil slette sikkerhedskopien '{0}'?",
|
||||||
"DeleteDownloadClient": "Slet Download Client"
|
"DeleteDownloadClient": "Slet Download Client",
|
||||||
|
"MaintenanceRelease": "Vedligeholdelsesfrigivelse: fejlrettelser og andre forbedringer. Se Github Commit History for flere detaljer",
|
||||||
|
"Filters": "Filter",
|
||||||
|
"HistoryCleanupDaysHelpText": "Sæt til 0 for at deaktivere automatisk oprydning",
|
||||||
|
"HistoryCleanupDaysHelpTextWarning": "Filer i papirkurven, der er ældre end det valgte antal dage, renses automatisk",
|
||||||
|
"OnGrab": "On Grab",
|
||||||
|
"OnHealthIssue": "Om sundhedsspørgsmål",
|
||||||
|
"TestAllIndexers": "Test alle indeksører"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,16 +6,14 @@
|
|||||||
"Backup": "Backups",
|
"Backup": "Backups",
|
||||||
"BackupNow": "Jetzt sichern",
|
"BackupNow": "Jetzt sichern",
|
||||||
"Clear": "Leeren",
|
"Clear": "Leeren",
|
||||||
"Connect": "Verbindungen",
|
"Connect": "Benachrichtigungen",
|
||||||
"Connections": "Verbindungen",
|
"Connections": "Verbindungen",
|
||||||
"CustomFilters": "Filter anpassen",
|
"CustomFilters": "Filter anpassen",
|
||||||
"Date": "Datum",
|
"Date": "Datum",
|
||||||
"Dates": "Termine",
|
"Dates": "Termine",
|
||||||
"Delete": "Löschen",
|
"Delete": "Löschen",
|
||||||
"DownloadClientCheckNoneAvailableMessage": "Es ist kein Downloader verfügbar",
|
"DownloadClientStatusCheckAllClientMessage": "Alle Download Clients sind aufgrund von Fehlern nicht verfügbar",
|
||||||
"DownloadClientCheckUnableToCommunicateMessage": "Kommunikation mit {0} nicht möglich.",
|
"DownloadClientStatusCheckSingleClientMessage": "Download Clients aufgrund von Fehlern nicht verfügbar: {0}",
|
||||||
"DownloadClientStatusCheckAllClientMessage": "Alle Downloader sind aufgrund von Fehlern nicht verfügbar",
|
|
||||||
"DownloadClientStatusCheckSingleClientMessage": "Downloader aufgrund von Fehlern nicht verfügbar: {0}",
|
|
||||||
"DownloadClients": "Downloader",
|
"DownloadClients": "Downloader",
|
||||||
"Edit": "Bearbeiten",
|
"Edit": "Bearbeiten",
|
||||||
"Events": "Events",
|
"Events": "Events",
|
||||||
@@ -30,13 +28,9 @@
|
|||||||
"IndexerStatusCheckAllClientMessage": "Alle Indexer sind aufgrund von Fehlern nicht verfügbar",
|
"IndexerStatusCheckAllClientMessage": "Alle Indexer sind aufgrund von Fehlern nicht verfügbar",
|
||||||
"IndexerStatusCheckSingleClientMessage": "Indexer aufgrund von Fehlern nicht verfügbar: {0}",
|
"IndexerStatusCheckSingleClientMessage": "Indexer aufgrund von Fehlern nicht verfügbar: {0}",
|
||||||
"Indexers": "Indexer",
|
"Indexers": "Indexer",
|
||||||
"Languages": "Sprachen",
|
|
||||||
"LogFiles": "Protokolle",
|
"LogFiles": "Protokolle",
|
||||||
"Logging": "Protokollierung",
|
"Logging": "Protokollierung",
|
||||||
"MonoNotNetCoreCheckMessage": "Bitte aktualisieren Sie auf die .NET Core-Version von Prowlarr",
|
|
||||||
"MonoTlsCheckMessage": "Der Workaround für Prowlarr Mono 4.x TLS ist weiterhin aktiviert. Entferne möglicherweise die Option MONO_TLS_PROVIDER=legacy",
|
|
||||||
"MoreInfo": "Mehr Infos",
|
"MoreInfo": "Mehr Infos",
|
||||||
"Movies": "Filme",
|
|
||||||
"NoChange": "Keine Änderung",
|
"NoChange": "Keine Änderung",
|
||||||
"NoChanges": "Keine Änderungen",
|
"NoChanges": "Keine Änderungen",
|
||||||
"Options": "Optionen",
|
"Options": "Optionen",
|
||||||
@@ -45,13 +39,10 @@
|
|||||||
"ProxyCheckFailedToTestMessage": "Proxy konnte nicht getestet werden: {0}",
|
"ProxyCheckFailedToTestMessage": "Proxy konnte nicht getestet werden: {0}",
|
||||||
"ProxyCheckResolveIpMessage": "Fehler beim Auflösen der IP-Adresse für den konfigurierten Proxy-Host {0}",
|
"ProxyCheckResolveIpMessage": "Fehler beim Auflösen der IP-Adresse für den konfigurierten Proxy-Host {0}",
|
||||||
"PtpOldSettingsCheckMessage": "Die folgenden PassThePopcorn-Indexer haben veraltete Einstellungen und sollten aktualisiert werden: {0}",
|
"PtpOldSettingsCheckMessage": "Die folgenden PassThePopcorn-Indexer haben veraltete Einstellungen und sollten aktualisiert werden: {0}",
|
||||||
"QualityDefinitions": "Qualitätsdefinitionen",
|
|
||||||
"Queue": "Warteschlange",
|
"Queue": "Warteschlange",
|
||||||
"Refresh": "Aktualisieren",
|
"Refresh": "Aktualisieren",
|
||||||
"ReleaseBranchCheckOfficialBranchMessage": "Zweig {0} ist kein gültiger Prowlarr-Release-Zweig. Sie erhalten keine Updates",
|
"ReleaseBranchCheckOfficialBranchMessage": "Zweig {0} ist kein gültiger Prowlarr-Release-Zweig. Sie erhalten keine Updates",
|
||||||
"ReleaseBranchCheckPreviousVersionMessage": "Zweig {0} ist für eine frühere Version von Prowlarr. Setzen Sie den Zweig für weitere Aktualisierungen auf 'nightly'",
|
|
||||||
"RestoreBackup": "Backup einspielen",
|
"RestoreBackup": "Backup einspielen",
|
||||||
"Restrictions": "Beschränkungen",
|
|
||||||
"SaveChanges": "Änderungen speichern",
|
"SaveChanges": "Änderungen speichern",
|
||||||
"Scheduled": "Geplant",
|
"Scheduled": "Geplant",
|
||||||
"Search": "Suche",
|
"Search": "Suche",
|
||||||
@@ -74,16 +65,16 @@
|
|||||||
"Updates": "Updates",
|
"Updates": "Updates",
|
||||||
"View": "Ansicht",
|
"View": "Ansicht",
|
||||||
"Language": "Sprache",
|
"Language": "Sprache",
|
||||||
"UISettingsSummary": "Einstellungen für Kalender, Datumsformat und Farbbeeinträchtigung",
|
"UISettingsSummary": "Optionen für Datum, Sprache und Farbbeinträchtigungen",
|
||||||
"TagsSettingsSummary": "Alle Tags und deren Benutzung anzeigen. Unbenutzte Tags können entfernt werden",
|
"TagsSettingsSummary": "Alle Tags und deren Benutzung anzeigen. Unbenutzte Tags können entfernt werden",
|
||||||
"Size": "Größe",
|
"Size": "Größe",
|
||||||
"ReleaseStatus": "Releasestatus",
|
"ReleaseStatus": "Releasestatus",
|
||||||
"Protocol": "Protokoll",
|
"Protocol": "Protokoll",
|
||||||
"LastWriteTime": "Zuletzt beschrieben",
|
"LastWriteTime": "Zuletzt beschrieben",
|
||||||
"Indexer": "Indexer",
|
"Indexer": "Indexer",
|
||||||
"DownloadClientsSettingsSummary": "Downloader, Downloadverarbeitung und Remote-Pfadzuordnungen",
|
"DownloadClientsSettingsSummary": "Download der Client-Konfigurationen für die Integration in die Prowlarr UI-Suche",
|
||||||
"Grabbed": "Erfasste",
|
"Grabbed": "Erfasste",
|
||||||
"GeneralSettingsSummary": "Port, SSL, Benutzername/Passwort, Proxy, Statistik und Updates",
|
"GeneralSettingsSummary": "Port, SSL, Benutzername/Passwort, Proxy, Analytik und Updates",
|
||||||
"Filename": "Dateiname",
|
"Filename": "Dateiname",
|
||||||
"Failed": "Fehlgeschlagen",
|
"Failed": "Fehlgeschlagen",
|
||||||
"EventType": "Event Typ",
|
"EventType": "Event Typ",
|
||||||
@@ -140,11 +131,8 @@
|
|||||||
"PendingChangesStayReview": "Auf der Seite bleiben",
|
"PendingChangesStayReview": "Auf der Seite bleiben",
|
||||||
"PendingChangesMessage": "Es gibt noch ungespeicherte Änderungen, bist du sicher, dass du die Seite verlassen möchtest?",
|
"PendingChangesMessage": "Es gibt noch ungespeicherte Änderungen, bist du sicher, dass du die Seite verlassen möchtest?",
|
||||||
"PendingChangesDiscardChanges": "Änderungen verwerfen und schließen",
|
"PendingChangesDiscardChanges": "Änderungen verwerfen und schließen",
|
||||||
"MonoVersionCheckUpgradeRecommendedMessage": "Die momentane installierte Mono Version {0} wird unterstützt aber es wird empfohlen auf {1} zu updaten.",
|
|
||||||
"ExistingMovies": "Vorhandene Filme",
|
|
||||||
"UpdateAutomaticallyHelpText": "Updates automatisch herunteraden und installieren. Es kann weiterhin unter \"System -> Updates\" ein manuelles Update angestoßen werden",
|
"UpdateAutomaticallyHelpText": "Updates automatisch herunteraden und installieren. Es kann weiterhin unter \"System -> Updates\" ein manuelles Update angestoßen werden",
|
||||||
"UrlBaseHelpText": "Für Reverse-Proxy-Unterstützung. Die Standardeinstellung leer",
|
"UrlBaseHelpText": "Für Reverse-Proxy-Unterstützung. Die Standardeinstellung leer",
|
||||||
"EnableAutoHelpText": "Wenn aktiviert werden Filme dieser Liste automatisch hinzugefügt",
|
|
||||||
"AppDataDirectory": "AppData Ordner",
|
"AppDataDirectory": "AppData Ordner",
|
||||||
"ApplyTags": "Tags setzen",
|
"ApplyTags": "Tags setzen",
|
||||||
"Authentication": "Authentifizierung",
|
"Authentication": "Authentifizierung",
|
||||||
@@ -163,17 +151,11 @@
|
|||||||
"CloneProfile": "Profil kopieren",
|
"CloneProfile": "Profil kopieren",
|
||||||
"DeleteBackup": "Backup löschen",
|
"DeleteBackup": "Backup löschen",
|
||||||
"DeleteDownloadClient": "Downloader löschen",
|
"DeleteDownloadClient": "Downloader löschen",
|
||||||
"DeleteIndexer": "Indexer löschen",
|
|
||||||
"DeleteTag": "Tag löschen",
|
"DeleteTag": "Tag löschen",
|
||||||
"Docker": "Docker",
|
"Docker": "Docker",
|
||||||
"DownloadClientSettings": "Downloader Einstellungen",
|
"DownloadClientSettings": "Downloader Einstellungen",
|
||||||
"Enable": "Aktivieren",
|
"Enable": "Aktivieren",
|
||||||
"EnableAutomaticAdd": "Automatisch hinzufügen",
|
|
||||||
"EnableAutomaticSearch": "Automatisch suchen",
|
"EnableAutomaticSearch": "Automatisch suchen",
|
||||||
"EnableColorImpairedMode": "Farbbeeinträchtigter Modus aktivieren",
|
|
||||||
"EnableCompletedDownloadHandlingHelpText": "Importiere fertige Downloads vom Downloader automatisch",
|
|
||||||
"EnabledHelpText": "Aktiviere diese Liste",
|
|
||||||
"EnableHelpText": "Metadaten Dateien erstellen für diesen Metadata Typ",
|
|
||||||
"EnableInteractiveSearch": "Interaktive Suche",
|
"EnableInteractiveSearch": "Interaktive Suche",
|
||||||
"EnableSSL": "SSL",
|
"EnableSSL": "SSL",
|
||||||
"EnableSslHelpText": " Erfordert einen Neustart als Administrator",
|
"EnableSslHelpText": " Erfordert einen Neustart als Administrator",
|
||||||
@@ -182,21 +164,17 @@
|
|||||||
"Hostname": "Hostname",
|
"Hostname": "Hostname",
|
||||||
"IgnoredAddresses": "Ignorierte Adressen",
|
"IgnoredAddresses": "Ignorierte Adressen",
|
||||||
"IllRestartLater": "Später neustarten",
|
"IllRestartLater": "Später neustarten",
|
||||||
"Importing": "Importiere",
|
|
||||||
"IncludeHealthWarningsHelpText": "Zustandswarnung",
|
"IncludeHealthWarningsHelpText": "Zustandswarnung",
|
||||||
"IndexerFlags": "Indexer Flags",
|
"IndexerFlags": "Indexer-Flags",
|
||||||
"Interval": "Intervall",
|
"Interval": "Intervall",
|
||||||
"LogLevel": "Log Level",
|
"LogLevel": "Log Level",
|
||||||
"Logs": "Logs",
|
"Logs": "Protokolle",
|
||||||
"Mechanism": "Verfahren",
|
"Mechanism": "Verfahren",
|
||||||
"MIA": "MIA",
|
"MIA": "MIA",
|
||||||
"MinimumLimits": "Mindest Grenzen",
|
|
||||||
"Mode": "Modus",
|
"Mode": "Modus",
|
||||||
"MonoVersion": "Mono Version",
|
|
||||||
"NetCore": ".NET Core",
|
"NetCore": ".NET Core",
|
||||||
"New": "Neu",
|
"New": "Neu",
|
||||||
"NoLeaveIt": "Nein, nicht ändern",
|
"NoLeaveIt": "Nein, nicht ändern",
|
||||||
"NoLimitForAnyRuntime": "Keine Begrenzung der Laufzeiten",
|
|
||||||
"NotificationTriggers": "Benachrichtigungs Auslöser",
|
"NotificationTriggers": "Benachrichtigungs Auslöser",
|
||||||
"OnHealthIssueHelpText": "Zustandsproblem",
|
"OnHealthIssueHelpText": "Zustandsproblem",
|
||||||
"OpenBrowserOnStart": "Browser beim Start öffnen",
|
"OpenBrowserOnStart": "Browser beim Start öffnen",
|
||||||
@@ -205,11 +183,9 @@
|
|||||||
"Password": "Passwort",
|
"Password": "Passwort",
|
||||||
"Port": "Port",
|
"Port": "Port",
|
||||||
"PortNumber": "Port Nummer",
|
"PortNumber": "Port Nummer",
|
||||||
"PreferredSize": "Bevorzugte Größe",
|
|
||||||
"ProxyBypassFilterHelpText": "Verwende ',' als Trennzeichen und '*.' als Platzhalter für Subdomains",
|
"ProxyBypassFilterHelpText": "Verwende ',' als Trennzeichen und '*.' als Platzhalter für Subdomains",
|
||||||
"ProxyType": "Proxy Typ",
|
"ProxyType": "Proxy Typ",
|
||||||
"ProxyUsernameHelpText": "Nur wenn ein Benutzername und Passwort erforderlich ist, muss es eingegeben werden. Ansonsten leer lassen.",
|
"ProxyUsernameHelpText": "Nur wenn ein Benutzername und Passwort erforderlich ist, muss es eingegeben werden. Ansonsten leer lassen.",
|
||||||
"QualitySettings": "Qualitäts Einstellungen",
|
|
||||||
"RefreshMovie": "Film aktualisieren",
|
"RefreshMovie": "Film aktualisieren",
|
||||||
"RemovedFromTaskQueue": "Aus der Aufgabenwarteschlage entfernt",
|
"RemovedFromTaskQueue": "Aus der Aufgabenwarteschlage entfernt",
|
||||||
"RemoveFilter": "Filter entfernen",
|
"RemoveFilter": "Filter entfernen",
|
||||||
@@ -227,25 +203,20 @@
|
|||||||
"SuggestTranslationChange": "Schlage eine Übersetzung vor",
|
"SuggestTranslationChange": "Schlage eine Übersetzung vor",
|
||||||
"TagsHelpText": "Wird auf Filme mit mindestens einem passenden Tag angewandt",
|
"TagsHelpText": "Wird auf Filme mit mindestens einem passenden Tag angewandt",
|
||||||
"TestAllClients": "Alle testen",
|
"TestAllClients": "Alle testen",
|
||||||
"UpdateMechanismHelpText": "Benutze den Built-In Updater oder ein Script",
|
"UpdateMechanismHelpText": "Benutze Prowlarr's Built-In Updater oder ein Script",
|
||||||
"UpdateScriptPathHelpText": "Pfad zu einem benutzerdefinierten Skript, das ein extrahiertes Update-Paket übernimmt und den Rest des Update-Prozesses abwickelt",
|
"UpdateScriptPathHelpText": "Pfad zu einem benutzerdefinierten Skript, das ein extrahiertes Update-Paket übernimmt und den Rest des Update-Prozesses abwickelt",
|
||||||
"Uptime": "Laufzeit",
|
"Uptime": "Laufzeit",
|
||||||
"URLBase": "URL Base",
|
"URLBase": "URL-Basis",
|
||||||
"Torrents": "Torrents",
|
"Torrents": "Torrents",
|
||||||
"UISettings": "Benutzeroberflächen Einstellungen",
|
"UISettings": "Benutzeroberflächen Einstellungen",
|
||||||
"UnableToLoadDownloadClients": "Downloader konnten nicht geladen werden",
|
"UnableToLoadDownloadClients": "Downloader konnten nicht geladen werden",
|
||||||
"UnableToLoadIndexers": "Indexer konnten nicht geladen werden",
|
|
||||||
"UnableToLoadNotifications": "Benachrichtigungen konnten nicht geladen werden",
|
"UnableToLoadNotifications": "Benachrichtigungen konnten nicht geladen werden",
|
||||||
"UnableToLoadQualityDefinitions": "Qualitätsdefinitionen konnten nicht geladen werden",
|
|
||||||
"UnableToLoadTags": "Tags konnten nicht geladen werden",
|
"UnableToLoadTags": "Tags konnten nicht geladen werden",
|
||||||
"Usenet": "Usenet",
|
"Usenet": "Usenet",
|
||||||
"UseProxy": "Proxy benutzen",
|
"UseProxy": "Proxy benutzen",
|
||||||
"Username": "Benutzername",
|
"Username": "Benutzername",
|
||||||
"Version": "Version",
|
"Version": "Version",
|
||||||
"YesCancel": "Ja, abbrechen",
|
"YesCancel": "Ja, abbrechen",
|
||||||
"EnableColorImpairedModeHelpText": "Alternativer Stil, um farbbeeinträchtigten Benutzern eine bessere Unterscheidung farbcodierter Informationen zu ermöglichen",
|
|
||||||
"EnableMediaInfoHelpText": "Videoinformationen wie Auflösung, Laufzeit und Codec aus Datien erkennen. Dazu ist es erforderlich, dass Prowlarr Teile der Datei liest, was zu hoher Festplatten- oder Netzwerkaktivität während der Scans führen kann.",
|
|
||||||
"NoMinimumForAnyRuntime": "Kein Minimum für Laufzeiten",
|
|
||||||
"AnalyticsEnabledHelpText": "Sende anonyme Nutzungs- und Fehlerinformationen an die Server von Prowlarr. Dazu gehören Informationen über Browser, welche Seiten der Prowlarr-Weboberfläche aufgerufen wurden, Fehlerberichte sowie Betriebssystem- und Laufzeitversion. Wir werden diese Informationen verwenden, um Funktionen und Fehlerbehebungen zu priorisieren.",
|
"AnalyticsEnabledHelpText": "Sende anonyme Nutzungs- und Fehlerinformationen an die Server von Prowlarr. Dazu gehören Informationen über Browser, welche Seiten der Prowlarr-Weboberfläche aufgerufen wurden, Fehlerberichte sowie Betriebssystem- und Laufzeitversion. Wir werden diese Informationen verwenden, um Funktionen und Fehlerbehebungen zu priorisieren.",
|
||||||
"RestartRequiredHelpTextWarning": "Erfordert einen Neustart",
|
"RestartRequiredHelpTextWarning": "Erfordert einen Neustart",
|
||||||
"ApiKey": "API-Schlüssel",
|
"ApiKey": "API-Schlüssel",
|
||||||
@@ -258,10 +229,7 @@
|
|||||||
"LaunchBrowserHelpText": " Öffne die Startseite von Prowlarr im Webbrowser nach dem Start.",
|
"LaunchBrowserHelpText": " Öffne die Startseite von Prowlarr im Webbrowser nach dem Start.",
|
||||||
"ReadTheWikiForMoreInformation": "Lese das Wiki für mehr Informationen",
|
"ReadTheWikiForMoreInformation": "Lese das Wiki für mehr Informationen",
|
||||||
"BackupFolderHelpText": "Relative Pfade befinden sich unter Prowlarrs AppData Ordner",
|
"BackupFolderHelpText": "Relative Pfade befinden sich unter Prowlarrs AppData Ordner",
|
||||||
"DelayProfile": "Verzögerungsprofil",
|
|
||||||
"MaximumLimits": "Maximale Grenzen",
|
|
||||||
"DeleteNotification": "Benachrichtigung löschen",
|
"DeleteNotification": "Benachrichtigung löschen",
|
||||||
"CloneIndexer": "Indexer kopieren",
|
|
||||||
"ConnectSettings": "Eintellungen für Verbindungen",
|
"ConnectSettings": "Eintellungen für Verbindungen",
|
||||||
"TagCannotBeDeletedWhileInUse": "Kann während der Benutzung nicht gelöscht werden",
|
"TagCannotBeDeletedWhileInUse": "Kann während der Benutzung nicht gelöscht werden",
|
||||||
"SSLCertPathHelpText": "Pfad zur PFX Datei",
|
"SSLCertPathHelpText": "Pfad zur PFX Datei",
|
||||||
@@ -269,20 +237,14 @@
|
|||||||
"ShownClickToHide": "Angezeigt, zum verstecken klicken",
|
"ShownClickToHide": "Angezeigt, zum verstecken klicken",
|
||||||
"RSSIsNotSupportedWithThisIndexer": "RSS wird von diesem Indexer nicht unterstützt",
|
"RSSIsNotSupportedWithThisIndexer": "RSS wird von diesem Indexer nicht unterstützt",
|
||||||
"RemovingTag": "Tag entfernen",
|
"RemovingTag": "Tag entfernen",
|
||||||
"Pending": "Ausstehend",
|
|
||||||
"Manual": "Manuell",
|
"Manual": "Manuell",
|
||||||
"LogLevelTraceHelpTextWarning": "Trace logging sollte nur kurzzeitig aktiviert werden",
|
"LogLevelTraceHelpTextWarning": "Trace logging sollte nur kurzzeitig aktiviert werden",
|
||||||
"HiddenClickToShow": "Versteckt, klicken zum anzeigen",
|
"HiddenClickToShow": "Versteckt, klicken zum anzeigen",
|
||||||
"ExistingTag": "Vorhandener Tag",
|
"ExistingTag": "Vorhandener Tag",
|
||||||
"EnableInteractiveSearchHelpTextWarning": "Der Indexer unterstützt keine Suchen",
|
|
||||||
"EnableInteractiveSearchHelpText": "Wird bei der manuellen Suche benutzt",
|
"EnableInteractiveSearchHelpText": "Wird bei der manuellen Suche benutzt",
|
||||||
"EnableAutomaticSearchHelpTextWarning": "Wird für die manuelle Suche benutzt",
|
|
||||||
"EnableAutomaticSearchHelpText": "Wird für automatische Suchen genutzt die vom Benutzer oder von Prowlarr gestartet werden",
|
"EnableAutomaticSearchHelpText": "Wird für automatische Suchen genutzt die vom Benutzer oder von Prowlarr gestartet werden",
|
||||||
"Downloading": "Lädt herunter",
|
|
||||||
"DownloadClientUnavailable": "Downloader ist nicht verfügbar",
|
|
||||||
"DeleteTagMessageText": "Tag '{0}' wirklich löschen?",
|
"DeleteTagMessageText": "Tag '{0}' wirklich löschen?",
|
||||||
"DeleteNotificationMessageText": "Benachrichtigung '{0}' wirklich löschen?",
|
"DeleteNotificationMessageText": "Benachrichtigung '{0}' wirklich löschen?",
|
||||||
"DeleteIndexerMessageText": "Indexer '{0}' wirklich löschen?",
|
|
||||||
"DeleteDownloadClientMessageText": "Downloader '{0}' wirklich löschen?",
|
"DeleteDownloadClientMessageText": "Downloader '{0}' wirklich löschen?",
|
||||||
"DeleteBackupMessageText": "Backup '{0}' wirkich löschen?",
|
"DeleteBackupMessageText": "Backup '{0}' wirkich löschen?",
|
||||||
"CancelPendingTask": "Diese laufende Aufgabe wirklich abbrechen?",
|
"CancelPendingTask": "Diese laufende Aufgabe wirklich abbrechen?",
|
||||||
@@ -306,10 +268,7 @@
|
|||||||
"NoTagsHaveBeenAddedYet": "Es wurden noch keine Tags erstellt",
|
"NoTagsHaveBeenAddedYet": "Es wurden noch keine Tags erstellt",
|
||||||
"NoLogFiles": "Keine Log-Dateien",
|
"NoLogFiles": "Keine Log-Dateien",
|
||||||
"NoBackupsAreAvailable": "Es sind keine Backups vorhanden",
|
"NoBackupsAreAvailable": "Es sind keine Backups vorhanden",
|
||||||
"MinutesSixty": "60 Minuten: {0}",
|
"MaintenanceRelease": "Wartung: Fehlerbehebung und andere Verbesserungen. Siehe Github Commit History für weitere Details",
|
||||||
"MinutesNinety": "90 Minuten: {0}",
|
|
||||||
"MinutesHundredTwenty": "120 Minuten: {0}",
|
|
||||||
"MaintenanceRelease": "Wartungsupdate",
|
|
||||||
"ForMoreInformationOnTheIndividualDownloadClients": "Für mehr Infomationen klicke auf die Info-Knöpfe.",
|
"ForMoreInformationOnTheIndividualDownloadClients": "Für mehr Infomationen klicke auf die Info-Knöpfe.",
|
||||||
"FilterPlaceHolder": "Filme suchen",
|
"FilterPlaceHolder": "Filme suchen",
|
||||||
"Exception": "Ausnahme",
|
"Exception": "Ausnahme",
|
||||||
@@ -323,7 +282,7 @@
|
|||||||
"UILanguage": "Oberflächen Sprache ( UI Language )",
|
"UILanguage": "Oberflächen Sprache ( UI Language )",
|
||||||
"Priority": "Priorität",
|
"Priority": "Priorität",
|
||||||
"InteractiveSearch": "Interaktive Suche",
|
"InteractiveSearch": "Interaktive Suche",
|
||||||
"IndexerPriorityHelpText": "Indexer Priorität von 1 (höchste) bis 50 (niedrigste). Standart: 25.",
|
"IndexerPriorityHelpText": "Indexer Priorität von 1 (höchste) bis 50 (niedrigste). Standard: 25.",
|
||||||
"IndexerPriority": "Priorität",
|
"IndexerPriority": "Priorität",
|
||||||
"EditIndexer": "Indexer bearbeiten",
|
"EditIndexer": "Indexer bearbeiten",
|
||||||
"Disabled": "Deaktiviert",
|
"Disabled": "Deaktiviert",
|
||||||
@@ -333,8 +292,6 @@
|
|||||||
"OpenThisModal": "Dieses Modal öffnen",
|
"OpenThisModal": "Dieses Modal öffnen",
|
||||||
"MovieIndexScrollTop": "Filmindex: Nach oben scrollen",
|
"MovieIndexScrollTop": "Filmindex: Nach oben scrollen",
|
||||||
"MovieIndexScrollBottom": "Filmindex: Nach unten scrollen",
|
"MovieIndexScrollBottom": "Filmindex: Nach unten scrollen",
|
||||||
"MovieDetailsPreviousMovie": "Film Details: Vorheriger Film",
|
|
||||||
"MovieDetailsNextMovie": "Film Details: Nächster Film",
|
|
||||||
"FocusSearchBox": "Suchbox fokussieren",
|
"FocusSearchBox": "Suchbox fokussieren",
|
||||||
"CloseCurrentModal": "Momentanes Modal schließen",
|
"CloseCurrentModal": "Momentanes Modal schließen",
|
||||||
"AcceptConfirmationModal": "Bestätigung akzeptieren Modal",
|
"AcceptConfirmationModal": "Bestätigung akzeptieren Modal",
|
||||||
@@ -382,7 +339,7 @@
|
|||||||
"EditAppProfile": "App-Profil bearbeiten",
|
"EditAppProfile": "App-Profil bearbeiten",
|
||||||
"Wiki": "Wiki",
|
"Wiki": "Wiki",
|
||||||
"RSS": "RSS",
|
"RSS": "RSS",
|
||||||
"RedirectHelpText": "Leiten Sie eingehende Download-Anfragen für den Indexer um, statt Proxying mit Prowlarr",
|
"RedirectHelpText": "Eingehende Download-Anfragen für den Indexer umleiten, anstatt Proxying mit Prowlarr",
|
||||||
"Redirect": "Umleiten",
|
"Redirect": "Umleiten",
|
||||||
"Reddit": "Reddit",
|
"Reddit": "Reddit",
|
||||||
"HomePage": "Startseite",
|
"HomePage": "Startseite",
|
||||||
@@ -396,8 +353,8 @@
|
|||||||
"UnableToLoadAppProfiles": "App-Profile können nicht geladen werden",
|
"UnableToLoadAppProfiles": "App-Profile können nicht geladen werden",
|
||||||
"UnableToAddANewAppProfilePleaseTryAgain": "Es kann kein neues Anwendungsprofil hinzugefügt werden. Bitte versuchen Sie es erneut.",
|
"UnableToAddANewAppProfilePleaseTryAgain": "Es kann kein neues Anwendungsprofil hinzugefügt werden. Bitte versuchen Sie es erneut.",
|
||||||
"UnableToAddANewApplicationPleaseTryAgain": "Es kann keine neue Anwendung hinzugefügt werden. Bitte versuchen Sie es erneut.",
|
"UnableToAddANewApplicationPleaseTryAgain": "Es kann keine neue Anwendung hinzugefügt werden. Bitte versuchen Sie es erneut.",
|
||||||
"SyncLevelFull": "Vollständige Synchronisierung: Hält diese App vollständig synchron. In Prowlarr vorgenommene Änderungen werden dann mit dieser App synchronisiert. Alle aus der Ferne vorgenommenen Änderungen werden von Prowlarr bei der nächsten Synchronisierung überschrieben.",
|
"SyncLevelFull": "Vollständige Synchronisierung: Hält die Indexer dieser App vollständig synchronisiert. Änderungen an Indexern in Prowlarr werden mit dieser App synchronisiert. Jede Änderung die and Indexern dieser App gemacht werden, wird von Prowlarr bei der nächsten Synchronisierung überschrieben.",
|
||||||
"SyncLevelAddRemove": "Nur hinzufügen und entfernen: Wenn es in Prowlarr hinzugefügt oder entfernt wird, aktualisiert es diese Remote-App.",
|
"SyncLevelAddRemove": "Nur hinzufügen und entfernen: Wenn Indexer zu Prowlarr hinzugefügt oder entfernt werden, wird diese Remote-App aktualisiert.",
|
||||||
"SyncLevel": "Sync-Level",
|
"SyncLevel": "Sync-Level",
|
||||||
"Privacy": "Privatsphäre",
|
"Privacy": "Privatsphäre",
|
||||||
"Presets": "Voreinstellungen",
|
"Presets": "Voreinstellungen",
|
||||||
@@ -429,5 +386,45 @@
|
|||||||
"AudioSearch": "Audio Suche",
|
"AudioSearch": "Audio Suche",
|
||||||
"AddIndexerProxy": "Indexer Proxy hinzufügen",
|
"AddIndexerProxy": "Indexer Proxy hinzufügen",
|
||||||
"AppSettingsSummary": "Anwendungen und Einstellungen um zu konfigurieren, wie Prowlarr mit deinen PVR Programmen interagiert",
|
"AppSettingsSummary": "Anwendungen und Einstellungen um zu konfigurieren, wie Prowlarr mit deinen PVR Programmen interagiert",
|
||||||
"DeleteIndexerProxy": "Indexer Proxy löschen"
|
"DeleteIndexerProxy": "Indexer Proxy löschen",
|
||||||
|
"Notification": "Benachrichtigungen",
|
||||||
|
"Filters": "Filter",
|
||||||
|
"Notifications": "Benachrichtigungen",
|
||||||
|
"HistoryCleanupDaysHelpText": "Auf 0 setzen um das automatische leeren des Papierkorbs zu deaktivieren",
|
||||||
|
"HistoryCleanupDaysHelpTextWarning": "Datien im Papierkorb die älter sind als der gewählte Wert, werden endgültig gelöscht",
|
||||||
|
"OnApplicationUpdate": "Bei Anwendungsaktualisierung",
|
||||||
|
"OnApplicationUpdateHelpText": "Bei Anwendungsaktualisierung",
|
||||||
|
"OnGrab": "Bei Erfassung",
|
||||||
|
"OnHealthIssue": "Bei Zustandsproblem",
|
||||||
|
"TestAllIndexers": "Alle testen",
|
||||||
|
"UserAgentProvidedByTheAppThatCalledTheAPI": "UserAgent von der App welcher die API aufgerufen hat",
|
||||||
|
"BookSearch": "Buch Suche",
|
||||||
|
"Id": "Id",
|
||||||
|
"IndexerProxies": "Indexer-Proxies",
|
||||||
|
"IndexerTagsHelpText": "Benutze Tags, um Indexer-Proxies zu spezifizieren oder um Indexer zu organisieren.",
|
||||||
|
"MovieSearch": "Film Suche",
|
||||||
|
"QueryOptions": "Abfrage-Optionen",
|
||||||
|
"Categories": "Kategorien",
|
||||||
|
"Database": "Datenbank",
|
||||||
|
"IndexerNoDefCheckMessage": "Indexer haben keine Definition und werden nicht funktionieren: {0}. Bitte entferne und (oder) füge diese neu zu Prowlarr hinzu",
|
||||||
|
"MassEditor": "Masseneditor",
|
||||||
|
"Private": "Privat",
|
||||||
|
"TvSearch": "TV Suche",
|
||||||
|
"Url": "Url",
|
||||||
|
"Auth": "Authentifizierung",
|
||||||
|
"HistoryCleanup": "Verlaufsbereinigung",
|
||||||
|
"IndexerAlreadySetup": "Mindestens eine Indexer Instanz ist bereits eingerichtet",
|
||||||
|
"IndexerInfo": "Indexer-Info",
|
||||||
|
"IndexerProxy": "Indexer-Proxy",
|
||||||
|
"IndexerSettingsSummary": "Konfiguration verschiedener globaler Indexer Einstellungen, einschließlich Proxies.",
|
||||||
|
"IndexerVipCheckExpiredClientMessage": "Die VIP Indexer Vorteile sind abgelaufen: {0}",
|
||||||
|
"IndexerVipCheckExpiringClientMessage": "Die Indexer VIP Vorteile verfallen bald: {0}",
|
||||||
|
"Proxies": "Proxies",
|
||||||
|
"Public": "Öffentlich",
|
||||||
|
"QueryResults": "Abfrageergebnisse",
|
||||||
|
"SearchType": "Suchtyp",
|
||||||
|
"SemiPrivate": "Halbprivat",
|
||||||
|
"UnableToLoadApplicationList": "Anwendungsliste kann nicht geladen werden",
|
||||||
|
"UnableToLoadIndexerProxies": "Indexer-Proxies können nicht geladen werden",
|
||||||
|
"Website": "Webseite"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
{
|
{
|
||||||
"DownloadClients": "Προγράμματα Λήψης",
|
"DownloadClients": "Προγράμματα Λήψης",
|
||||||
"DownloadClientCheckUnableToCommunicateMessage": "Αδύνατο να επικοινωνήσει με {0}.",
|
|
||||||
"DownloadClientCheckNoneAvailableMessage": "Δεν υπάρχει διαθέσιμο πρόγραμμα Λήψης",
|
|
||||||
"DownloadClient": "Πρόγραμμα Λήψης",
|
"DownloadClient": "Πρόγραμμα Λήψης",
|
||||||
"Details": "Λεπτομέρειες",
|
"Details": "Λεπτομέρειες",
|
||||||
"Delete": "Διαγραφή",
|
"Delete": "Διαγραφή",
|
||||||
@@ -61,7 +59,6 @@
|
|||||||
"IllRestartLater": "Θα επανεκκινήσω αργότερα",
|
"IllRestartLater": "Θα επανεκκινήσω αργότερα",
|
||||||
"Protocol": "Πρωτόκολλο",
|
"Protocol": "Πρωτόκολλο",
|
||||||
"Reddit": "Reddit",
|
"Reddit": "Reddit",
|
||||||
"Restrictions": "Περιορισμοί",
|
|
||||||
"Result": "Αποτέλεσμα",
|
"Result": "Αποτέλεσμα",
|
||||||
"Retention": "Κράτηση",
|
"Retention": "Κράτηση",
|
||||||
"RSS": "RSS",
|
"RSS": "RSS",
|
||||||
@@ -77,16 +74,13 @@
|
|||||||
"ApplyTagsHelpTexts2": "Προσθήκη: Προσθέστε τις ετικέτες στην υπάρχουσα λίστα ετικετών",
|
"ApplyTagsHelpTexts2": "Προσθήκη: Προσθέστε τις ετικέτες στην υπάρχουσα λίστα ετικετών",
|
||||||
"ApplyTagsHelpTexts3": "Αφαίρεση: Καταργήστε τις καταχωρημένες ετικέτες",
|
"ApplyTagsHelpTexts3": "Αφαίρεση: Καταργήστε τις καταχωρημένες ετικέτες",
|
||||||
"AreYouSureYouWantToResetYourAPIKey": "Είστε βέβαιοι ότι θέλετε να επαναφέρετε το κλειδί API σας;",
|
"AreYouSureYouWantToResetYourAPIKey": "Είστε βέβαιοι ότι θέλετε να επαναφέρετε το κλειδί API σας;",
|
||||||
"DeleteIndexer": "Διαγραφή ευρετηρίου",
|
|
||||||
"NoChange": "Καμία αλλαγή",
|
"NoChange": "Καμία αλλαγή",
|
||||||
"Port": "Λιμάνι",
|
"Port": "Λιμάνι",
|
||||||
"PortNumber": "Αριθμός θύρας",
|
"PortNumber": "Αριθμός θύρας",
|
||||||
"PreferredSize": "Προτιμώμενο μέγεθος",
|
|
||||||
"IndexerStatusCheckAllClientMessage": "Όλοι οι δείκτες δεν είναι διαθέσιμοι λόγω αστοχιών",
|
"IndexerStatusCheckAllClientMessage": "Όλοι οι δείκτες δεν είναι διαθέσιμοι λόγω αστοχιών",
|
||||||
"IndexerStatusCheckSingleClientMessage": "Τα ευρετήρια δεν είναι διαθέσιμα λόγω αστοχιών: {0}",
|
"IndexerStatusCheckSingleClientMessage": "Τα ευρετήρια δεν είναι διαθέσιμα λόγω αστοχιών: {0}",
|
||||||
"KeyboardShortcuts": "Συντομεύσεις πληκτρολογίου",
|
"KeyboardShortcuts": "Συντομεύσεις πληκτρολογίου",
|
||||||
"Language": "Γλώσσα",
|
"Language": "Γλώσσα",
|
||||||
"MonoTlsCheckMessage": "Η λύση Radarr Mono 4.x tls είναι ακόμα ενεργοποιημένη, εξετάστε το ενδεχόμενο να καταργήσετε το MONO_TLS_PROVIDER = επιλογή περιβάλλοντος παλαιού τύπου",
|
|
||||||
"PriorityHelpText": "Προτεραιότητα πολλαπλών πελατών λήψης. Το Round-Robin χρησιμοποιείται για πελάτες με την ίδια προτεραιότητα.",
|
"PriorityHelpText": "Προτεραιότητα πολλαπλών πελατών λήψης. Το Round-Robin χρησιμοποιείται για πελάτες με την ίδια προτεραιότητα.",
|
||||||
"PrioritySettings": "Προτεραιότητα",
|
"PrioritySettings": "Προτεραιότητα",
|
||||||
"Reset": "Επαναφορά",
|
"Reset": "Επαναφορά",
|
||||||
@@ -103,11 +97,6 @@
|
|||||||
"ConnectSettings": "Σύνδεση ρυθμίσεων",
|
"ConnectSettings": "Σύνδεση ρυθμίσεων",
|
||||||
"Disabled": "άτομα με ειδικές ανάγκες",
|
"Disabled": "άτομα με ειδικές ανάγκες",
|
||||||
"Manual": "Εγχειρίδιο",
|
"Manual": "Εγχειρίδιο",
|
||||||
"MaximumLimits": "Μέγιστα όρια",
|
|
||||||
"MinutesSixty": "60 λεπτά: {0}",
|
|
||||||
"MovieDetailsNextMovie": "Λεπτομέρειες ταινίας: Επόμενη ταινία",
|
|
||||||
"QualityDefinitions": "Ορισμοί ποιότητας",
|
|
||||||
"QualitySettings": "Ρυθμίσεις ποιότητας",
|
|
||||||
"SetTags": "Ορισμός ετικετών",
|
"SetTags": "Ορισμός ετικετών",
|
||||||
"ShowSearch": "Εμφάνιση αναζήτησης",
|
"ShowSearch": "Εμφάνιση αναζήτησης",
|
||||||
"Status": "Κατάσταση",
|
"Status": "Κατάσταση",
|
||||||
@@ -123,7 +112,6 @@
|
|||||||
"IndexerPriority": "Προτεραιότητα ευρετηρίου",
|
"IndexerPriority": "Προτεραιότητα ευρετηρίου",
|
||||||
"Indexers": "Ευρετήρια",
|
"Indexers": "Ευρετήρια",
|
||||||
"Info": "Πληροφορίες",
|
"Info": "Πληροφορίες",
|
||||||
"Languages": "Γλώσσες",
|
|
||||||
"LastWriteTime": "Τελευταία ώρα εγγραφής",
|
"LastWriteTime": "Τελευταία ώρα εγγραφής",
|
||||||
"Level": "Επίπεδο",
|
"Level": "Επίπεδο",
|
||||||
"LogLevel": "Επίπεδο καταγραφής",
|
"LogLevel": "Επίπεδο καταγραφής",
|
||||||
@@ -143,24 +131,16 @@
|
|||||||
"CloneProfile": "Προφίλ κλώνου",
|
"CloneProfile": "Προφίλ κλώνου",
|
||||||
"CloseCurrentModal": "Κλείσιμο τρέχοντος modal",
|
"CloseCurrentModal": "Κλείσιμο τρέχοντος modal",
|
||||||
"DBMigration": "Μετεγκατάσταση DB",
|
"DBMigration": "Μετεγκατάσταση DB",
|
||||||
"DelayProfile": "Προφίλ χρονοκαθυστέρησης",
|
|
||||||
"DeleteApplicationMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε την ειδοποίηση \"{0}\";",
|
"DeleteApplicationMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε την ειδοποίηση \"{0}\";",
|
||||||
"DeleteBackupMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε το αντίγραφο ασφαλείας \"{0}\";",
|
"DeleteBackupMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε το αντίγραφο ασφαλείας \"{0}\";",
|
||||||
"DeleteIndexerMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε το ευρετήριο \"{0}\";",
|
|
||||||
"DeleteIndexerProxyMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τη λίστα \"{0}\";",
|
"DeleteIndexerProxyMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τη λίστα \"{0}\";",
|
||||||
"DeleteNotificationMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε την ειδοποίηση \"{0}\";",
|
"DeleteNotificationMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε την ειδοποίηση \"{0}\";",
|
||||||
"DeleteTagMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε την ετικέτα \"{0}\";",
|
"DeleteTagMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε την ετικέτα \"{0}\";",
|
||||||
"DownloadClientUnavailable": "Ο πελάτης λήψης δεν είναι διαθέσιμος",
|
|
||||||
"EnableAutoHelpText": "Εάν ενεργοποιηθεί, οι Ταινίες θα προστεθούν αυτόματα στο Radarr από αυτήν τη λίστα",
|
|
||||||
"EnableAutomaticAdd": "Ενεργοποίηση αυτόματης προσθήκης",
|
|
||||||
"EnableAutomaticSearch": "Ενεργοποίηση αυτόματης αναζήτησης",
|
"EnableAutomaticSearch": "Ενεργοποίηση αυτόματης αναζήτησης",
|
||||||
"EnableAutomaticSearchHelpText": "Θα χρησιμοποιηθεί όταν πραγματοποιούνται αυτόματες αναζητήσεις μέσω του περιβάλλοντος χρήστη ή του Radarr",
|
"EnableAutomaticSearchHelpText": "Θα χρησιμοποιηθεί όταν πραγματοποιούνται αυτόματες αναζητήσεις μέσω του περιβάλλοντος χρήστη ή του Radarr",
|
||||||
"EnableColorImpairedModeHelpText": "Τροποποιημένο στυλ για να επιτρέπεται στους χρήστες με προβλήματα χρώματος να διακρίνουν καλύτερα τις πληροφορίες με χρωματική κωδικοποίηση",
|
|
||||||
"EnableMediaInfoHelpText": "Εξαγωγή πληροφοριών βίντεο, όπως ανάλυση, χρόνος εκτέλεσης και πληροφορίες κωδικοποιητή από αρχεία. Αυτό απαιτεί από τον Radarr να διαβάσει τμήματα του αρχείου που ενδέχεται να προκαλέσουν υψηλή δραστηριότητα δίσκου ή δικτύου κατά τη διάρκεια των σαρώσεων.",
|
|
||||||
"EnableSslHelpText": " Απαιτείται επανεκκίνηση ως διαχειριστής για να τεθεί σε ισχύ",
|
"EnableSslHelpText": " Απαιτείται επανεκκίνηση ως διαχειριστής για να τεθεί σε ισχύ",
|
||||||
"Error": "Λάθος",
|
"Error": "Λάθος",
|
||||||
"ErrorLoadingContents": "Σφάλμα κατά τη φόρτωση περιεχομένων",
|
"ErrorLoadingContents": "Σφάλμα κατά τη φόρτωση περιεχομένων",
|
||||||
"ExistingMovies": "Υφιστάμενες ταινίες",
|
|
||||||
"GeneralSettings": "Γενικές Ρυθμίσεις",
|
"GeneralSettings": "Γενικές Ρυθμίσεις",
|
||||||
"Grabs": "Αρπάζω",
|
"Grabs": "Αρπάζω",
|
||||||
"HealthNoIssues": "Δεν υπάρχουν προβλήματα με τη διαμόρφωσή σας",
|
"HealthNoIssues": "Δεν υπάρχουν προβλήματα με τη διαμόρφωσή σας",
|
||||||
@@ -177,29 +157,19 @@
|
|||||||
"LogLevelTraceHelpTextWarning": "Η καταγραφή ιχνών πρέπει να ενεργοποιηθεί προσωρινά",
|
"LogLevelTraceHelpTextWarning": "Η καταγραφή ιχνών πρέπει να ενεργοποιηθεί προσωρινά",
|
||||||
"Mechanism": "Μηχανισμός",
|
"Mechanism": "Μηχανισμός",
|
||||||
"Message": "Μήνυμα",
|
"Message": "Μήνυμα",
|
||||||
"MinimumLimits": "Ελάχιστα όρια",
|
|
||||||
"MinutesHundredTwenty": "120 λεπτά: {0}",
|
|
||||||
"MinutesNinety": "90 λεπτά: {0}",
|
|
||||||
"MonoVersion": "Μονο έκδοση",
|
|
||||||
"MonoVersionCheckUpgradeRecommendedMessage": "Η τρέχουσα εγκατεστημένη έκδοση Mono {0} υποστηρίζεται, αλλά συνιστάται η αναβάθμιση σε {1}.",
|
|
||||||
"MovieDetailsPreviousMovie": "Λεπτομέρειες ταινίας: Προηγούμενη ταινία",
|
|
||||||
"MovieIndexScrollBottom": "Ευρετήριο ταινιών: Κύλιση κάτω",
|
"MovieIndexScrollBottom": "Ευρετήριο ταινιών: Κύλιση κάτω",
|
||||||
"MovieIndexScrollTop": "Ευρετήριο ταινιών: Κύλιση στην κορυφή",
|
"MovieIndexScrollTop": "Ευρετήριο ταινιών: Κύλιση στην κορυφή",
|
||||||
"Movies": "Κινηματογράφος",
|
|
||||||
"Name": "Ονομα",
|
"Name": "Ονομα",
|
||||||
"New": "Νέος",
|
"New": "Νέος",
|
||||||
"NoBackupsAreAvailable": "Δεν υπάρχουν διαθέσιμα αντίγραφα ασφαλείας",
|
"NoBackupsAreAvailable": "Δεν υπάρχουν διαθέσιμα αντίγραφα ασφαλείας",
|
||||||
"NoLeaveIt": "Όχι, άσε το",
|
"NoLeaveIt": "Όχι, άσε το",
|
||||||
"NoLimitForAnyRuntime": "Δεν υπάρχει όριο για οποιοδήποτε χρόνο εκτέλεσης",
|
|
||||||
"NoLinks": "Χωρίς συνδέσμους",
|
"NoLinks": "Χωρίς συνδέσμους",
|
||||||
"NoLogFiles": "Δεν υπάρχουν αρχεία καταγραφής",
|
"NoLogFiles": "Δεν υπάρχουν αρχεία καταγραφής",
|
||||||
"NoMinimumForAnyRuntime": "Χωρίς ελάχιστο για κάθε χρόνο εκτέλεσης",
|
|
||||||
"NoTagsHaveBeenAddedYet": "Δεν έχουν προστεθεί ετικέτες ακόμη",
|
"NoTagsHaveBeenAddedYet": "Δεν έχουν προστεθεί ετικέτες ακόμη",
|
||||||
"NotificationTriggers": "Ενεργοποιήσεις ειδοποίησης",
|
"NotificationTriggers": "Ενεργοποιήσεις ειδοποίησης",
|
||||||
"PageSizeHelpText": "Αριθμός στοιχείων προς εμφάνιση σε κάθε σελίδα",
|
"PageSizeHelpText": "Αριθμός στοιχείων προς εμφάνιση σε κάθε σελίδα",
|
||||||
"Password": "Κωδικός πρόσβασης",
|
"Password": "Κωδικός πρόσβασης",
|
||||||
"Peers": "Ομότιμοι",
|
"Peers": "Ομότιμοι",
|
||||||
"Pending": "εκκρεμής",
|
|
||||||
"PendingChangesMessage": "Έχετε μη αποθηκευμένες αλλαγές, είστε βέβαιοι ότι θέλετε να αποχωρήσετε από αυτήν τη σελίδα;",
|
"PendingChangesMessage": "Έχετε μη αποθηκευμένες αλλαγές, είστε βέβαιοι ότι θέλετε να αποχωρήσετε από αυτήν τη σελίδα;",
|
||||||
"PendingChangesStayReview": "Παραμείνετε και ελέγξτε τις αλλαγές",
|
"PendingChangesStayReview": "Παραμείνετε και ελέγξτε τις αλλαγές",
|
||||||
"Presets": "Προεπιλογές",
|
"Presets": "Προεπιλογές",
|
||||||
@@ -257,7 +227,6 @@
|
|||||||
"UnableToAddANewIndexerPleaseTryAgain": "Δεν είναι δυνατή η προσθήκη νέου ευρετηρίου, δοκιμάστε ξανά.",
|
"UnableToAddANewIndexerPleaseTryAgain": "Δεν είναι δυνατή η προσθήκη νέου ευρετηρίου, δοκιμάστε ξανά.",
|
||||||
"UnableToLoadGeneralSettings": "Δεν είναι δυνατή η φόρτωση των γενικών ρυθμίσεων",
|
"UnableToLoadGeneralSettings": "Δεν είναι δυνατή η φόρτωση των γενικών ρυθμίσεων",
|
||||||
"UnableToLoadNotifications": "Δεν είναι δυνατή η φόρτωση ειδοποιήσεων",
|
"UnableToLoadNotifications": "Δεν είναι δυνατή η φόρτωση ειδοποιήσεων",
|
||||||
"UnableToLoadQualityDefinitions": "Δεν είναι δυνατή η φόρτωση των ορισμών ποιότητας",
|
|
||||||
"UnableToLoadUISettings": "Δεν είναι δυνατή η φόρτωση των ρυθμίσεων διεπαφής χρήστη",
|
"UnableToLoadUISettings": "Δεν είναι δυνατή η φόρτωση των ρυθμίσεων διεπαφής χρήστη",
|
||||||
"UnsavedChanges": "Μη αποθηκευμένες αλλαγές",
|
"UnsavedChanges": "Μη αποθηκευμένες αλλαγές",
|
||||||
"UpdateCheckStartupTranslocationMessage": "Δεν είναι δυνατή η εγκατάσταση της ενημέρωσης επειδή ο φάκελος εκκίνησης \"{0}\" βρίσκεται σε ένα φάκελο \"Μετατόπιση εφαρμογών\".",
|
"UpdateCheckStartupTranslocationMessage": "Δεν είναι δυνατή η εγκατάσταση της ενημέρωσης επειδή ο φάκελος εκκίνησης \"{0}\" βρίσκεται σε ένα φάκελο \"Μετατόπιση εφαρμογών\".",
|
||||||
@@ -272,19 +241,16 @@
|
|||||||
"Warn": "Προειδοποιώ",
|
"Warn": "Προειδοποιώ",
|
||||||
"YesCancel": "Ναι, Ακύρωση",
|
"YesCancel": "Ναι, Ακύρωση",
|
||||||
"Exception": "Εξαίρεση",
|
"Exception": "Εξαίρεση",
|
||||||
"Importing": "Εισαγωγή",
|
|
||||||
"IncludeHealthWarningsHelpText": "Συμπεριλάβετε προειδοποιήσεις για την υγεία",
|
"IncludeHealthWarningsHelpText": "Συμπεριλάβετε προειδοποιήσεις για την υγεία",
|
||||||
"Security": "Ασφάλεια",
|
"Security": "Ασφάλεια",
|
||||||
"Tasks": "Καθήκοντα",
|
"Tasks": "Καθήκοντα",
|
||||||
"UnableToLoadBackups": "Δεν είναι δυνατή η φόρτωση αντιγράφων ασφαλείας",
|
"UnableToLoadBackups": "Δεν είναι δυνατή η φόρτωση αντιγράφων ασφαλείας",
|
||||||
"UnableToLoadDownloadClients": "Δεν είναι δυνατή η φόρτωση πελατών λήψης",
|
"UnableToLoadDownloadClients": "Δεν είναι δυνατή η φόρτωση πελατών λήψης",
|
||||||
"UnableToLoadIndexers": "Δεν είναι δυνατή η φόρτωση του ευρετηρίου",
|
|
||||||
"UpdateMechanismHelpText": "Χρησιμοποιήστε το ενσωματωμένο πρόγραμμα ενημέρωσης του Radarr ή ένα σενάριο",
|
"UpdateMechanismHelpText": "Χρησιμοποιήστε το ενσωματωμένο πρόγραμμα ενημέρωσης του Radarr ή ένα σενάριο",
|
||||||
"AnalyticsEnabledHelpText": "Στείλτε ανώνυμες πληροφορίες χρήσης και σφάλματος στους διακομιστές του Radarr. Αυτό περιλαμβάνει πληροφορίες στο πρόγραμμα περιήγησής σας, ποιες σελίδες Radarr WebUI χρησιμοποιείτε, αναφορά σφαλμάτων καθώς και έκδοση λειτουργικού συστήματος και χρόνου εκτέλεσης. Θα χρησιμοποιήσουμε αυτές τις πληροφορίες για να δώσουμε προτεραιότητα σε λειτουργίες και διορθώσεις σφαλμάτων.",
|
"AnalyticsEnabledHelpText": "Στείλτε ανώνυμες πληροφορίες χρήσης και σφάλματος στους διακομιστές του Radarr. Αυτό περιλαμβάνει πληροφορίες στο πρόγραμμα περιήγησής σας, ποιες σελίδες Radarr WebUI χρησιμοποιείτε, αναφορά σφαλμάτων καθώς και έκδοση λειτουργικού συστήματος και χρόνου εκτέλεσης. Θα χρησιμοποιήσουμε αυτές τις πληροφορίες για να δώσουμε προτεραιότητα σε λειτουργίες και διορθώσεις σφαλμάτων.",
|
||||||
"AppDataDirectory": "Κατάλογος AppData",
|
"AppDataDirectory": "Κατάλογος AppData",
|
||||||
"BindAddress": "Δεσμευμένη διεύθυνση",
|
"BindAddress": "Δεσμευμένη διεύθυνση",
|
||||||
"EnableRss": "Ενεργοποίηση RSS",
|
"EnableRss": "Ενεργοποίηση RSS",
|
||||||
"EnableInteractiveSearchHelpTextWarning": "Η αναζήτηση δεν υποστηρίζεται με αυτό το ευρετήριο",
|
|
||||||
"IndexerFlags": "Σημαίες ευρετηρίου",
|
"IndexerFlags": "Σημαίες ευρετηρίου",
|
||||||
"IndexerLongTermStatusCheckAllClientMessage": "Όλοι οι δείκτες δεν είναι διαθέσιμοι λόγω αστοχιών για περισσότερο από 6 ώρες",
|
"IndexerLongTermStatusCheckAllClientMessage": "Όλοι οι δείκτες δεν είναι διαθέσιμοι λόγω αστοχιών για περισσότερο από 6 ώρες",
|
||||||
"InteractiveSearch": "Διαδραστική αναζήτηση",
|
"InteractiveSearch": "Διαδραστική αναζήτηση",
|
||||||
@@ -314,14 +280,12 @@
|
|||||||
"CertificateValidation": "Επικύρωση πιστοποιητικού",
|
"CertificateValidation": "Επικύρωση πιστοποιητικού",
|
||||||
"CertificateValidationHelpText": "Αλλάξτε πόσο αυστηρή είναι η επικύρωση πιστοποίησης HTTPS",
|
"CertificateValidationHelpText": "Αλλάξτε πόσο αυστηρή είναι η επικύρωση πιστοποίησης HTTPS",
|
||||||
"ClientPriority": "Προτεραιότητα πελάτη",
|
"ClientPriority": "Προτεραιότητα πελάτη",
|
||||||
"CloneIndexer": "Δείκτης κλώνου",
|
|
||||||
"DeleteBackup": "Διαγραφή αντιγράφων ασφαλείας",
|
"DeleteBackup": "Διαγραφή αντιγράφων ασφαλείας",
|
||||||
"DeleteDownloadClient": "Διαγραφή προγράμματος-πελάτη λήψης",
|
"DeleteDownloadClient": "Διαγραφή προγράμματος-πελάτη λήψης",
|
||||||
"DeleteDownloadClientMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τον πελάτη λήψης \"{0}\";",
|
"DeleteDownloadClientMessageText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τον πελάτη λήψης \"{0}\";",
|
||||||
"DeleteNotification": "Διαγραφή ειδοποίησης",
|
"DeleteNotification": "Διαγραφή ειδοποίησης",
|
||||||
"Docker": "Λιμενεργάτης",
|
"Docker": "Λιμενεργάτης",
|
||||||
"DownloadClientSettings": "Λήψη ρυθμίσεων πελάτη",
|
"DownloadClientSettings": "Λήψη ρυθμίσεων πελάτη",
|
||||||
"Downloading": "Λήψη",
|
|
||||||
"EnableInteractiveSearch": "Ενεργοποίηση διαδραστικής αναζήτησης",
|
"EnableInteractiveSearch": "Ενεργοποίηση διαδραστικής αναζήτησης",
|
||||||
"SuggestTranslationChange": "Προτείνετε αλλαγή μετάφρασης",
|
"SuggestTranslationChange": "Προτείνετε αλλαγή μετάφρασης",
|
||||||
"System": "Σύστημα",
|
"System": "Σύστημα",
|
||||||
@@ -354,11 +318,14 @@
|
|||||||
"DeleteTag": "Διαγραφή ετικέτας",
|
"DeleteTag": "Διαγραφή ετικέτας",
|
||||||
"EditIndexer": "Επεξεργασία ευρετηρίου",
|
"EditIndexer": "Επεξεργασία ευρετηρίου",
|
||||||
"Enable": "επιτρέπω",
|
"Enable": "επιτρέπω",
|
||||||
"EnableAutomaticSearchHelpTextWarning": "Θα χρησιμοποιηθεί όταν χρησιμοποιείται διαδραστική αναζήτηση",
|
|
||||||
"EnableColorImpairedMode": "Ενεργοποίηση λειτουργίας με προβλήματα χρώματος",
|
|
||||||
"EnableCompletedDownloadHandlingHelpText": "Αυτόματη εισαγωγή ολοκληρωμένων λήψεων από τον πελάτη λήψης",
|
|
||||||
"Enabled": "Ενεργοποιήθηκε",
|
"Enabled": "Ενεργοποιήθηκε",
|
||||||
"EnabledHelpText": "Ενεργοποιήστε αυτήν τη λίστα για χρήση στο Radarr",
|
"EnableInteractiveSearchHelpText": "Θα χρησιμοποιηθεί όταν χρησιμοποιείται διαδραστική αναζήτηση",
|
||||||
"EnableHelpText": "Ενεργοποίηση δημιουργίας αρχείων μεταδεδομένων για αυτόν τον τύπο μεταδεδομένων",
|
"OnGrab": "Στο Grab",
|
||||||
"EnableInteractiveSearchHelpText": "Θα χρησιμοποιηθεί όταν χρησιμοποιείται διαδραστική αναζήτηση"
|
"Filters": "Φίλτρο",
|
||||||
|
"HistoryCleanupDaysHelpText": "Ορίστε σε 0 για να απενεργοποιήσετε τον αυτόματο καθαρισμό",
|
||||||
|
"HistoryCleanupDaysHelpTextWarning": "Τα αρχεία στον κάδο ανακύκλωσης παλαιότερα από τον επιλεγμένο αριθμό ημερών θα καθαρίζονται αυτόματα",
|
||||||
|
"OnHealthIssue": "Σχετικά με το θέμα της υγείας",
|
||||||
|
"TestAllIndexers": "Δοκιμάστε όλους τους δείκτες",
|
||||||
|
"MaintenanceRelease": "Έκδοση συντήρησης: επιδιορθώσεις σφαλμάτων και άλλες βελτιώσεις. Δείτε το Github Commit History για περισσότερες λεπτομέρειες",
|
||||||
|
"ConnectionLostMessage": "Το Radarr έχασε τη σύνδεσή του με το backend και θα χρειαστεί να επαναφορτωθεί για να αποκαταστήσει τη λειτουργικότητά του."
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user