1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-04-18 21:35:51 -04:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Mark McDowall da5feda71b Fixed: Parsing of release names with trailing colon in the title 2021-10-30 22:55:42 -04:00
458 changed files with 5239 additions and 8075 deletions
+4 -2
View File
@@ -1,4 +1,5 @@
name: Bug Report name: Bug Report
title: "[BUG]: "
description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Reddit or Discord first' description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Reddit or Discord first'
labels: ['Type: Bug', 'Status: Needs Triage'] labels: ['Type: Bug', 'Status: Needs Triage']
body: body:
@@ -63,11 +64,12 @@ body:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: Trace Logs? label: Anything else?
description: | description: |
Trace Logs (https://wiki.servarr.com/radarr/troubleshooting#logging-and-log-files) Trace Logs (https://wiki.servarr.com/radarr/troubleshooting#logging-and-log-files)
Links? References? Anything that will give us more context about the issue you are encountering!
***Generally speaking, all bug reports must have trace logs provided.*** ***Generally speaking, all bug reports must have trace logs provided.***
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
Additionally, any additional info? Screenshots? References? Anything that will give us more context about the issue you are encountering!
validations: validations:
required: true required: true
@@ -1,4 +1,5 @@
name: Feature Request name: Feature Request
title: "[FEAT]: "
description: 'Suggest an idea for Radarr' description: 'Suggest an idea for Radarr'
labels: ['Type: Feature Request', 'Status: Needs Triage'] labels: ['Type: Feature Request', 'Status: Needs Triage']
body: body:
+18 -27
View File
@@ -1,21 +1,19 @@
# Radarr # Radarr
[![Build Status](https://dev.azure.com/Radarr/Radarr/_apis/build/status/Radarr.Radarr?branchName=develop)](https://dev.azure.com/Radarr/Radarr/_build/latest?definitionId=1&branchName=develop) [![Build Status](https://dev.azure.com/Radarr/Radarr/_apis/build/status/Radarr.Radarr?branchName=develop)](https://dev.azure.com/Radarr/Radarr/_build/latest?definitionId=1&branchName=develop)
[![Translated](https://translate.servarr.com/widgets/servarr/-/radarr/svg-badge.svg)](https://translate.servarr.com/engage/radarr/?utm_source=widget) [![Translated](https://translate.servarr.com/widgets/radarr/-/radarr/svg-badge.svg)](https://translate.servarr.com/engage/radarr/?utm_source=widget)
[![Docker Pulls](https://img.shields.io/docker/pulls/linuxserver/radarr.svg)](https://wiki.servarr.com/radarr/installation#docker) [![Docker Pulls](https://img.shields.io/docker/pulls/linuxserver/radarr.svg)](https://wiki.servarr.com/radarr/installation#docker)
![Github Downloads](https://img.shields.io/github/downloads/Radarr/Radarr/total.svg) ![Github Downloads](https://img.shields.io/github/downloads/Radarr/Radarr/total.svg)
[![Backers on Open Collective](https://opencollective.com/Radarr/backers/badge.svg)](#backers) [![Backers on Open Collective](https://opencollective.com/Radarr/backers/badge.svg)](#backers)
[![Sponsors on Open Collective](https://opencollective.com/Radarr/sponsors/badge.svg)](#sponsors) [![Sponsors on Open Collective](https://opencollective.com/Radarr/sponsors/badge.svg)](#sponsors)
[![Mega Sponsors on Open Collective](https://opencollective.com/Radarr/megasponsors/badge.svg)](#mega-sponsors)
Radarr is a movie collection manager for Usenet and BitTorrent users. It can monitor multiple RSS feeds for new movies and will interface with clients and indexers to grab, sort, and rename them. It can also be configured to automatically upgrade the quality of existing files in the library when a better quality format becomes available. Radarr is a movie collection manager for Usenet and BitTorrent users. It can monitor multiple RSS feeds for new movies and will interface with clients and indexers to grab, sort, and rename them. It can also be configured to automatically upgrade the quality of existing files in the library when a better quality format becomes available.
Note that only one type of a given movie is supported. If you want both an 4k version and 1080p version of a given movie you will need multiple instances.
## Major Features Include ## Major Features Include:
* Adding new movies with lots of information, such as trailers, ratings, etc. * Adding new movies with lots of information, such as trailers, ratings, etc.
* Support for major platforms: Windows, Linux, macOS, Raspberry Pi, etc. * Support for major platforms: Windows, Linux, macOS, Raspberry Pi, etc.
* Can watch for better quality of the movies you have and do an automatic upgrade. *e.g. from DVD to Blu-Ray* * Can watch for better quality of the movies you have and do an automatic upgrade. *eg. from DVD to Blu-Ray*
* Automatic failed download handling will try another release if one fails * Automatic failed download handling will try another release if one fails
* Manual search so you can pick any release or to see why a release was not downloaded automatically * Manual search so you can pick any release or to see why a release was not downloaded automatically
* Full integration with SABnzbd and NZBGet * Full integration with SABnzbd and NZBGet
@@ -23,60 +21,53 @@ Note that only one type of a given movie is supported. If you want both an 4k ve
* Automatically importing downloaded movies * Automatically importing downloaded movies
* Recognizing Special Editions, Director's Cut, etc. * Recognizing Special Editions, Director's Cut, etc.
* Identifying releases with hardcoded subs * Identifying releases with hardcoded subs
* Identifying releases with AKA movie names * QBittorrent, Deluge, rTorrent, Transmission, uTorrent, and other download clients are supported
* SABnzbd, NZBGet, QBittorrent, Deluge, rTorrent, Transmission, uTorrent, and other download clients are supported and integrated * Full integration with Kodi, Plex (notification, library update)
* Full integration with Kodi and Plex (notifications, library updates) * A beautiful UI
* Importing Metadata such as trailers or subtitles * Importing Metadata such as trailers or subtitles
* Adding metadata such as posters and information for Kodi and others to use * Adding metadata such as posters and information for Kodi and others to use
* Advanced customization for profiles, such that Radarr will always download the copy you want * Advanced customization for profiles, such that Radarr will always download the copy you want
* A beautiful UI
## Support ## Support
[![Wiki](https://img.shields.io/badge/servarr-wiki-181717.svg?maxAge=60)](https://wiki.servarr.com/radarr)
[![Discord](https://img.shields.io/badge/discord-chat-7289DA.svg?maxAge=60)](https://radarr.video/discord)
[![Reddit](https://img.shields.io/badge/reddit-discussion-FF4500.svg?maxAge=60)](https://www.reddit.com/r/Radarr)
Note: GitHub Issues are for Bugs and Feature Requests Only Note: GitHub Issues are for Bugs and Feature Requests Only
[![Discord](https://img.shields.io/badge/discord-chat-7289DA.svg?maxAge=60)](https://radarr.video/discord)
[![Reddit](https://img.shields.io/badge/reddit-discussion-FF4500.svg?maxAge=60)](https://www.reddit.com/r/Radarr)
[![GitHub - Bugs and Feature Requests Only](https://img.shields.io/badge/github-issues-red.svg?maxAge=60)](https://github.com/Radarr/Radarr/issues) [![GitHub - Bugs and Feature Requests Only](https://img.shields.io/badge/github-issues-red.svg?maxAge=60)](https://github.com/Radarr/Radarr/issues)
[![Wiki](https://img.shields.io/badge/servarr-wiki-181717.svg?maxAge=60)](https://wiki.servarr.com/radarr)
## Contributors & Developers ## Contributors & Developers
[API Documentation](https://radarr.video/docs/api/) [API Documentation](https://radarr.video/docs/api/)
This project exists thanks to all the people who contribute. This project exists thanks to all the people who contribute. [Contribute](CONTRIBUTING.md).
- [Contribute (GitHub)](CONTRIBUTING.md) <a href="https://github.com/Radarr/Radarr/graphs/contributors"><img src="https://opencollective.com/Radarr/contributors.svg?width=890&button=false" /></a>
- [Contribution (Wiki Article)](https://wiki.servarr.com/radarr/contributing)
[![Contributors List](https://opencollective.com/Radarr/contributors.svg?width=890&button=false)](https://github.com/Radarr/Radarr/graphs/contributors)
## Backers ## Backers
Thank you to all our backers! 🙏 [Become a backer](https://opencollective.com/Radarr#backer) Thank you to all our backers! 🙏 [Become a backer](https://opencollective.com/Radarr#backer)
[![Backers List](https://opencollective.com/Radarr/backers.svg?width=890)](https://opencollective.com/Radarr#backer) <img src="https://opencollective.com/Radarr/backers.svg?width=890"></a>
## Sponsors ## Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor](https://opencollective.com/Radarr#sponsor) Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor](https://opencollective.com/Radarr#sponsor)
[![Sponsors List](https://opencollective.com/Radarr/sponsors.svg?width=890)](https://opencollective.com/Radarr#sponsor) <img src="https://opencollective.com/Radarr/sponsors.svg?width=890"></a>
## Mega Sponsors ## Mega Sponsors
[![Mega Sponsors List](https://opencollective.com/Radarr/tiers/mega-sponsor.svg?width=890)](https://opencollective.com/Radarr#mega-sponsor) <img src="https://opencollective.com/Radarr/tiers/mega-sponsor.svg?width=890"></a>
## JetBrains ## JetBrains
Thank you to [<img src="/Logo/jetbrains.svg" alt="JetBrains" width="32"> JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools. Thank you to [<img src="/Logo/jetbrains.svg" alt="JetBrains" width="32"> JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools.
* [<img src="/Logo/resharper.svg" alt="ReSharper" width="32"> ReSharper](http://www.jetbrains.com/resharper/) * [<img src="/Logo/resharper.svg" alt="ReSharper" width="32"> ReSharper](http://www.jetbrains.com/resharper/)
* [<img src="/Logo/webstorm.svg" alt="WebStorm" width="32"> WebStorm](http://www.jetbrains.com/webstorm/) * [<img src="/Logo/webstorm.svg" alt="WebStorm" width="32"> WebStorm](http://www.jetbrains.com/webstorm/)
* [<img src="/Logo/rider.svg" alt="Rider" width="32"> Rider](http://www.jetbrains.com/rider/) * [<img src="/Logo/rider.svg" alt="Rider" width="32"> Rider](http://www.jetbrains.com/rider/)
* [<img src="/Logo/dottrace.svg" alt="dotTrace" width="32"> dotTrace](http://www.jetbrains.com/dottrace/) * [<img src="/Logo/dottrace.svg" alt="dotTrace" width="32"> dotTrace](http://www.jetbrains.com/dottrace/)
### License ### License
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html) * [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
* Copyright 2010-2022 * Copyright 2010-2021
+37 -61
View File
@@ -7,13 +7,13 @@ variables:
outputFolder: './_output' outputFolder: './_output'
artifactsFolder: './_artifacts' artifactsFolder: './_artifacts'
testsFolder: './_tests' testsFolder: './_tests'
majorVersion: '4.0.4' majorVersion: '4.0.0'
minorVersion: $[counter('minorVersion', 2000)] minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)' radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)' buildName: '$(Build.SourceBranchName).$(radarrVersion)'
sentryOrg: 'servarr' sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com' sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.101' dotnetVersion: '5.0.401'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn yarnCacheFolder: $(Pipeline.Workspace)/.yarn
trigger: trigger:
@@ -67,7 +67,7 @@ stages:
enableAnalysis: 'true' enableAnalysis: 'true'
Mac: Mac:
osName: 'Mac' osName: 'Mac'
imageName: 'macos-10.15' imageName: 'macos-10.14'
enableAnalysis: 'false' enableAnalysis: 'false'
Windows: Windows:
osName: 'Windows' osName: 'Windows'
@@ -111,23 +111,23 @@ stages:
artifact: '$(osName)Backend' artifact: '$(osName)Backend'
displayName: Publish Backend displayName: Publish Backend
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/win-x64/publish' - publish: '$(testsFolder)/net5.0/win-x64/publish'
artifact: WindowsCoreTests artifact: WindowsCoreTests
displayName: Publish Windows Test Package displayName: Publish Windows Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/linux-x64/publish' - publish: '$(testsFolder)/net5.0/linux-x64/publish'
artifact: LinuxCoreTests artifact: LinuxCoreTests
displayName: Publish Linux Test Package displayName: Publish Linux Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish' - publish: '$(testsFolder)/net5.0/linux-musl-x64/publish'
artifact: LinuxMuslCoreTests artifact: LinuxMuslCoreTests
displayName: Publish Linux Musl Test Package displayName: Publish Linux Musl Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish' - publish: '$(testsFolder)/net5.0/freebsd-x64/publish'
artifact: FreebsdCoreTests artifact: FreebsdCoreTests
displayName: Publish FreeBSD Test Package displayName: Publish FreeBSD Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/osx-x64/publish' - publish: '$(testsFolder)/net5.0/osx-x64/publish'
artifact: MacCoreTests artifact: MacCoreTests
displayName: Publish MacOS Test Package displayName: Publish MacOS Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
@@ -144,7 +144,7 @@ stages:
imageName: 'ubuntu-18.04' imageName: 'ubuntu-18.04'
Mac: Mac:
osName: 'Mac' osName: 'Mac'
imageName: 'macos-10.15' imageName: 'macos-10.14'
Windows: Windows:
osName: 'Windows' osName: 'Windows'
imageName: 'windows-2019' imageName: 'windows-2019'
@@ -203,12 +203,12 @@ stages:
- bash: ./build.sh --packages - bash: ./build.sh --packages
displayName: Create Packages displayName: Create Packages
- bash: | - bash: |
setup/inno/ISCC.exe setup/radarr.iss //DFramework=net6.0 //DRuntime=win-x86 setup/inno/ISCC.exe setup/radarr.iss //DFramework=net5.0 //DRuntime=win-x86
cp setup/output/Radarr.*windows.net6.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x86-installer.exe cp setup/output/Radarr.*windows.net5.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x86-installer.exe
displayName: Create .NET Core Windows installer displayName: Create .NET Core Windows installer
- bash: | - bash: |
setup/inno/ISCC.exe setup/radarr.iss //DFramework=net6.0 //DRuntime=win-x64 setup/inno/ISCC.exe setup/radarr.iss //DFramework=net5.0 //DRuntime=win-x64
cp setup/output/Radarr.*windows.net6.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x64-installer.exe cp setup/output/Radarr.*windows.net5.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x64-installer.exe
displayName: Create .NET Core Windows installer displayName: Create .NET Core Windows installer
- publish: $(Build.ArtifactStagingDirectory) - publish: $(Build.ArtifactStagingDirectory)
artifact: 'WindowsInstaller' artifact: 'WindowsInstaller'
@@ -241,7 +241,6 @@ stages:
- bash: ./build.sh --packages --enable-bsd - bash: ./build.sh --packages --enable-bsd
displayName: Create Packages displayName: Create Packages
- bash: | - bash: |
find . -name "ffprobe" -exec chmod a+x {} \;
find . -name "Radarr" -exec chmod a+x {} \; find . -name "Radarr" -exec chmod a+x {} \;
find . -name "Radarr.Update" -exec chmod a+x {} \; find . -name "Radarr.Update" -exec chmod a+x {} \;
displayName: Set executable bits displayName: Set executable bits
@@ -251,44 +250,29 @@ stages:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x64.zip' archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x64.zip'
archiveType: 'zip' archiveType: 'zip'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/win-x64/net6.0 rootFolderOrFile: $(artifactsFolder)/win-x64/net5.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create Windows x86 Core zip displayName: Create Windows x86 Core zip
inputs: inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x86.zip' archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x86.zip'
archiveType: 'zip' archiveType: 'zip'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/win-x86/net6.0 rootFolderOrFile: $(artifactsFolder)/win-x86/net5.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create MacOS x64 Core app displayName: Create MacOS Core app
inputs: inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-x64.zip' archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-x64.zip'
archiveType: 'zip' archiveType: 'zip'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/osx-x64-app/net6.0 rootFolderOrFile: $(artifactsFolder)/macos-app/net5.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create MacOS x64 Core tar displayName: Create MacOS Core tar
inputs: inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-core-x64.tar.gz' archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-core-x64.tar.gz'
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/osx-x64/net6.0 rootFolderOrFile: $(artifactsFolder)/macos/net5.0
- task: ArchiveFiles@2
displayName: Create MacOS arm64 Core app
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-arm64.zip'
archiveType: 'zip'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/osx-arm64-app/net6.0
- task: ArchiveFiles@2
displayName: Create MacOS arm64 Core tar
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-core-arm64.tar.gz'
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/osx-arm64/net6.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create Linux Core tar displayName: Create Linux Core tar
inputs: inputs:
@@ -296,7 +280,7 @@ stages:
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-x64/net6.0 rootFolderOrFile: $(artifactsFolder)/linux-x64/net5.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create Linux Musl Core tar displayName: Create Linux Musl Core tar
inputs: inputs:
@@ -304,7 +288,7 @@ stages:
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net6.0 rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net5.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create ARM32 Linux Core tar displayName: Create ARM32 Linux Core tar
inputs: inputs:
@@ -312,15 +296,7 @@ stages:
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-arm/net6.0 rootFolderOrFile: $(artifactsFolder)/linux-arm/net5.0
- task: ArchiveFiles@2
displayName: Create ARM32 Linux Musl Core tar
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-musl-core-arm.tar.gz'
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm/net6.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create ARM64 Linux Core tar displayName: Create ARM64 Linux Core tar
inputs: inputs:
@@ -328,7 +304,7 @@ stages:
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net6.0 rootFolderOrFile: $(artifactsFolder)/linux-arm64/net5.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create ARM64 Linux Musl Core tar displayName: Create ARM64 Linux Musl Core tar
inputs: inputs:
@@ -336,7 +312,7 @@ stages:
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net6.0 rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net5.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create FreeBSD Core Core tar displayName: Create FreeBSD Core Core tar
inputs: inputs:
@@ -344,7 +320,7 @@ stages:
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/freebsd-x64/net6.0 rootFolderOrFile: $(artifactsFolder)/freebsd-x64/net5.0
- publish: $(Build.ArtifactStagingDirectory) - publish: $(Build.ArtifactStagingDirectory)
artifact: 'Packages' artifact: 'Packages'
displayName: Publish Packages displayName: Publish Packages
@@ -407,7 +383,7 @@ stages:
osName: 'Mac' osName: 'Mac'
testName: 'MacCore' testName: 'MacCore'
poolName: 'Azure Pipelines' poolName: 'Azure Pipelines'
imageName: 'macos-10.15' imageName: 'macos-10.14'
WindowsCore: WindowsCore:
osName: 'Windows' osName: 'Windows'
testName: 'WindowsCore' testName: 'WindowsCore'
@@ -441,13 +417,16 @@ stages:
buildType: 'current' buildType: 'current'
artifactName: '$(testName)Tests' artifactName: '$(testName)Tests'
targetPath: $(testsFolder) targetPath: $(testsFolder)
- bash: |
wget https://mediaarea.net/repo/deb/repo-mediaarea_1.0-11_all.deb
sudo dpkg -i repo-mediaarea_1.0-11_all.deb
sudo apt-get update
sudo apt-get install -y --allow-unauthenticated libmediainfo-dev libmediainfo0v5 mediainfo
displayName: Install mediainfo
condition: and(succeeded(), eq(variables['testName'], 'LinuxCore'))
- powershell: Set-Service SCardSvr -StartupType Manual - powershell: Set-Service SCardSvr -StartupType Manual
displayName: Enable Windows Test Service displayName: Enable Windows Test Service
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- bash: |
chmod a+x _tests/ffprobe
displayName: Make ffprobe Executable
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
- bash: find ${TESTSFOLDER} -name "Radarr.Test.Dummy" -exec chmod a+x {} \; - bash: find ${TESTSFOLDER} -name "Radarr.Test.Dummy" -exec chmod a+x {} \;
displayName: Make Test Dummy Executable displayName: Make Test Dummy Executable
condition: and(succeeded(), ne(variables['osName'], 'Windows')) condition: and(succeeded(), ne(variables['osName'], 'Windows'))
@@ -495,9 +474,6 @@ stages:
buildType: 'current' buildType: 'current'
artifactName: $(artifactName) artifactName: $(artifactName)
targetPath: $(testsFolder) targetPath: $(testsFolder)
- bash: |
chmod a+x _tests/ffprobe
displayName: Make ffprobe Executable
- bash: find ${TESTSFOLDER} -name "Radarr.Test.Dummy" -exec chmod a+x {} \; - bash: find ${TESTSFOLDER} -name "Radarr.Test.Dummy" -exec chmod a+x {} \;
displayName: Make Test Dummy Executable displayName: Make Test Dummy Executable
condition: and(succeeded(), ne(variables['osName'], 'Windows')) condition: and(succeeded(), ne(variables['osName'], 'Windows'))
@@ -541,7 +517,7 @@ stages:
MacCore: MacCore:
osName: 'Mac' osName: 'Mac'
testName: 'MacCore' testName: 'MacCore'
imageName: 'macos-10.15' imageName: 'macos-10.14'
pattern: 'Radarr.*.osx-core-x64.tar.gz' pattern: 'Radarr.*.osx-core-x64.tar.gz'
WindowsCore: WindowsCore:
osName: 'Windows' osName: 'Windows'
@@ -716,7 +692,7 @@ stages:
failBuild: true failBuild: true
Mac: Mac:
osName: 'Mac' osName: 'Mac'
imageName: 'macos-10.15' imageName: 'macos-10.14'
pattern: 'Radarr.*.osx-core-x64.tar.gz' pattern: 'Radarr.*.osx-core-x64.tar.gz'
failBuild: true failBuild: true
Windows: Windows:
@@ -889,8 +865,8 @@ stages:
sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml
sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml
- bash: | - bash: |
./build.sh --backend -f net6.0 -r win-x64 ./build.sh --backend -f net5.0 -r win-x64
TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage TEST_DIR=_tests/net5.0/win-x64/publish/ ./test.sh Windows Unit Coverage
displayName: Coverage Unit Tests displayName: Coverage Unit Tests
- task: SonarCloudAnalyze@1 - task: SonarCloudAnalyze@1
condition: eq(variables['System.PullRequest.IsFork'], 'False') condition: eq(variables['System.PullRequest.IsFork'], 'False')
+25 -29
View File
@@ -129,7 +129,7 @@ PackageLinux()
echo "Adding Radarr.Mono to UpdatePackage" echo "Adding Radarr.Mono to UpdatePackage"
cp $folder/Radarr.Mono.* $folder/Radarr.Update cp $folder/Radarr.Mono.* $folder/Radarr.Update
if [ "$framework" = "net6.0" ]; then if [ "$framework" = "net5.0" ]; then
cp $folder/Mono.Posix.NETStandard.* $folder/Radarr.Update cp $folder/Mono.Posix.NETStandard.* $folder/Radarr.Update
cp $folder/libMonoPosixHelper.* $folder/Radarr.Update cp $folder/libMonoPosixHelper.* $folder/Radarr.Update
fi fi
@@ -140,13 +140,12 @@ PackageLinux()
PackageMacOS() PackageMacOS()
{ {
local framework="$1" local framework="$1"
local runtime="$2"
ProgressStart "Creating MacOS Package for $framework $runtime" ProgressStart "Creating MacOS Package for $framework"
local folder=$artifactsFolder/$runtime/$framework/Radarr local folder=$artifactsFolder/macos/$framework/Radarr
PackageFiles "$folder" "$framework" "$runtime" PackageFiles "$folder" "$framework" "osx-x64"
echo "Removing Service helpers" echo "Removing Service helpers"
rm -f $folder/ServiceUninstall.* rm -f $folder/ServiceUninstall.*
@@ -157,7 +156,7 @@ PackageMacOS()
echo "Adding Radarr.Mono to UpdatePackage" echo "Adding Radarr.Mono to UpdatePackage"
cp $folder/Radarr.Mono.* $folder/Radarr.Update cp $folder/Radarr.Mono.* $folder/Radarr.Update
if [ "$framework" = "net6.0" ]; then if [ "$framework" = "net5.0" ]; then
cp $folder/Mono.Posix.NETStandard.* $folder/Radarr.Update cp $folder/Mono.Posix.NETStandard.* $folder/Radarr.Update
cp $folder/libMonoPosixHelper.* $folder/Radarr.Update cp $folder/libMonoPosixHelper.* $folder/Radarr.Update
fi fi
@@ -168,11 +167,10 @@ PackageMacOS()
PackageMacOSApp() PackageMacOSApp()
{ {
local framework="$1" local framework="$1"
local runtime="$2"
ProgressStart "Creating macOS App Package for $framework $runtime" ProgressStart "Creating macOS App Package for $framework"
local folder="$artifactsFolder/$runtime-app/$framework" local folder=$artifactsFolder/macos-app/$framework
rm -rf $folder rm -rf $folder
mkdir -p $folder mkdir -p $folder
@@ -180,7 +178,7 @@ PackageMacOSApp()
mkdir -p $folder/Radarr.app/Contents/MacOS mkdir -p $folder/Radarr.app/Contents/MacOS
echo "Copying Binaries" echo "Copying Binaries"
cp -r $artifactsFolder/$runtime/$framework/Radarr/* $folder/Radarr.app/Contents/MacOS cp -r $artifactsFolder/macos/$framework/Radarr/* $folder/Radarr.app/Contents/MacOS
echo "Removing Update Folder" echo "Removing Update Folder"
rm -r $folder/Radarr.app/Contents/MacOS/Radarr.Update rm -r $folder/Radarr.app/Contents/MacOS/Radarr.Update
@@ -227,8 +225,8 @@ Package()
PackageWindows "$framework" "$runtime" PackageWindows "$framework" "$runtime"
;; ;;
osx) osx)
PackageMacOS "$framework" "$runtime" PackageMacOS "$framework"
PackageMacOSApp "$framework" "$runtime" PackageMacOSApp "$framework"
;; ;;
esac esac
} }
@@ -328,14 +326,14 @@ then
Build Build
if [[ -z "$RID" || -z "$FRAMEWORK" ]]; if [[ -z "$RID" || -z "$FRAMEWORK" ]];
then then
PackageTests "net6.0" "win-x64" PackageTests "net5.0" "win-x64"
PackageTests "net6.0" "win-x86" PackageTests "net5.0" "win-x86"
PackageTests "net6.0" "linux-x64" PackageTests "net5.0" "linux-x64"
PackageTests "net6.0" "linux-musl-x64" PackageTests "net5.0" "linux-musl-x64"
PackageTests "net6.0" "osx-x64" PackageTests "net5.0" "osx-x64"
if [ "$ENABLE_BSD" = "YES" ]; if [ "$ENABLE_BSD" = "YES" ];
then then
PackageTests "net6.0" "freebsd-x64" PackageTests "net5.0" "freebsd-x64"
fi fi
else else
PackageTests "$FRAMEWORK" "$RID" PackageTests "$FRAMEWORK" "$RID"
@@ -364,19 +362,17 @@ then
if [[ -z "$RID" || -z "$FRAMEWORK" ]]; if [[ -z "$RID" || -z "$FRAMEWORK" ]];
then then
Package "net6.0" "win-x64" Package "net5.0" "win-x64"
Package "net6.0" "win-x86" Package "net5.0" "win-x86"
Package "net6.0" "linux-x64" Package "net5.0" "linux-x64"
Package "net6.0" "linux-musl-x64" Package "net5.0" "linux-musl-x64"
Package "net6.0" "linux-arm64" Package "net5.0" "linux-arm64"
Package "net6.0" "linux-musl-arm64" Package "net5.0" "linux-musl-arm64"
Package "net6.0" "linux-arm" Package "net5.0" "linux-arm"
Package "net6.0" "linux-musl-arm" Package "net5.0" "osx-x64"
Package "net6.0" "osx-x64"
Package "net6.0" "osx-arm64"
if [ "$ENABLE_BSD" = "YES" ]; if [ "$ENABLE_BSD" = "YES" ];
then then
Package "net6.0" "freebsd-x64" Package "net5.0" "freebsd-x64"
fi fi
else else
Package "$FRAMEWORK" "$RID" Package "$FRAMEWORK" "$RID"
+1 -1
View File
@@ -120,7 +120,7 @@ class Blocklist extends Component {
<PageToolbar> <PageToolbar>
<PageToolbarSection> <PageToolbarSection>
<PageToolbarButton <PageToolbarButton
label={translate('RemoveSelected')} label="Remove Selected"
iconName={icons.REMOVE} iconName={icons.REMOVE}
isDisabled={!selectedIds.length} isDisabled={!selectedIds.length}
isSpinning={isRemoving} isSpinning={isRemoving}
@@ -25,7 +25,6 @@ function HistoryDetails(props) {
releaseGroup, releaseGroup,
nzbInfoUrl, nzbInfoUrl,
downloadClient, downloadClient,
downloadClientName,
downloadId, downloadId,
age, age,
ageHours, ageHours,
@@ -33,8 +32,6 @@ function HistoryDetails(props) {
publishedDate publishedDate
} = data; } = data;
const downloadClientNameInfo = downloadClientName ?? downloadClient;
return ( return (
<DescriptionList> <DescriptionList>
<DescriptionListItem <DescriptionListItem
@@ -74,12 +71,11 @@ function HistoryDetails(props) {
} }
{ {
downloadClientNameInfo ? !!downloadClient &&
<DescriptionListItem <DescriptionListItem
title={translate('DownloadClient')} title={translate('DownloadClient')}
data={downloadClientNameInfo} data={downloadClient}
/> : />
null
} }
{ {
-11
View File
@@ -282,17 +282,6 @@ class Queue extends Component {
return !!(item && item.movieId); return !!(item && item.movieId);
}) })
)} )}
allPending={isConfirmRemoveModalOpen && (
selectedIds.every((id) => {
const item = items.find((i) => i.id === id);
if (!item) {
return false;
}
return item.status === 'delay' || item.status === 'downloadClientUnavailable';
})
)}
onRemovePress={this.onRemoveSelectedConfirmed} onRemovePress={this.onRemoveSelectedConfirmed}
onModalClose={this.onConfirmRemoveModalClose} onModalClose={this.onConfirmRemoveModalClose}
/> />
-1
View File
@@ -332,7 +332,6 @@ class QueueRow extends Component {
isOpen={isRemoveQueueItemModalOpen} isOpen={isRemoveQueueItemModalOpen}
sourceTitle={title} sourceTitle={title}
canIgnore={!!movie} canIgnore={!!movie}
isPending={isPending}
onRemovePress={this.onRemoveQueueItemModalConfirmed} onRemovePress={this.onRemoveQueueItemModalConfirmed}
onModalClose={this.onRemoveQueueItemModalClose} onModalClose={this.onRemoveQueueItemModalClose}
/> />
@@ -66,8 +66,7 @@ class RemoveQueueItemModal extends Component {
const { const {
isOpen, isOpen,
sourceTitle, sourceTitle,
canIgnore, canIgnore
isPending
} = this.props; } = this.props;
const { remove, blocklist } = this.state; const { remove, blocklist } = this.state;
@@ -90,22 +89,18 @@ class RemoveQueueItemModal extends Component {
{translate('RemoveFromQueueText', [sourceTitle])} {translate('RemoveFromQueueText', [sourceTitle])}
</div> </div>
{ <FormGroup>
isPending ? <FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
null :
<FormGroup>
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="remove" name="remove"
value={remove} value={remove}
helpTextWarning={translate('RemoveHelpTextWarning')} helpTextWarning={translate('RemoveHelpTextWarning')}
isDisabled={!canIgnore} isDisabled={!canIgnore}
onChange={this.onRemoveChange} onChange={this.onRemoveChange}
/> />
</FormGroup> </FormGroup>
}
<FormGroup> <FormGroup>
<FormLabel>{translate('BlocklistRelease')}</FormLabel> <FormLabel>{translate('BlocklistRelease')}</FormLabel>
@@ -142,7 +137,6 @@ RemoveQueueItemModal.propTypes = {
isOpen: PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
sourceTitle: PropTypes.string.isRequired, sourceTitle: PropTypes.string.isRequired,
canIgnore: PropTypes.bool.isRequired, canIgnore: PropTypes.bool.isRequired,
isPending: PropTypes.bool.isRequired,
onRemovePress: PropTypes.func.isRequired, onRemovePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };
@@ -67,8 +67,7 @@ class RemoveQueueItemsModal extends Component {
const { const {
isOpen, isOpen,
selectedCount, selectedCount,
canIgnore, canIgnore
allPending
} = this.props; } = this.props;
const { remove, blocklist } = this.state; const { remove, blocklist } = this.state;
@@ -83,34 +82,30 @@ class RemoveQueueItemsModal extends Component {
onModalClose={this.onModalClose} onModalClose={this.onModalClose}
> >
<ModalHeader> <ModalHeader>
{selectedCount > 1 ? translate('RemoveSelectedItems') : translate('RemoveSelectedItem')} Remove Selected Item{selectedCount > 1 ? 's' : ''}
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
<div className={styles.message}> <div className={styles.message}>
{selectedCount > 1 ? translate('AreYouSureYouWantToRemoveSelectedItemsFromQueue', selectedCount) : translate('AreYouSureYouWantToRemoveSelectedItemFromQueue')} {translate('AreYouSureYouWantToRemoveSelectedItemsFromQueue', [selectedCount, selectedCount > 1 ? 's' : ''])}
</div> </div>
{ <FormGroup>
allPending ? <FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
null :
<FormGroup>
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="remove" name="remove"
value={remove} value={remove}
helpTextWarning={translate('RemoveHelpTextWarning')} helpTextWarning={translate('RemoveHelpTextWarning')}
isDisabled={!canIgnore} isDisabled={!canIgnore}
onChange={this.onRemoveChange} onChange={this.onRemoveChange}
/> />
</FormGroup> </FormGroup>
}
<FormGroup> <FormGroup>
<FormLabel> <FormLabel>
{selectedCount > 1 ? translate('BlocklistReleases') : translate('BlocklistRelease')} Blocklist Release{selectedCount > 1 ? 's' : ''}
</FormLabel> </FormLabel>
<FormInputGroup <FormInputGroup
@@ -146,7 +141,6 @@ RemoveQueueItemsModal.propTypes = {
isOpen: PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
selectedCount: PropTypes.number.isRequired, selectedCount: PropTypes.number.isRequired,
canIgnore: PropTypes.bool.isRequired, canIgnore: PropTypes.bool.isRequired,
allPending: PropTypes.bool.isRequired,
onRemovePress: PropTypes.func.isRequired, onRemovePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };
@@ -161,7 +161,7 @@ class AddNewMovie extends Component {
{translate('YouCanAlsoSearch')} {translate('YouCanAlsoSearch')}
</div> </div>
<div> <div>
<Link to="https://wiki.servarr.com/radarr/faq#why-can-i-not-add-a-new-movie-to-radarr"> <Link to="https://wiki.servarr.com/radarr/faq#why-cant-i-add-a-new-movie-to-radarr">
{translate('CantFindMovie')} {translate('CantFindMovie')}
</Link> </Link>
</div> </div>
@@ -86,13 +86,6 @@ class AddNewMovieSearchResult extends Component {
} = this.state; } = this.state;
const linkProps = isExistingMovie ? { to: `/movie/${titleSlug}` } : { onPress: this.onPress }; const linkProps = isExistingMovie ? { to: `/movie/${titleSlug}` } : { onPress: this.onPress };
const posterWidth = 167;
const posterHeight = 250;
const elementStyle = {
width: `${posterWidth}px`,
height: `${posterHeight}px`
};
return ( return (
<div className={styles.searchResult}> <div className={styles.searchResult}>
@@ -109,7 +102,6 @@ class AddNewMovieSearchResult extends Component {
<div className={styles.posterContainer}> <div className={styles.posterContainer}>
<MoviePoster <MoviePoster
className={styles.poster} className={styles.poster}
style={elementStyle}
images={images} images={images}
size={250} size={250}
overflow={true} overflow={true}
@@ -122,7 +114,7 @@ class AddNewMovieSearchResult extends Component {
monitored={monitored} monitored={monitored}
hasFile={hasFile} hasFile={hasFile}
status={status} status={status}
posterWidth={posterWidth} posterWidth={167}
detailedProgressBar={true} detailedProgressBar={true}
queueStatus={queueStatus} queueStatus={queueStatus}
queueState={queueState} queueState={queueState}
@@ -191,7 +183,7 @@ class AddNewMovieSearchResult extends Component {
<div> <div>
<Label size={sizes.LARGE}> <Label size={sizes.LARGE}>
<HeartRating <HeartRating
ratings={ratings} rating={ratings.value}
iconSize={13} iconSize={13}
/> />
</Label> </Label>
@@ -43,15 +43,15 @@ class CalendarEvent extends Component {
const link = `/movie/${titleSlug}`; const link = `/movie/${titleSlug}`;
const eventType = []; const eventType = [];
if (inCinemas && moment(date).isSame(moment(inCinemas), 'day')) { if (moment(date).isSame(moment(inCinemas), 'day')) {
eventType.push('Cinemas'); eventType.push('Cinemas');
} }
if (physicalRelease && moment(date).isSame(moment(physicalRelease), 'day')) { if (moment(date).isSame(moment(physicalRelease), 'day')) {
eventType.push('Physical'); eventType.push('Physical');
} }
if (digitalRelease && moment(date).isSame(moment(digitalRelease), 'day')) { if (moment(date).isSame(moment(digitalRelease), 'day')) {
eventType.push('Digital'); eventType.push('Digital');
} }
-5
View File
@@ -16,9 +16,4 @@
color: #3a3f51; color: #3a3f51;
font-size: 21px; font-size: 21px;
line-height: inherit; line-height: inherit;
&.small {
color: #909293;
font-size: 18px;
}
} }
+1 -9
View File
@@ -1,7 +1,5 @@
import classNames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { sizes } from 'Helpers/Props';
import styles from './FieldSet.css'; import styles from './FieldSet.css';
class FieldSet extends Component { class FieldSet extends Component {
@@ -11,14 +9,13 @@ class FieldSet extends Component {
render() { render() {
const { const {
size,
legend, legend,
children children
} = this.props; } = this.props;
return ( return (
<fieldset className={styles.fieldSet}> <fieldset className={styles.fieldSet}>
<legend className={classNames(styles.legend, (size === sizes.SMALL) && styles.small)}> <legend className={styles.legend}>
{legend} {legend}
</legend> </legend>
{children} {children}
@@ -29,13 +26,8 @@ class FieldSet extends Component {
} }
FieldSet.propTypes = { FieldSet.propTypes = {
size: PropTypes.oneOf(sizes.all).isRequired,
legend: PropTypes.oneOfType([PropTypes.node, PropTypes.string]), legend: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
children: PropTypes.node children: PropTypes.node
}; };
FieldSet.defaultProps = {
size: sizes.MEDIUM
};
export default FieldSet; export default FieldSet;
@@ -166,9 +166,7 @@ class FilterBuilderModalContent extends Component {
</div> </div>
</div> </div>
<div className={styles.label}> <div className={styles.label}>Filters</div>
{translate('Filters')}
</div>
<div className={styles.rows}> <div className={styles.rows}>
{ {
@@ -6,7 +6,8 @@ import SelectInput from './SelectInput';
const availabilityOptions = [ const availabilityOptions = [
{ key: 'announced', value: translate('Announced') }, { key: 'announced', value: translate('Announced') },
{ key: 'inCinemas', value: translate('InCinemas') }, { key: 'inCinemas', value: translate('InCinemas') },
{ key: 'released', value: translate('Released') } { key: 'released', value: translate('Released') },
{ key: 'preDB', value: translate('PreDB') }
]; ];
function AvailabilitySelectInput(props) { function AvailabilitySelectInput(props) {
@@ -1,100 +0,0 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchDownloadClients } from 'Store/Actions/settingsActions';
import sortByName from 'Utilities/Array/sortByName';
import EnhancedSelectInput from './EnhancedSelectInput';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.downloadClients,
(state, { includeAny }) => includeAny,
(state, { protocol }) => protocol,
(downloadClients, includeAny, protocolFilter) => {
const {
isFetching,
isPopulated,
error,
items
} = downloadClients;
const filteredItems = items.filter((item) => item.protocol === protocolFilter);
const values = _.map(filteredItems.sort(sortByName), (downloadClient) => {
return {
key: downloadClient.id,
value: downloadClient.name
};
});
if (includeAny) {
values.unshift({
key: 0,
value: '(Any)'
});
}
return {
isFetching,
isPopulated,
error,
values
};
}
);
}
const mapDispatchToProps = {
dispatchFetchDownloadClients: fetchDownloadClients
};
class DownloadClientSelectInputConnector extends Component {
//
// Lifecycle
componentDidMount() {
if (!this.props.isPopulated) {
this.props.dispatchFetchDownloadClients();
}
}
//
// Listeners
onChange = ({ name, value }) => {
this.props.onChange({ name, value: parseInt(value) });
}
//
// Render
render() {
return (
<EnhancedSelectInput
{...this.props}
onChange={this.onChange}
/>
);
}
}
DownloadClientSelectInputConnector.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
includeAny: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired,
dispatchFetchDownloadClients: PropTypes.func.isRequired
};
DownloadClientSelectInputConnector.defaultProps = {
includeAny: false,
protocol: 'torrent'
};
export default connect(createMapStateToProps, mapDispatchToProps)(DownloadClientSelectInputConnector);
@@ -8,7 +8,6 @@ import AvailabilitySelectInput from './AvailabilitySelectInput';
import CaptchaInputConnector from './CaptchaInputConnector'; import CaptchaInputConnector from './CaptchaInputConnector';
import CheckInput from './CheckInput'; import CheckInput from './CheckInput';
import DeviceInputConnector from './DeviceInputConnector'; import DeviceInputConnector from './DeviceInputConnector';
import DownloadClientSelectInputConnector from './DownloadClientSelectInputConnector';
import EnhancedSelectInput from './EnhancedSelectInput'; import EnhancedSelectInput from './EnhancedSelectInput';
import EnhancedSelectInputConnector from './EnhancedSelectInputConnector'; import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
import FormInputHelpText from './FormInputHelpText'; import FormInputHelpText from './FormInputHelpText';
@@ -74,9 +73,6 @@ function getComponent(type) {
case inputTypes.INDEXER_FLAGS_SELECT: case inputTypes.INDEXER_FLAGS_SELECT:
return IndexerFlagsSelectInputConnector; return IndexerFlagsSelectInputConnector;
case inputTypes.DOWNLOAD_CLIENT_SELECT:
return DownloadClientSelectInputConnector;
case inputTypes.LANGUAGE_SELECT: case inputTypes.LANGUAGE_SELECT:
return LanguageSelectInputConnector; return LanguageSelectInputConnector;
@@ -64,7 +64,6 @@ function ProviderFieldFormGroup(props) {
label, label,
helpText, helpText,
helpLink, helpLink,
placeholder,
value, value,
type, type,
advanced, advanced,
@@ -97,7 +96,6 @@ function ProviderFieldFormGroup(props) {
label={label} label={label}
helpText={helpText} helpText={helpText}
helpLink={helpLink} helpLink={helpLink}
placeholder={placeholder}
value={value} value={value}
values={getSelectValues(selectOptions)} values={getSelectValues(selectOptions)}
errors={errors} errors={errors}
@@ -123,7 +121,6 @@ ProviderFieldFormGroup.propTypes = {
label: PropTypes.string.isRequired, label: PropTypes.string.isRequired,
helpText: PropTypes.string, helpText: PropTypes.string,
helpLink: PropTypes.string, helpLink: PropTypes.string,
placeholder: PropTypes.string,
value: PropTypes.any, value: PropTypes.any,
type: PropTypes.string.isRequired, type: PropTypes.string.isRequired,
advanced: PropTypes.bool.isRequired, advanced: PropTypes.bool.isRequired,
+2 -3
View File
@@ -1,5 +1,4 @@
.image { .heart {
align-content: center;
margin-right: 5px; margin-right: 5px;
vertical-align: -0.125em; color: $themeRed;
} }
File diff suppressed because one or more lines are too long
-5
View File
@@ -1,5 +0,0 @@
.image {
align-content: center;
margin-right: 5px;
vertical-align: -0.125em;
}
File diff suppressed because one or more lines are too long
@@ -81,7 +81,7 @@ class PageHeader extends Component {
<IconButton <IconButton
className={styles.donate} className={styles.donate}
name={icons.HEART} name={icons.HEART}
to="https://radarr.video/donate" to="https://opencollective.com/radarr"
size={14} size={14}
/> />
<IconButton <IconButton
+1 -3
View File
@@ -100,9 +100,7 @@ class PageJumpBar extends Component {
// Listeners // Listeners
onMeasure = ({ height }) => { onMeasure = ({ height }) => {
if (height > 0) { this.setState({ height });
this.setState({ height });
}
} }
// //
@@ -1,5 +0,0 @@
.image {
align-content: center;
margin-right: 5px;
vertical-align: -0.125em;
}
@@ -1,58 +0,0 @@
import PropTypes from 'prop-types';
import React, { PureComponent } from 'react';
import styles from './RottenTomatoRating.css';
class RottenTomatoRating extends PureComponent {
//
// Render
render() {
const {
ratings,
hideIcon,
iconSize
} = this.props;
const rtRotten = 'data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTYwIDU2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNDQ1LjE4NSA0NDQuNjg0Yy03OS4zNjkgNC4xNjctOTUuNTg3LTg2LjY1Mi0xMjYuNzI2LTg2LjAwNi0xMy4yNjguMjc5LTIzLjcyNiAxNC4xNTEtMTkuMTMzIDMwLjMyIDIuNTI1IDguODg4IDkuNTMgMjEuOTIzIDEzLjk0NCAzMC4wMTEgMTUuNTcgMjguNTQ0LTcuNDQ3IDYwLjg0NS0zNC4zODMgNjMuNTc3LTQ0Ljc2IDQuNTQtNjMuNDMzLTIxLjQyNi02Mi4yNzgtNDguMDA3IDEuMy0yOS44NCAyNi42LTYwLjMzMS42NS03My4zMDUtMjcuMTk0LTEzLjU5Ny00OS4zMDEgMzkuNTcyLTc1LjMyNSA1MS40MzktMjMuNTUzIDEwLjc0MS01Ni4yNDggMi40MTMtNjcuODcyLTIzLjc0MS04LjE2NC0xOC4zNzktNi42OC01My43NjggMjkuNjctNjcuMjcgMjIuNzA2LTguNDMzIDczLjMwNSAxMS4wMjkgNzUuOS0xMy42MjMgMi45OTItMjguNDE2LTUzLjE1NS0zMC44MTItNzAuMDYtMzcuNjI2LTI5LjkxMi0xMi4wNTUtNDcuNTY3LTM3Ljg1LTMzLjczNC02NS41MjIgMTAuMzc4LTIwLjc1NyA0MC45MTUtMjkuMjAzIDY0LjIyMy0yMC4xMSAyNy45MjIgMTAuODkyIDMyLjQwNCAzOS44NTMgNDYuNzEgNTEuODk3IDEyLjMyNCAxMC4zOCAyOS4xOSAxMS42OCA0MC4yMiA0LjU0MyA4LjEzNS01LjI2NSAxMC44NDMtMTYuODI4IDcuNzc0LTI3LjM5LTQuMDctMTQuMDIzLTE0Ljg3NS0yMi43NzMtMjUuNDE1LTMxLjM0Ni0xOC43NTgtMTUuMjQ5LTQ1LjI0LTI4LjM2LTI5LjIyMi02OS45ODMgMTMuMTMtMzQuMTEgNTEuNjQyLTM1LjM0IDUxLjY0Mi0zNS4zNCAxNS4zLTEuNzIgMjkuMDAyIDIuOSA0MC4xNjcgMTIuODc1IDE0LjkyNyAxMy4zMzUgMTcuODM0IDMxLjE2IDE1LjMzNiA1MC4xNzYtMi4yODMgMTcuMzU4LTguNDI2IDMyLjU2LTExLjYzIDQ5Ljc1OS0zLjcxNyAxOS45NjYgNi45NTQgNDAuMDg2IDI3LjI0OSA0MC44NjkgMjYuNjk0IDEuMDMxIDM0LjY5OC0xOS40ODYgMzcuOTY0LTMyLjQ5MiA0Ljc4Mi0xOS4wMjggMTEuMDU4LTM2LjY5NCAyOC43MTgtNDcuODIgMjUuMzQ2LTE1Ljk3IDYwLjU1Mi0xMi40NyA3Ni44ODYgMTguMjIyIDEyLjkyIDI0LjI4NCA4Ljc3MiA1Ny43MTUtMTEuMDQ3IDc1Ljk3LTguODkyIDguMTg4LTE5LjU4NCAxMS4wNzUtMzEuMTQ4IDExLjE1Ni0xNi41ODUuMTE3LTMzLjE2Mi0uMjktNDguNTU2IDcuNDcxLTEwLjQ4IDUuMjgxLTE1LjA0NyAxMy44ODgtMTUuMDQ1IDI1LjQyMyAwIDExLjI0MiA1Ljg1MyAxOC41ODUgMTUuMzM2IDIzLjM2MyAxNy44NiA5LjAwMyAzNy41NzcgMTAuODQzIDU2Ljg3MSAxNC4yMjIgMjcuOTggNC45IDUyLjU4MSAxNC43NTUgNjguMzc1IDQwLjcyLjE0Mi4yMjguMjguNDU4LjQxNS42OSAxOC4xMzkgMzAuNzQxLS44MzEgNzUuMDA1LTM2LjQ3NiA3Ni44NzgiIGZpbGw9IiMwQUM4NTUiLz48L3N2Zz4=';
const rtFresh = 'data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTYwIDU2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtNDc4LjI5IDI5Ni45OGMtMy45OS02My45NjYtMzYuNTItMTExLjgyLTg1LjQ2OC0xMzguNTggMC4yNzggMS41Ni0xLjEwOSAzLjUwOC0yLjY4OCAyLjgxOC0zMi4wMTYtMTQuMDA2LTg2LjMyOCAzMS4zMi0xMjQuMjggNy41ODQgMC4yODUgOC41MTktMS4zNzggNTAuMDcyLTU5LjkxNCA1Mi40ODMtMS4zODIgMC4wNTYtMi4xNDItMS4zNTUtMS4yNjgtMi4zNTQgNy44MjgtOC45MjkgMTUuNzMyLTMxLjUzNSA0LjM2Ny00My41ODYtMjQuMzM4IDIxLjgxLTM4LjQ3MiAzMC4wMTctODUuMTM4IDE5LjE4Ni0yOS44NzggMzEuMjQxLTQ2LjgwOSA3NC00My40ODUgMTI3LjI2IDYuNzggMTA4Ljc0IDEwOC42MyAxNzAuODkgMjExLjE5IDE2NC40OSAxMDIuNTYtNi4zOTUgMTkzLjQ3LTgwLjU3MiAxODYuNjgtMTg5LjMxIiBmaWxsPSIjRkEzMjBBIi8+PHBhdGggZD0iTTI5MS4zNzUgMTMyLjI5M2MyMS4wNzUtNS4wMjMgODEuNjkzLS40OSAxMDEuMTE0IDI1LjI3NCAxLjE2NiAxLjU0NS0uNDc1IDQuNDY4LTIuMzU1IDMuNjQ4LTMyLjAxNi0xNC4wMDYtODYuMzI4IDMxLjMyLTEyNC4yODIgNy41ODQuMjg1IDguNTE5LTEuMzc4IDUwLjA3Mi01OS45MTQgNTIuNDgzLTEuMzgyLjA1Ni0yLjE0Mi0xLjM1NS0xLjI2OC0yLjM1NCA3LjgyOC04LjkyOSAxNS43My0zMS41MzUgNC4zNjctNDMuNTg2LTI2LjUxMiAyMy43NTgtNDAuODg0IDMxLjM5Mi05OC40MjYgMTUuODM4LTEuODgzLS41MDgtMS4yNDEtMy41MzUuNzYyLTQuMjk4IDEwLjg3Ni00LjE1NyAzNS41MTUtMjIuMzYxIDU4LjgyNC0zMC4zODUgNC40MzgtMS41MjYgOC44NjItMi43MSAxMy4xOC0zLjQtMjUuNjY1LTIuMjkzLTM3LjIzNS01Ljg2Mi01My41NTktMy40LTEuNzg5LjI3LTMuMDA0LTEuODEzLTEuODk1LTMuMjQxIDIxLjk5NS0yOC4zMzIgNjIuNTEzLTM2Ljg4OCA4Ny41MTItMjEuODM3LTE1LjQxLTE5LjA5NC0yNy40OC0zNC4zMjEtMjcuNDgtMzQuMzIxbDI4LjYwMS0xNi4yNDZzMTEuODE3IDI2LjQgMjAuNDE0IDQ1LjYxNGMyMS4yNzUtMzEuNDM1IDYwLjg2LTM0LjMzNiA3Ny41ODUtMTIuMDMzLjk5MiAxLjMyNi0uMDQ1IDMuMjEtMS43MDIgMy4xNzEtMTMuNjEyLS4zMzEtMjEuMTA3IDEyLjA1LTIxLjY3NSAyMS40NjZsLjE5Ny4wMjMiIGZpbGw9IiMwMDkxMkQiLz48L3N2Zz4=';
const rating = ratings.rottenTomatoes;
let ratingString = '0%';
if (rating) {
ratingString = `${rating.value}%`;
}
return (
<span>
{
!hideIcon &&
<img
className={styles.image}
src={rating.value > 50 ? rtFresh : rtRotten}
style={{
height: `${iconSize}px`
}}
/>
}
{ratingString}
</span>
);
}
}
RottenTomatoRating.propTypes = {
ratings: PropTypes.object.isRequired,
iconSize: PropTypes.number.isRequired,
hideIcon: PropTypes.bool
};
RottenTomatoRating.defaultProps = {
iconSize: 14
};
export default RottenTomatoRating;
-5
View File
@@ -1,5 +0,0 @@
.image {
align-content: center;
margin-right: 5px;
vertical-align: -0.125em;
}
-57
View File
@@ -1,57 +0,0 @@
import PropTypes from 'prop-types';
import React, { PureComponent } from 'react';
import styles from './TmdbRating.css';
class TmdbRating extends PureComponent {
//
// Render
render() {
const {
ratings,
hideIcon,
iconSize
} = this.props;
const tmdbImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxOTAuMjQgODEuNTIiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iYSIgeTE9IjQwLjc2IiB4Mj0iMTkwLjI0IiB5Mj0iNDAuNzYiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM5MGNlYTEiLz48c3RvcCBvZmZzZXQ9Ii41NiIgc3RvcC1jb2xvcj0iIzNjYmVjOSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzAwYjNlNSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxwYXRoIGQ9Ik0xMDUuNjcgMzYuMDZoNjYuOWExNy42NyAxNy42NyAwIDAwMTcuNjctMTcuNjZBMTcuNjcgMTcuNjcgMCAwMDE3Mi41Ny43M2gtNjYuOUExNy42NyAxNy42NyAwIDAwODggMTguNGExNy42NyAxNy42NyAwIDAwMTcuNjcgMTcuNjZ6bS04OCA0NWg3Ni45YTE3LjY3IDE3LjY3IDAgMDAxNy42Ny0xNy42NiAxNy42NyAxNy42NyAwIDAwLTE3LjY3LTE3LjY3aC03Ni45QTE3LjY3IDE3LjY3IDAgMDAwIDYzLjRhMTcuNjcgMTcuNjcgMCAwMDE3LjY3IDE3LjY2em0tNy4yNi00NS42NGg3LjhWNi45MmgxMC4xVjBoLTI4djYuOWgxMC4xem0yOC4xIDBoNy44VjguMjVoLjFsOSAyNy4xNWg2bDkuMy0yNy4xNWguMVYzNS40aDcuOFYwSDY2Ljc2bC04LjIgMjMuMWgtLjFMNTAuMzEgMGgtMTEuOHptMTEzLjkyIDIwLjI1YTE1LjA3IDE1LjA3IDAgMDAtNC41Mi01LjUyIDE4LjU3IDE4LjU3IDAgMDAtNi42OC0zLjA4IDMzLjU0IDMzLjU0IDAgMDAtOC4wNy0xaC0xMS43djM1LjRoMTIuNzVhMjQuNTggMjQuNTggMCAwMDcuNTUtMS4xNSAxOS4zNCAxOS4zNCAwIDAwNi4zNS0zLjMyIDE2LjI3IDE2LjI3IDAgMDA0LjM3LTUuNSAxNi45MSAxNi45MSAwIDAwMS42My03LjU4IDE4LjUgMTguNSAwIDAwLTEuNjgtOC4yNXpNMTQ1IDY4LjZhOC44IDguOCAwIDAxLTIuNjQgMy40IDEwLjcgMTAuNyAwIDAxLTQgMS44MiAyMS41NyAyMS41NyAwIDAxLTUgLjU1aC00LjA1di0yMWg0LjZhMTcgMTcgMCAwMTQuNjcuNjMgMTEuNjYgMTEuNjYgMCAwMTMuODggMS44N0E5LjE0IDkuMTQgMCAwMTE0NSA1OWE5Ljg3IDkuODcgMCAwMTEgNC41MiAxMS44OSAxMS44OSAwIDAxLTEgNS4wOHptNDQuNjMtLjEzYTggOCAwIDAwLTEuNTgtMi42MiA4LjM4IDguMzggMCAwMC0yLjQyLTEuODUgMTAuMzEgMTAuMzEgMCAwMC0zLjE3LTF2LS4xYTkuMjIgOS4yMiAwIDAwNC40Mi0yLjgyIDcuNDMgNy40MyAwIDAwMS42OC01IDguNDIgOC40MiAwIDAwLTEuMTUtNC42NSA4LjA5IDguMDkgMCAwMC0zLTIuNzIgMTIuNTYgMTIuNTYgMCAwMC00LjE4LTEuMyAzMi44NCAzMi44NCAwIDAwLTQuNjItLjMzaC0xMy4ydjM1LjRoMTQuNWEyMi40MSAyMi40MSAwIDAwNC43Mi0uNSAxMy41MyAxMy41MyAwIDAwNC4yOC0xLjY1IDkuNDIgOS40MiAwIDAwMy4xLTMgOC41MiA4LjUyIDAgMDAxLjItNC42OCA5LjM5IDkuMzkgMCAwMC0uNTUtMy4xOHptLTE5LjQyLTE1Ljc1aDUuM2ExMCAxMCAwIDAxMS44NS4xOCA2LjE4IDYuMTggMCAwMTEuNy41NyAzLjM5IDMuMzkgMCAwMTEuMjIgMS4xMyAzLjIyIDMuMjIgMCAwMS40OCAxLjgyIDMuNjMgMy42MyAwIDAxLS40MyAxLjggMy40IDMuNCAwIDAxLTEuMTIgMS4yIDQuOTIgNC45MiAwIDAxLTEuNTguNjUgNy41MSA3LjUxIDAgMDEtMS43Ny4yaC01LjY1em0xMS43MiAyMGEzLjkgMy45IDAgMDEtMS4yMiAxLjMgNC42NCA0LjY0IDAgMDEtMS42OC43IDguMTggOC4xOCAwIDAxLTEuODIuMmgtN3YtOGg1LjlhMTUuMzUgMTUuMzUgMCAwMTIgLjE1IDguNDcgOC40NyAwIDAxMi4wNS41NSA0IDQgMCAwMTEuNTcgMS4xOCAzLjExIDMuMTEgMCAwMS42MyAyIDMuNzEgMy43MSAwIDAxLS40MyAxLjkyeiIgZmlsbD0idXJsKCNhKSIvPjwvc3ZnPg==';
const rating = ratings.tmdb;
let ratingString = '0%';
if (rating) {
ratingString = `${rating.value * 10}%`;
}
return (
<span title={`${rating.votes} votes`}>
{
!hideIcon &&
<img
className={styles.image}
src={tmdbImage}
style={{
height: `${iconSize}px`
}}
/>
}
{ratingString}
</span>
);
}
}
TmdbRating.propTypes = {
ratings: PropTypes.object.isRequired,
iconSize: PropTypes.number.isRequired,
hideIcon: PropTypes.bool
};
TmdbRating.defaultProps = {
iconSize: 14
};
export default TmdbRating;
@@ -73,7 +73,7 @@ function getInfoRowProps(row, props) {
return { return {
title: translate('Ratings'), title: translate('Ratings'),
iconName: icons.HEART, iconName: icons.HEART,
label: `${props.ratings.tmdb.value * 10}%` label: `${props.ratings.value * 10}%`
}; };
} }
@@ -112,7 +112,7 @@ function DiscoverMoviePosterInfo(props) {
return ( return (
<div className={styles.info}> <div className={styles.info}>
<HeartRating <HeartRating
ratings={ratings} rating={ratings.value}
/> />
</div> </div>
); );
@@ -129,7 +129,7 @@ DiscoverMoviePosterInfo.propTypes = {
digitalRelease: PropTypes.string, digitalRelease: PropTypes.string,
physicalRelease: PropTypes.string, physicalRelease: PropTypes.string,
runtime: PropTypes.number, runtime: PropTypes.number,
ratings: PropTypes.arrayOf(PropTypes.object).isRequired, ratings: PropTypes.object,
sortKey: PropTypes.string.isRequired, sortKey: PropTypes.string.isRequired,
showRelativeDates: PropTypes.bool.isRequired, showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired, shortDateFormat: PropTypes.string.isRequired,
@@ -246,7 +246,7 @@ class DiscoverMovieRow extends Component {
className={styles[name]} className={styles[name]}
> >
<HeartRating <HeartRating
ratings={ratings} rating={ratings.value}
/> />
</VirtualTableRowCell> </VirtualTableRowCell>
); );
@@ -373,7 +373,7 @@ DiscoverMovieRow.propTypes = {
digitalRelease: PropTypes.string, digitalRelease: PropTypes.string,
runtime: PropTypes.number, runtime: PropTypes.number,
genres: PropTypes.arrayOf(PropTypes.string).isRequired, genres: PropTypes.arrayOf(PropTypes.string).isRequired,
ratings: PropTypes.arrayOf(PropTypes.object).isRequired, ratings: PropTypes.object.isRequired,
certification: PropTypes.string, certification: PropTypes.string,
collection: PropTypes.object, collection: PropTypes.object,
columns: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired,
-2
View File
@@ -13,7 +13,6 @@ export const QUALITY_PROFILE_SELECT = 'qualityProfileSelect';
export const ROOT_FOLDER_SELECT = 'rootFolderSelect'; export const ROOT_FOLDER_SELECT = 'rootFolderSelect';
export const INDEXER_FLAGS_SELECT = 'indexerFlagsSelect'; export const INDEXER_FLAGS_SELECT = 'indexerFlagsSelect';
export const LANGUAGE_SELECT = 'languageSelect'; export const LANGUAGE_SELECT = 'languageSelect';
export const DOWNLOAD_CLIENT_SELECT = 'downloadClientSelect';
export const SELECT = 'select'; export const SELECT = 'select';
export const DYNAMIC_SELECT = 'dynamicSelect'; export const DYNAMIC_SELECT = 'dynamicSelect';
export const TAG = 'tag'; export const TAG = 'tag';
@@ -36,7 +35,6 @@ export const all = [
PASSWORD, PASSWORD,
PATH, PATH,
QUALITY_PROFILE_SELECT, QUALITY_PROFILE_SELECT,
DOWNLOAD_CLIENT_SELECT,
ROOT_FOLDER_SELECT, ROOT_FOLDER_SELECT,
INDEXER_FLAGS_SELECT, INDEXER_FLAGS_SELECT,
LANGUAGE_SELECT, LANGUAGE_SELECT,
@@ -19,7 +19,6 @@ import { align, icons, kinds, scrollDirections } from 'Helpers/Props';
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal'; import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal'; import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal'; import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
import getErrorMessage from 'Utilities/Object/getErrorMessage'; import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds'; import getSelectedIds from 'Utilities/Table/getSelectedIds';
@@ -41,11 +40,6 @@ const columns = [
isSortable: true, isSortable: true,
isVisible: true isVisible: true
}, },
{
name: 'releaseGroup',
label: translate('ReleaseGroup'),
isVisible: true
},
{ {
name: 'quality', name: 'quality',
label: translate('Quality'), label: translate('Quality'),
@@ -89,7 +83,6 @@ const SELECT = 'select';
const MOVIE = 'movie'; const MOVIE = 'movie';
const LANGUAGE = 'language'; const LANGUAGE = 'language';
const QUALITY = 'quality'; const QUALITY = 'quality';
const RELEASE_GROUP = 'releaseGroup';
class InteractiveImportModalContent extends Component { class InteractiveImportModalContent extends Component {
@@ -209,11 +202,10 @@ class InteractiveImportModalContent extends Component {
const errorMessage = getErrorMessage(error, translate('UnableToLoadManualImportItems')); const errorMessage = getErrorMessage(error, translate('UnableToLoadManualImportItems'));
const bulkSelectOptions = [ const bulkSelectOptions = [
{ key: SELECT, value: translate('SelectDotDot'), disabled: true }, {
key: SELECT, value: translate('SelectDotDot'), disabled: true },
{ key: LANGUAGE, value: translate('SelectLanguage') }, { key: LANGUAGE, value: translate('SelectLanguage') },
{ key: QUALITY, value: translate('SelectQuality') }, { key: QUALITY, value: translate('SelectQuality') }];
{ key: RELEASE_GROUP, value: translate('SelectReleaseGroup') }
];
if (allowMovieChange) { if (allowMovieChange) {
bulkSelectOptions.splice(1, 0, { bulkSelectOptions.splice(1, 0, {
@@ -380,13 +372,6 @@ class InteractiveImportModalContent extends Component {
real={false} real={false}
onModalClose={this.onSelectModalClose} onModalClose={this.onSelectModalClose}
/> />
<SelectReleaseGroupModal
isOpen={selectModalOpen === RELEASE_GROUP}
ids={selectedIds}
releaseGroup=""
onModalClose={this.onSelectModalClose}
/>
</ModalContent> </ModalContent>
); );
} }
@@ -110,8 +110,7 @@ class InteractiveImportModalContentConnector extends Component {
const { const {
movie, movie,
quality, quality,
languages, languages
releaseGroup
} = item; } = item;
if (!movie) { if (!movie) {
@@ -133,7 +132,6 @@ class InteractiveImportModalContentConnector extends Component {
path: item.path, path: item.path,
folderName: item.folderName, folderName: item.folderName,
movieId: movie.id, movieId: movie.id,
releaseGroup,
quality, quality,
languages, languages,
downloadId: this.props.downloadId downloadId: this.props.downloadId
@@ -11,7 +11,6 @@ import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal'; import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal'; import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal'; import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
import MovieLanguage from 'Movie/MovieLanguage'; import MovieLanguage from 'Movie/MovieLanguage';
import MovieQuality from 'Movie/MovieQuality'; import MovieQuality from 'Movie/MovieQuality';
import formatBytes from 'Utilities/Number/formatBytes'; import formatBytes from 'Utilities/Number/formatBytes';
@@ -29,7 +28,6 @@ class InteractiveImportRow extends Component {
this.state = { this.state = {
isSelectMovieModalOpen: false, isSelectMovieModalOpen: false,
isSelectReleaseGroupModalOpen: false,
isSelectQualityModalOpen: false, isSelectQualityModalOpen: false,
isSelectLanguageModalOpen: false isSelectLanguageModalOpen: false
}; };
@@ -105,10 +103,6 @@ class InteractiveImportRow extends Component {
this.setState({ isSelectMovieModalOpen: true }); this.setState({ isSelectMovieModalOpen: true });
} }
onSelectReleaseGroupPress = () => {
this.setState({ isSelectReleaseGroupModalOpen: true });
}
onSelectQualityPress = () => { onSelectQualityPress = () => {
this.setState({ isSelectQualityModalOpen: true }); this.setState({ isSelectQualityModalOpen: true });
} }
@@ -122,11 +116,6 @@ class InteractiveImportRow extends Component {
this.selectRowAfterChange(changed); this.selectRowAfterChange(changed);
} }
onSelectReleaseGroupModalClose = (changed) => {
this.setState({ isSelectReleaseGroupModalOpen: false });
this.selectRowAfterChange(changed);
}
onSelectQualityModalClose = (changed) => { onSelectQualityModalClose = (changed) => {
this.setState({ isSelectQualityModalOpen: false }); this.setState({ isSelectQualityModalOpen: false });
this.selectRowAfterChange(changed); this.selectRowAfterChange(changed);
@@ -148,7 +137,6 @@ class InteractiveImportRow extends Component {
movie, movie,
quality, quality,
languages, languages,
releaseGroup,
size, size,
rejections, rejections,
isReprocessing, isReprocessing,
@@ -159,8 +147,7 @@ class InteractiveImportRow extends Component {
const { const {
isSelectMovieModalOpen, isSelectMovieModalOpen,
isSelectQualityModalOpen, isSelectQualityModalOpen,
isSelectLanguageModalOpen, isSelectLanguageModalOpen
isSelectReleaseGroupModalOpen
} = this.state; } = this.state;
const movieTitle = movie ? movie.title + ( movie.year > 0 ? ` (${movie.year})` : '') : ''; const movieTitle = movie ? movie.title + ( movie.year > 0 ? ` (${movie.year})` : '') : '';
@@ -168,7 +155,6 @@ class InteractiveImportRow extends Component {
const showMoviePlaceholder = isSelected && !movie; const showMoviePlaceholder = isSelected && !movie;
const showQualityPlaceholder = isSelected && !quality; const showQualityPlaceholder = isSelected && !quality;
const showLanguagePlaceholder = isSelected && !languages && !isReprocessing; const showLanguagePlaceholder = isSelected && !languages && !isReprocessing;
const showReleaseGroupPlaceholder = isSelected && !releaseGroup;
return ( return (
<TableRow> <TableRow>
@@ -195,17 +181,6 @@ class InteractiveImportRow extends Component {
} }
</TableRowCellButton> </TableRowCellButton>
<TableRowCellButton
title={translate('ClickToChangeReleaseGroup')}
onPress={this.onSelectReleaseGroupPress}
>
{
showReleaseGroupPlaceholder ?
<InteractiveImportRowCellPlaceholder /> :
releaseGroup
}
</TableRowCellButton>
<TableRowCellButton <TableRowCellButton
className={styles.quality} className={styles.quality}
title={translate('ClickToChangeQuality')} title={translate('ClickToChangeQuality')}
@@ -293,13 +268,6 @@ class InteractiveImportRow extends Component {
onModalClose={this.onSelectMovieModalClose} onModalClose={this.onSelectMovieModalClose}
/> />
<SelectReleaseGroupModal
isOpen={isSelectReleaseGroupModalOpen}
ids={[id]}
releaseGroup={releaseGroup ?? ''}
onModalClose={this.onSelectReleaseGroupModalClose}
/>
<SelectQualityModal <SelectQualityModal
isOpen={isSelectQualityModalOpen} isOpen={isSelectQualityModalOpen}
ids={[id]} ids={[id]}
@@ -328,7 +296,6 @@ InteractiveImportRow.propTypes = {
movie: PropTypes.object, movie: PropTypes.object,
quality: PropTypes.object, quality: PropTypes.object,
languages: PropTypes.arrayOf(PropTypes.object), languages: PropTypes.arrayOf(PropTypes.object),
releaseGroup: PropTypes.string,
size: PropTypes.number.isRequired, size: PropTypes.number.isRequired,
rejections: PropTypes.arrayOf(PropTypes.object).isRequired, rejections: PropTypes.arrayOf(PropTypes.object).isRequired,
isReprocessing: PropTypes.bool, isReprocessing: PropTypes.bool,
@@ -128,7 +128,7 @@ class SelectLanguageModalContent extends Component {
kind={kinds.SUCCESS} kind={kinds.SUCCESS}
onPress={this.onLanguageSelect} onPress={this.onLanguageSelect}
> >
{translate('SelectLanguages')} {translate('SelectLanguges')}
</Button> </Button>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>
@@ -1,37 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Modal from 'Components/Modal/Modal';
import SelectReleaseGroupModalContentConnector from './SelectReleaseGroupModalContentConnector';
class SelectReleaseGroupModal extends Component {
//
// Render
render() {
const {
isOpen,
onModalClose,
...otherProps
} = this.props;
return (
<Modal
isOpen={isOpen}
onModalClose={onModalClose}
>
<SelectReleaseGroupModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
</Modal>
);
}
}
SelectReleaseGroupModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default SelectReleaseGroupModal;
@@ -1,99 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import Button from 'Components/Link/Button';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
class SelectReleaseGroupModalContent extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
const {
releaseGroup
} = props;
this.state = {
releaseGroup
};
}
//
// Listeners
onReleaseGroupChange = ({ value }) => {
this.setState({ releaseGroup: value });
}
onReleaseGroupSelect = () => {
this.props.onReleaseGroupSelect(this.state);
}
//
// Render
render() {
const {
onModalClose
} = this.props;
const {
releaseGroup
} = this.state;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{translate('ManualImportSetReleaseGroup')}
</ModalHeader>
<ModalBody>
<Form>
<FormGroup>
<FormLabel>{translate('ReleaseGroup')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="releaseGroup"
value={releaseGroup}
onChange={this.onReleaseGroupChange}
/>
</FormGroup>
</Form>
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>
{translate('Cancel')}
</Button>
<Button
kind={kinds.SUCCESS}
onPress={this.onReleaseGroupSelect}
>
{translate('SetReleaseGroup')}
</Button>
</ModalFooter>
</ModalContent>
);
}
}
SelectReleaseGroupModalContent.propTypes = {
releaseGroup: PropTypes.string.isRequired,
onReleaseGroupSelect: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default SelectReleaseGroupModalContent;
@@ -1,54 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { reprocessInteractiveImportItems, updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
import SelectReleaseGroupModalContent from './SelectReleaseGroupModalContent';
const mapDispatchToProps = {
dispatchUpdateInteractiveImportItems: updateInteractiveImportItems,
dispatchReprocessInteractiveImportItems: reprocessInteractiveImportItems
};
class SelectReleaseGroupModalContentConnector extends Component {
//
// Listeners
onReleaseGroupSelect = ({ releaseGroup }) => {
const {
ids,
dispatchUpdateInteractiveImportItems,
dispatchReprocessInteractiveImportItems
} = this.props;
dispatchUpdateInteractiveImportItems({
ids,
releaseGroup
});
dispatchReprocessInteractiveImportItems({ ids });
this.props.onModalClose(true);
}
//
// Render
render() {
return (
<SelectReleaseGroupModalContent
{...this.props}
onReleaseGroupSelect={this.onReleaseGroupSelect}
/>
);
}
}
SelectReleaseGroupModalContentConnector.propTypes = {
ids: PropTypes.arrayOf(PropTypes.number).isRequired,
dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired,
dispatchReprocessInteractiveImportItems: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default connect(null, mapDispatchToProps)(SelectReleaseGroupModalContentConnector);
@@ -155,7 +155,7 @@ DeleteMovieModalContent.propTypes = {
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
path: PropTypes.string.isRequired, path: PropTypes.string.isRequired,
hasFile: PropTypes.bool.isRequired, hasFile: PropTypes.bool.isRequired,
sizeOnDisk: PropTypes.number.isRequired, sizeOnDisk: PropTypes.string.isRequired,
onDeletePress: PropTypes.func.isRequired, onDeletePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };
+4 -5
View File
@@ -5,7 +5,7 @@
.header { .header {
position: relative; position: relative;
width: 100%; width: 100%;
height: 375px; height: 350px;
} }
.errorMessage { .errorMessage {
@@ -41,8 +41,8 @@
.poster { .poster {
flex-shrink: 0; flex-shrink: 0;
margin-right: 35px; margin-right: 35px;
width: 217px; width: 200px;
height: 319px; height: 294px;
} }
.info { .info {
@@ -151,8 +151,7 @@
.qualityProfileName, .qualityProfileName,
.statusName, .statusName,
.studio, .studio,
.collection, .collection {
.genres {
font-weight: 300; font-weight: 300;
font-size: 17px; font-size: 17px;
} }
+12 -49
View File
@@ -3,8 +3,8 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs'; import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import TextTruncate from 'react-text-truncate'; import TextTruncate from 'react-text-truncate';
import HeartRating from 'Components/HeartRating';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import ImdbRating from 'Components/ImdbRating';
import InfoLabel from 'Components/InfoLabel'; import InfoLabel from 'Components/InfoLabel';
import IconButton from 'Components/Link/IconButton'; import IconButton from 'Components/Link/IconButton';
import Marquee from 'Components/Marquee'; import Marquee from 'Components/Marquee';
@@ -16,8 +16,6 @@ import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton'; import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection'; import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator'; import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import RottenTomatoRating from 'Components/RottenTomatoRating';
import TmdbRating from 'Components/TmdbRating';
import Popover from 'Components/Tooltip/Popover'; import Popover from 'Components/Tooltip/Popover';
import Tooltip from 'Components/Tooltip/Tooltip'; import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props'; import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
@@ -268,7 +266,6 @@ class MovieDetails extends Component {
qualityProfileId, qualityProfileId,
monitored, monitored,
studio, studio,
genres,
collection, collection,
overview, overview,
youTubeTrailerId, youTubeTrailerId,
@@ -451,6 +448,17 @@ class MovieDetails extends Component {
</span> </span>
} }
{
!!ratings &&
<span className={styles.rating}>
<HeartRating
rating={ratings.value}
iconSize={20}
hideHeart={isSmallScreen}
/>
</span>
}
{ {
<span className={styles.links}> <span className={styles.links}>
<Tooltip <Tooltip
@@ -492,36 +500,6 @@ class MovieDetails extends Component {
</div> </div>
</div> </div>
<div className={styles.details}>
{
!!ratings.tmdb &&
<span className={styles.rating}>
<TmdbRating
ratings={ratings}
iconSize={20}
/>
</span>
}
{
!!ratings.imdb &&
<span className={styles.rating}>
<ImdbRating
ratings={ratings}
iconSize={20}
/>
</span>
}
{
!!ratings.rottenTomatoes &&
<span className={styles.rating}>
<RottenTomatoRating
ratings={ratings}
iconSize={20}
/>
</span>
}
</div>
<div className={styles.detailsLabels}> <div className={styles.detailsLabels}>
<InfoLabel <InfoLabel
className={styles.detailsInfoLabel} className={styles.detailsInfoLabel}
@@ -604,19 +582,6 @@ class MovieDetails extends Component {
</span> </span>
</InfoLabel> </InfoLabel>
} }
{
!!genres.length && !isSmallScreen &&
<InfoLabel
className={styles.detailsInfoLabel}
title={translate('Genres')}
size={sizes.LARGE}
>
<span className={styles.genres}>
{genres.join(', ')}
</span>
</InfoLabel>
}
</div> </div>
<Measure onMeasure={this.onMeasure}> <Measure onMeasure={this.onMeasure}>
@@ -801,7 +766,6 @@ MovieDetails.propTypes = {
monitored: PropTypes.bool.isRequired, monitored: PropTypes.bool.isRequired,
status: PropTypes.string.isRequired, status: PropTypes.string.isRequired,
studio: PropTypes.string, studio: PropTypes.string,
genres: PropTypes.arrayOf(PropTypes.string).isRequired,
collection: PropTypes.object, collection: PropTypes.object,
youTubeTrailerId: PropTypes.string, youTubeTrailerId: PropTypes.string,
isAvailable: PropTypes.bool.isRequired, isAvailable: PropTypes.bool.isRequired,
@@ -834,7 +798,6 @@ MovieDetails.propTypes = {
}; };
MovieDetails.defaultProps = { MovieDetails.defaultProps = {
genres: [],
tags: [], tags: [],
isSaving: false, isSaving: false,
sizeOnDisk: 0 sizeOnDisk: 0
@@ -67,7 +67,8 @@ class MovieHistoryRow extends Component {
data, data,
isMarkingAsFailed, isMarkingAsFailed,
shortDateFormat, shortDateFormat,
timeFormat timeFormat,
onMarkAsFailedPress
} = this.props; } = this.props;
const { const {
@@ -142,7 +143,7 @@ class MovieHistoryRow extends Component {
isMarkingAsFailed={isMarkingAsFailed} isMarkingAsFailed={isMarkingAsFailed}
shortDateFormat={shortDateFormat} shortDateFormat={shortDateFormat}
timeFormat={timeFormat} timeFormat={timeFormat}
onMarkAsFailedPress={this.onMarkAsFailedPress} onMarkAsFailedPress={onMarkAsFailedPress}
onModalClose={this.onDetailsModalClose} onModalClose={this.onDetailsModalClose}
/> />
</TableRow> </TableRow>
@@ -101,15 +101,6 @@ function MovieIndexSortMenu(props) {
{translate('DigitalRelease')} {translate('DigitalRelease')}
</SortMenuItem> </SortMenuItem>
<SortMenuItem
name="ratings"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('Ratings')}
</SortMenuItem>
<SortMenuItem <SortMenuItem
name="path" name="path"
sortKey={sortKey} sortKey={sortKey}
@@ -332,7 +332,7 @@ class MovieIndexRow extends Component {
className={styles[name]} className={styles[name]}
> >
<HeartRating <HeartRating
ratings={ratings} rating={ratings.value}
/> />
</VirtualTableRowCell> </VirtualTableRowCell>
); );
+1 -1
View File
@@ -6,7 +6,7 @@ export function getMovieStatusDetails(status) {
let statusDetails = { let statusDetails = {
icon: icons.ANNOUNCED, icon: icons.ANNOUNCED,
title: translate('Announced'), title: translate('Announced'),
message: translate('AnnouncedMsg') message: translate('AnnoucedMsg')
}; };
if (status === 'deleted') { if (status === 'deleted') {
@@ -33,9 +33,3 @@
width: 100px; width: 100px;
} }
.releaseGroup {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 120px;
}
@@ -74,7 +74,6 @@ class MovieFileEditorRow extends Component {
mediaInfo, mediaInfo,
relativePath, relativePath,
size, size,
releaseGroup,
quality, quality,
qualityCutoffNotMet, qualityCutoffNotMet,
customFormats, customFormats,
@@ -156,12 +155,6 @@ class MovieFileEditorRow extends Component {
} }
</TableRowCell> </TableRowCell>
<TableRowCell
className={styles.releaseGroup}
>
{releaseGroup}
</TableRowCell>
<TableRowCell <TableRowCell
className={styles.formats} className={styles.formats}
> >
@@ -223,7 +216,6 @@ MovieFileEditorRow.propTypes = {
size: PropTypes.number.isRequired, size: PropTypes.number.isRequired,
relativePath: PropTypes.string.isRequired, relativePath: PropTypes.string.isRequired,
quality: PropTypes.object.isRequired, quality: PropTypes.object.isRequired,
releaseGroup: PropTypes.string,
customFormats: PropTypes.arrayOf(PropTypes.object).isRequired, customFormats: PropTypes.arrayOf(PropTypes.object).isRequired,
qualityCutoffNotMet: PropTypes.bool.isRequired, qualityCutoffNotMet: PropTypes.bool.isRequired,
languages: PropTypes.arrayOf(PropTypes.object).isRequired, languages: PropTypes.arrayOf(PropTypes.object).isRequired,
@@ -39,11 +39,6 @@ const columns = [
label: translate('Quality'), label: translate('Quality'),
isVisible: true isVisible: true
}, },
{
name: 'releaseGroup',
label: translate('ReleaseGroup'),
isVisible: true
},
{ {
name: 'quality.customFormats', name: 'quality.customFormats',
label: translate('Formats'), label: translate('Formats'),
@@ -4,11 +4,6 @@
margin-right: auto; margin-right: auto;
} }
.rightButtons {
justify-content: flex-end;
margin-right: auto;
}
.addSpecification { .addSpecification {
composes: customFormat from '~./CustomFormat.css'; composes: customFormat from '~./CustomFormat.css';
@@ -198,25 +198,26 @@ class EditCustomFormatModalContent extends Component {
</div> </div>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<div className={styles.rightButtons}> {
{ id &&
id && <Button
<Button className={styles.deleteButton}
className={styles.deleteButton} kind={kinds.DANGER}
kind={kinds.DANGER} onPress={onDeleteCustomFormatPress}
onPress={onDeleteCustomFormatPress} >
> {translate('Delete')}
{translate('Delete')} </Button>
</Button> }
}
<Button {
className={styles.deleteButton} !id &&
onPress={this.onImportPress} <Button
> className={styles.deleteButton}
{translate('Import')} onPress={this.onImportPress}
</Button> >
</div> {translate('Import')}
</Button>
}
<Button <Button
onPress={onModalClose} onPress={onModalClose}
@@ -1,7 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Alert from 'Components/Alert'; import Alert from 'Components/Alert';
import FieldSet from 'Components/FieldSet';
import Form from 'Components/Form/Form'; import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup'; import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup'; import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -14,7 +13,7 @@ import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent'; import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter'; import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props'; import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import styles from './EditDownloadClientModalContent.css'; import styles from './EditDownloadClientModalContent.css';
@@ -46,10 +45,7 @@ class EditDownloadClientModalContent extends Component {
implementationName, implementationName,
name, name,
enable, enable,
protocol,
priority, priority,
removeCompletedDownloads,
removeFailedDownloads,
fields, fields,
message message
} = item; } = item;
@@ -140,38 +136,6 @@ class EditDownloadClientModalContent extends Component {
/> />
</FormGroup> </FormGroup>
<FieldSet
size={sizes.SMALL}
legend={translate('CompletedDownloadHandling')}
>
<FormGroup>
<FormLabel>{translate('RemoveCompleted')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="removeCompletedDownloads"
helpText={translate('RemoveCompletedDownloadsHelpText')}
{...removeCompletedDownloads}
onChange={onInputChange}
/>
</FormGroup>
{
protocol.value !== 'torrent' &&
<FormGroup>
<FormLabel>{translate('RemoveFailed')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="removeFailedDownloads"
helpText={translate('RemoveFailedDownloadsHelpText')}
{...removeFailedDownloads}
onChange={onInputChange}
/>
</FormGroup>
}
</FieldSet>
</Form> </Form>
} }
</ModalBody> </ModalBody>
@@ -1,13 +1,12 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import Alert from 'Components/Alert';
import FieldSet from 'Components/FieldSet'; import FieldSet from 'Components/FieldSet';
import Form from 'Components/Form/Form'; import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup'; import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup'; import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel'; import FormLabel from 'Components/Form/FormLabel';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { inputTypes, kinds, sizes } from 'Helpers/Props'; import { inputTypes, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
function DownloadClientOptions(props) { function DownloadClientOptions(props) {
@@ -35,15 +34,11 @@ function DownloadClientOptions(props) {
} }
{ {
hasSettings && !isFetching && !error && advancedSettings && hasSettings && !isFetching && !error &&
<div> <div>
<FieldSet legend={translate('CompletedDownloadHandling')}> <FieldSet legend={translate('CompletedDownloadHandling')}>
<Form> <Form>
<FormGroup <FormGroup size={sizes.MEDIUM}>
advancedSettings={advancedSettings}
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>{translate('Enable')}</FormLabel> <FormLabel>{translate('Enable')}</FormLabel>
<FormInputGroup <FormInputGroup
@@ -55,6 +50,22 @@ function DownloadClientOptions(props) {
/> />
</FormGroup> </FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>{translate('Remove')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="removeCompletedDownloads"
helpText={translate('RemoveCompletedDownloadsHelpText')}
onChange={onInputChange}
{...settings.removeCompletedDownloads}
/>
</FormGroup>
<FormGroup <FormGroup
advancedSettings={advancedSettings} advancedSettings={advancedSettings}
isAdvanced={true} isAdvanced={true}
@@ -91,10 +102,23 @@ function DownloadClientOptions(props) {
{...settings.autoRedownloadFailed} {...settings.autoRedownloadFailed}
/> />
</FormGroup> </FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>{translate('Remove')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="removeFailedDownloads"
helpText={translate('RemoveFailedDownloadsHelpText')}
onChange={onInputChange}
{...settings.removeFailedDownloads}
/>
</FormGroup>
</Form> </Form>
<Alert kind={kinds.INFO}>
{translate('RemoveDownloadsAlert')}
</Alert>
</FieldSet> </FieldSet>
</div> </div>
} }
@@ -51,15 +51,9 @@ class RemotePathMappings extends Component {
{...otherProps} {...otherProps}
> >
<div className={styles.remotePathMappingsHeader}> <div className={styles.remotePathMappingsHeader}>
<div className={styles.host}> <div className={styles.host}>Host</div>
{translate('Host')} <div className={styles.path}>Remote Path</div>
</div> <div className={styles.path}>Local Path</div>
<div className={styles.path}>
{translate('RemotePath')}
</div>
<div className={styles.path}>
{translate('LocalPath')}
</div>
</div> </div>
<div> <div>
@@ -2,16 +2,16 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import Modal from 'Components/Modal/Modal'; import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props'; import { sizes } from 'Helpers/Props';
import EditImportListExclusionModalContentConnector from './EditImportListExclusionModalContentConnector'; import EditImportExclusionModalContentConnector from './EditImportExclusionModalContentConnector';
function EditImportListExclusionModal({ isOpen, onModalClose, ...otherProps }) { function EditImportExclusionModal({ isOpen, onModalClose, ...otherProps }) {
return ( return (
<Modal <Modal
size={sizes.MEDIUM} size={sizes.MEDIUM}
isOpen={isOpen} isOpen={isOpen}
onModalClose={onModalClose} onModalClose={onModalClose}
> >
<EditImportListExclusionModalContentConnector <EditImportExclusionModalContentConnector
{...otherProps} {...otherProps}
onModalClose={onModalClose} onModalClose={onModalClose}
/> />
@@ -19,9 +19,9 @@ function EditImportListExclusionModal({ isOpen, onModalClose, ...otherProps }) {
); );
} }
EditImportListExclusionModal.propTypes = { EditImportExclusionModal.propTypes = {
isOpen: PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };
export default EditImportListExclusionModal; export default EditImportExclusionModal;
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { clearPendingChanges } from 'Store/Actions/baseActions'; import { clearPendingChanges } from 'Store/Actions/baseActions';
import EditImportListExclusionModal from './EditImportListExclusionModal'; import EditImportExclusionModal from './EditImportExclusionModal';
function mapStateToProps() { function mapStateToProps() {
return {}; return {};
@@ -12,7 +12,7 @@ const mapDispatchToProps = {
clearPendingChanges clearPendingChanges
}; };
class EditImportListExclusionModalConnector extends Component { class EditImportExclusionModalConnector extends Component {
// //
// Listeners // Listeners
@@ -27,7 +27,7 @@ class EditImportListExclusionModalConnector extends Component {
render() { render() {
return ( return (
<EditImportListExclusionModal <EditImportExclusionModal
{...this.props} {...this.props}
onModalClose={this.onModalClose} onModalClose={this.onModalClose}
/> />
@@ -35,9 +35,9 @@ class EditImportListExclusionModalConnector extends Component {
} }
} }
EditImportListExclusionModalConnector.propTypes = { EditImportExclusionModalConnector.propTypes = {
onModalClose: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired,
clearPendingChanges: PropTypes.func.isRequired clearPendingChanges: PropTypes.func.isRequired
}; };
export default connect(mapStateToProps, mapDispatchToProps)(EditImportListExclusionModalConnector); export default connect(mapStateToProps, mapDispatchToProps)(EditImportExclusionModalConnector);
@@ -13,9 +13,9 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props'; import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import styles from './EditImportListExclusionModalContent.css'; import styles from './EditImportExclusionModalContent.css';
function EditImportListExclusionModalContent(props) { function EditImportExclusionModalContent(props) {
const { const {
id, id,
isFetching, isFetching,
@@ -130,7 +130,7 @@ function EditImportListExclusionModalContent(props) {
); );
} }
EditImportListExclusionModalContent.propTypes = { EditImportExclusionModalContent.propTypes = {
id: PropTypes.number, id: PropTypes.number,
isFetching: PropTypes.bool.isRequired, isFetching: PropTypes.bool.isRequired,
error: PropTypes.object, error: PropTypes.object,
@@ -143,4 +143,4 @@ EditImportListExclusionModalContent.propTypes = {
onDeleteImportExclusionPress: PropTypes.func onDeleteImportExclusionPress: PropTypes.func
}; };
export default EditImportListExclusionModalContent; export default EditImportExclusionModalContent;
@@ -5,7 +5,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { saveImportExclusion, setImportExclusionValue } from 'Store/Actions/settingsActions'; import { saveImportExclusion, setImportExclusionValue } from 'Store/Actions/settingsActions';
import selectSettings from 'Store/Selectors/selectSettings'; import selectSettings from 'Store/Selectors/selectSettings';
import EditImportListExclusionModalContent from './EditImportListExclusionModalContent'; import EditImportExclusionModalContent from './EditImportExclusionModalContent';
const newImportExclusion = { const newImportExclusion = {
movieTitle: '', movieTitle: '',
@@ -97,7 +97,7 @@ class EditImportExclusionModalContentConnector extends Component {
render() { render() {
return ( return (
<EditImportListExclusionModalContent <EditImportExclusionModalContent
{...this.props} {...this.props}
onSavePress={this.onSavePress} onSavePress={this.onSavePress}
onInputChange={this.onInputChange} onInputChange={this.onInputChange}
@@ -8,14 +8,12 @@
} }
.movieTitle { .movieTitle {
@add-mixin truncate; flex: 0 0 400px;
flex: 0 0 600px;
} }
.tmdbId, .tmdbId,
.movieYear { .movieYear {
flex: 0 0 70px; flex: 0 0 200px;
} }
.actions { .actions {
@@ -6,10 +6,10 @@ import Link from 'Components/Link/Link';
import ConfirmModal from 'Components/Modal/ConfirmModal'; import ConfirmModal from 'Components/Modal/ConfirmModal';
import { icons, kinds } from 'Helpers/Props'; import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import EditImportListExclusionModalConnector from './EditImportListExclusionModalConnector'; import EditImportExclusionModalConnector from './EditImportExclusionModalConnector';
import styles from './ImportListExclusion.css'; import styles from './ImportExclusion.css';
class ImportListExclusion extends Component { class ImportExclusion extends Component {
// //
// Lifecycle // Lifecycle
@@ -78,7 +78,7 @@ class ImportListExclusion extends Component {
</Link> </Link>
</div> </div>
<EditImportListExclusionModalConnector <EditImportExclusionModalConnector
id={id} id={id}
isOpen={this.state.isEditImportExclusionModalOpen} isOpen={this.state.isEditImportExclusionModalOpen}
onModalClose={this.onEditImportExclusionModalClose} onModalClose={this.onEditImportExclusionModalClose}
@@ -99,7 +99,7 @@ class ImportListExclusion extends Component {
} }
} }
ImportListExclusion.propTypes = { ImportExclusion.propTypes = {
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
movieTitle: PropTypes.string.isRequired, movieTitle: PropTypes.string.isRequired,
tmdbId: PropTypes.number.isRequired, tmdbId: PropTypes.number.isRequired,
@@ -107,9 +107,9 @@ ImportListExclusion.propTypes = {
onConfirmDeleteImportExclusion: PropTypes.func.isRequired onConfirmDeleteImportExclusion: PropTypes.func.isRequired
}; };
ImportListExclusion.defaultProps = { ImportExclusion.defaultProps = {
// The drag preview will not connect the drag handle. // The drag preview will not connect the drag handle.
connectDragSource: (node) => node connectDragSource: (node) => node
}; };
export default ImportListExclusion; export default ImportExclusion;
@@ -1,16 +1,16 @@
.importListExclusionsHeader { .importExclusionsHeader {
display: flex; display: flex;
margin-bottom: 10px; margin-bottom: 10px;
font-weight: bold; font-weight: bold;
} }
.title { .title {
flex: 0 0 600px; flex: 0 0 400px;
} }
.tmdbId, .tmdbId,
.movieYear { .movieYear {
flex: 0 0 70px; flex: 0 0 200px;
} }
.addImportExclusion { .addImportExclusion {
@@ -6,11 +6,11 @@ import Link from 'Components/Link/Link';
import PageSectionContent from 'Components/Page/PageSectionContent'; import PageSectionContent from 'Components/Page/PageSectionContent';
import { icons } from 'Helpers/Props'; import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import EditImportListExclusionModalConnector from './EditImportListExclusionModalConnector'; import EditImportExclusionModalConnector from './EditImportExclusionModalConnector';
import ImportListExclusion from './ImportListExclusion'; import ImportExclusion from './ImportExclusion';
import styles from './ImportListExclusions.css'; import styles from './ImportExclusions.css';
class ImportListExclusions extends Component { class ImportExclusions extends Component {
// //
// Lifecycle // Lifecycle
@@ -50,23 +50,17 @@ class ImportListExclusions extends Component {
errorMessage={translate('UnableToLoadListExclusions')} errorMessage={translate('UnableToLoadListExclusions')}
{...otherProps} {...otherProps}
> >
<div className={styles.importListExclusionsHeader}> <div className={styles.importExclusionsHeader}>
<div className={styles.tmdbId}> <div className={styles.tmdbId}>TMDB Id</div>
TMDb Id <div className={styles.title}>Title</div>
</div> <div className={styles.movieYear}>Year</div>
<div className={styles.title}>
{translate('Title')}
</div>
<div className={styles.movieYear}>
{translate('Year')}
</div>
</div> </div>
<div> <div>
{ {
items.map((item, index) => { items.map((item, index) => {
return ( return (
<ImportListExclusion <ImportExclusion
key={item.id} key={item.id}
{...item} {...item}
{...otherProps} {...otherProps}
@@ -87,7 +81,7 @@ class ImportListExclusions extends Component {
</Link> </Link>
</div> </div>
<EditImportListExclusionModalConnector <EditImportExclusionModalConnector
isOpen={this.state.isAddImportExclusionModalOpen} isOpen={this.state.isAddImportExclusionModalOpen}
onModalClose={this.onModalClose} onModalClose={this.onModalClose}
/> />
@@ -98,11 +92,11 @@ class ImportListExclusions extends Component {
} }
} }
ImportListExclusions.propTypes = { ImportExclusions.propTypes = {
isFetching: PropTypes.bool.isRequired, isFetching: PropTypes.bool.isRequired,
error: PropTypes.object, error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteImportExclusion: PropTypes.func.isRequired onConfirmDeleteImportExclusion: PropTypes.func.isRequired
}; };
export default ImportListExclusions; export default ImportExclusions;
@@ -3,7 +3,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { deleteImportExclusion, fetchImportExclusions } from 'Store/Actions/settingsActions'; import { deleteImportExclusion, fetchImportExclusions } from 'Store/Actions/settingsActions';
import ImportListExclusions from './ImportListExclusions'; import ImportExclusions from './ImportExclusions';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
@@ -42,7 +42,7 @@ class ImportExclusionsConnector extends Component {
render() { render() {
return ( return (
<ImportListExclusions <ImportExclusions
{...this.state} {...this.state}
{...this.props} {...this.props}
onConfirmDeleteImportExclusion={this.onConfirmDeleteImportExclusion} onConfirmDeleteImportExclusion={this.onConfirmDeleteImportExclusion}
@@ -7,7 +7,7 @@ import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import { icons } from 'Helpers/Props'; import { icons } from 'Helpers/Props';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import ImportListExclusionsConnector from './ImportListExclusions/ImportListExclusionsConnector'; import ImportExclusionsConnector from './ImportExclusions/ImportExclusionsConnector';
import ImportListsConnector from './ImportLists/ImportListsConnector'; import ImportListsConnector from './ImportLists/ImportListsConnector';
import ImportListOptionsConnector from './Options/ImportListOptionsConnector'; import ImportListOptionsConnector from './Options/ImportListOptionsConnector';
@@ -86,7 +86,7 @@ class ImportListSettings extends Component {
onChildStateChange={this.onChildStateChange} onChildStateChange={this.onChildStateChange}
/> />
<ImportListExclusionsConnector /> <ImportExclusionsConnector />
</PageContentBody> </PageContentBody>
</PageContent> </PageContent>
@@ -45,9 +45,7 @@ function EditIndexerModalContent(props) {
supportsSearch, supportsSearch,
tags, tags,
fields, fields,
priority, priority
protocol,
downloadClientId
} = item; } = item;
return ( return (
@@ -156,25 +154,8 @@ function EditIndexerModalContent(props) {
/> />
</FormGroup> </FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('DownloadClient')}</FormLabel>
<FormInputGroup
type={inputTypes.DOWNLOAD_CLIENT_SELECT}
name="downloadClientId"
helpText={translate('IndexerDownloadClientHelpText')}
{...downloadClientId}
includeAny={true}
protocol={protocol.value}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup> <FormGroup>
<FormLabel>{translate('Tags')}</FormLabel> <FormLabel>Tags</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.TAG} type={inputTypes.TAG}
@@ -85,7 +85,7 @@ function IndexerOptions(props) {
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="preferIndexerFlags" name="preferIndexerFlags"
helpText={translate('PreferIndexerFlagsHelpText')} helpText={translate('PreferIndexerFlagsHelpText')}
helpLink="https://wiki.servarr.com/radarr/settings#indexer-flags" helpLink="https://wiki.servarr.com/Definitions#Indexer_Flags"
onChange={onInputChange} onChange={onInputChange}
{...settings.preferIndexerFlags} {...settings.preferIndexerFlags}
/> />
@@ -147,8 +147,7 @@ class NamingModal extends Component {
{ token: '{MediaInfo VideoCodec}', example: 'x264' }, { token: '{MediaInfo VideoCodec}', example: 'x264' },
{ token: '{MediaInfo VideoBitDepth}', example: '10' }, { token: '{MediaInfo VideoBitDepth}', example: '10' },
{ token: '{MediaInfo VideoDynamicRange}', example: 'HDR' }, { token: '{MediaInfo VideoDynamicRange}', example: 'HDR' }
{ token: '{MediaInfo VideoDynamicRangeType}', example: 'DV HDR10' }
]; ];
const releaseGroupTokens = [ const releaseGroupTokens = [
@@ -63,7 +63,6 @@ class Notification extends Component {
onMovieFileDelete, onMovieFileDelete,
onMovieFileDeleteForUpgrade, onMovieFileDeleteForUpgrade,
onHealthIssue, onHealthIssue,
onApplicationUpdate,
supportsOnGrab, supportsOnGrab,
supportsOnDownload, supportsOnDownload,
supportsOnUpgrade, supportsOnUpgrade,
@@ -71,8 +70,7 @@ class Notification extends Component {
supportsOnMovieDelete, supportsOnMovieDelete,
supportsOnMovieFileDelete, supportsOnMovieFileDelete,
supportsOnMovieFileDeleteForUpgrade, supportsOnMovieFileDeleteForUpgrade,
supportsOnHealthIssue, supportsOnHealthIssue
supportsOnApplicationUpdate
} = this.props; } = this.props;
return ( return (
@@ -125,14 +123,6 @@ class Notification extends Component {
null null
} }
{
supportsOnApplicationUpdate && onApplicationUpdate ?
<Label kind={kinds.SUCCESS}>
{translate('OnApplicationUpdate')}
</Label> :
null
}
{ {
supportsOnMovieDelete && onMovieDelete ? supportsOnMovieDelete && onMovieDelete ?
<Label kind={kinds.SUCCESS}> <Label kind={kinds.SUCCESS}>
@@ -158,7 +148,7 @@ class Notification extends Component {
} }
{ {
!onGrab && !onDownload && !onRename && !onHealthIssue && !onApplicationUpdate && !onMovieDelete && !onMovieFileDelete ? !onGrab && !onDownload && !onRename && !onHealthIssue && !onMovieDelete && !onMovieFileDelete ?
<Label <Label
kind={kinds.DISABLED} kind={kinds.DISABLED}
outline={true} outline={true}
@@ -200,7 +190,6 @@ Notification.propTypes = {
onMovieFileDelete: PropTypes.bool.isRequired, onMovieFileDelete: PropTypes.bool.isRequired,
onMovieFileDeleteForUpgrade: PropTypes.bool.isRequired, onMovieFileDeleteForUpgrade: PropTypes.bool.isRequired,
onHealthIssue: PropTypes.bool.isRequired, onHealthIssue: PropTypes.bool.isRequired,
onApplicationUpdate: PropTypes.bool.isRequired,
supportsOnGrab: PropTypes.bool.isRequired, supportsOnGrab: PropTypes.bool.isRequired,
supportsOnDownload: PropTypes.bool.isRequired, supportsOnDownload: PropTypes.bool.isRequired,
supportsOnMovieDelete: PropTypes.bool.isRequired, supportsOnMovieDelete: PropTypes.bool.isRequired,
@@ -209,7 +198,6 @@ Notification.propTypes = {
supportsOnUpgrade: PropTypes.bool.isRequired, supportsOnUpgrade: PropTypes.bool.isRequired,
supportsOnRename: PropTypes.bool.isRequired, supportsOnRename: PropTypes.bool.isRequired,
supportsOnHealthIssue: PropTypes.bool.isRequired, supportsOnHealthIssue: PropTypes.bool.isRequired,
supportsOnApplicationUpdate: PropTypes.bool.isRequired,
onConfirmDeleteNotification: PropTypes.func.isRequired onConfirmDeleteNotification: PropTypes.func.isRequired
}; };
@@ -23,7 +23,6 @@ function NotificationEventItems(props) {
onMovieFileDelete, onMovieFileDelete,
onMovieFileDeleteForUpgrade, onMovieFileDeleteForUpgrade,
onHealthIssue, onHealthIssue,
onApplicationUpdate,
supportsOnGrab, supportsOnGrab,
supportsOnDownload, supportsOnDownload,
supportsOnUpgrade, supportsOnUpgrade,
@@ -31,7 +30,6 @@ function NotificationEventItems(props) {
supportsOnMovieDelete, supportsOnMovieDelete,
supportsOnMovieFileDelete, supportsOnMovieFileDelete,
supportsOnMovieFileDeleteForUpgrade, supportsOnMovieFileDeleteForUpgrade,
supportsOnApplicationUpdate,
supportsOnHealthIssue, supportsOnHealthIssue,
includeHealthWarnings includeHealthWarnings
} = item; } = item;
@@ -152,17 +150,6 @@ function NotificationEventItems(props) {
/> />
</div> </div>
} }
<div>
<FormInputGroup
type={inputTypes.CHECK}
name="onApplicationUpdate"
helpText={translate('OnApplicationUpdateHelpText')}
isDisabled={!supportsOnApplicationUpdate.value}
{...onApplicationUpdate}
onChange={onInputChange}
/>
</div>
</div> </div>
</div> </div>
</FormGroup> </FormGroup>
@@ -82,18 +82,10 @@ class DelayProfiles extends Component {
> >
<div> <div>
<div className={styles.delayProfilesHeader}> <div className={styles.delayProfilesHeader}>
<div className={styles.column}> <div className={styles.column}>Protocol</div>
{translate('Protocol')} <div className={styles.column}>Usenet Delay</div>
</div> <div className={styles.column}>Torrent Delay</div>
<div className={styles.column}> <div className={styles.tags}>Tags</div>
{translate('UsenetDelay')}
</div>
<div className={styles.column}>
{translate('TorrentDelay')}
</div>
<div className={styles.tags}>
{translate('Tags')}
</div>
</div> </div>
<div className={styles.delayProfiles}> <div className={styles.delayProfiles}>
@@ -25,15 +25,9 @@ class QualityDefinitions extends Component {
{...otherProps} {...otherProps}
> >
<div className={styles.header}> <div className={styles.header}>
<div className={styles.quality}> <div className={styles.quality}>Quality</div>
{translate('Quality')} <div className={styles.title}>Title</div>
</div> <div className={styles.sizeLimit}>Size Limit</div>
<div className={styles.title}>
{translate('Title')}
</div>
<div className={styles.sizeLimit}>
{translate('SizeLimit')}
</div>
{ {
advancedSettings ? advancedSettings ?
@@ -152,7 +152,7 @@ function TagDetailsModalContent(props) {
{ {
indexers.length ? indexers.length ?
<FieldSet legend={translate('Indexers')}> <FieldSet legend="Indexers">
{ {
indexers.map((item) => { indexers.map((item) => {
return ( return (
@@ -109,7 +109,6 @@ export default {
selectedSchema.onMovieDelete = selectedSchema.supportsOnMovieDelete; selectedSchema.onMovieDelete = selectedSchema.supportsOnMovieDelete;
selectedSchema.onMovieFileDelete = selectedSchema.supportsOnMovieFileDelete; selectedSchema.onMovieFileDelete = selectedSchema.supportsOnMovieFileDelete;
selectedSchema.onMovieFileDeleteForUpgrade = selectedSchema.supportsOnMovieFileDeleteForUpgrade; selectedSchema.onMovieFileDeleteForUpgrade = selectedSchema.supportsOnMovieFileDeleteForUpgrade;
selectedSchema.onApplicationUpdate = selectedSchema.supportsOnApplicationUpdate;
return selectedSchema; return selectedSchema;
}); });
@@ -200,7 +200,7 @@ export const defaultState = {
ratings: function(item) { ratings: function(item) {
const { ratings = {} } = item; const { ratings = {} } = item;
return ratings.tmdb? ratings.tmdb.value : 0; return ratings.value;
} }
}, },
@@ -330,23 +330,8 @@ export const defaultState = {
valueType: filterBuilderValueTypes.MINIMUM_AVAILABILITY valueType: filterBuilderValueTypes.MINIMUM_AVAILABILITY
}, },
{ {
name: 'tmdbRating', name: 'ratings',
label: translate('TmdbRating'), label: 'Rating',
type: filterBuilderTypes.NUMBER
},
{
name: 'tmdbVotes',
label: translate('TmdbVotes'),
type: filterBuilderTypes.NUMBER
},
{
name: 'imdbRating',
label: translate('ImdbRating'),
type: filterBuilderTypes.NUMBER
},
{
name: 'imdbVotes',
label: translate('ImdbVotes'),
type: filterBuilderTypes.NUMBER type: filterBuilderTypes.NUMBER
}, },
{ {
@@ -615,7 +600,6 @@ export const actionHandlers = handleThunks({
const promise = createAjaxRequest({ const promise = createAjaxRequest({
url: '/exclusions/bulk', url: '/exclusions/bulk',
method: 'POST', method: 'POST',
contentType: 'application/json',
data: JSON.stringify(exclusions) data: JSON.stringify(exclusions)
}).request; }).request;
@@ -148,10 +148,9 @@ export const actionHandlers = handleThunks({
return { return {
id, id,
path: item.path, path: item.path,
movieId: item.movie ? item.movie.id : undefined, movieId: item.movie.id,
quality: item.quality, quality: item.quality,
languages: item.languages, languages: item.languages,
releaseGroup: item.releaseGroup,
downloadId: item.downloadId downloadId: item.downloadId
}; };
}); });
+2 -30
View File
@@ -131,38 +131,10 @@ export const filterPredicates = {
return dateFilterPredicate(item.digitalRelease, filterValue, type); return dateFilterPredicate(item.digitalRelease, filterValue, type);
}, },
tmdbRating: function(item, filterValue, type) { ratings: function(item, filterValue, type) {
const predicate = filterTypePredicates[type]; const predicate = filterTypePredicates[type];
const rating = item.ratings.tmdb ? item.ratings.tmdb.value : 0; return predicate(item.ratings.value * 10, filterValue);
return predicate(rating * 10, filterValue);
},
tmdbVotes: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
const rating = item.ratings.tmdb ? item.ratings.tmdb.votes : 0;
return predicate(rating, filterValue);
},
imdbRating: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
console.log(item.ratings);
const rating = item.ratings.imdb ? item.ratings.imdb.value : 0;
return predicate(rating, filterValue);
},
imdbVotes: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
const rating = item.ratings.imdb ? item.ratings.imdb.votes : 0;
return predicate(rating, filterValue);
}, },
qualityCutoffNotMet: function(item) { qualityCutoffNotMet: function(item) {
@@ -209,7 +209,7 @@ export const defaultState = {
ratings: function(item) { ratings: function(item) {
const { ratings = {} } = item; const { ratings = {} } = item;
return ratings.tmdb? ratings.tmdb.value : 0; return ratings.value;
} }
}, },
@@ -357,23 +357,8 @@ export const defaultState = {
} }
}, },
{ {
name: 'tmdbRating', name: 'ratings',
label: translate('TmdbRating'), label: translate('Ratings'),
type: filterBuilderTypes.NUMBER
},
{
name: 'tmdbVotes',
label: translate('TmdbVotes'),
type: filterBuilderTypes.NUMBER
},
{
name: 'imdbRating',
label: translate('ImdbRating'),
type: filterBuilderTypes.NUMBER
},
{
name: 'imdbVotes',
label: translate('ImdbVotes'),
type: filterBuilderTypes.NUMBER type: filterBuilderTypes.NUMBER
}, },
{ {
+1 -1
View File
@@ -27,7 +27,7 @@ const paged = `${section}.paged`;
export const defaultState = { export const defaultState = {
options: { options: {
includeUnknownMovieItems: true includeUnknownMovieItems: false
}, },
status: { status: {
@@ -100,7 +100,7 @@ export default function createSentryMiddleware() {
return; return;
} }
const dsn = isProduction ? 'https://7794f2858478485ea337fb5535624fbd@sentry.servarr.com/12' : const dsn = isProduction ? 'https://b0fb75c38ef4487dbf742f79c4ba62d2@sentry.servarr.com/12' :
'https://da610619280249f891ec3ee306906793@sentry.servarr.com/13'; 'https://da610619280249f891ec3ee306906793@sentry.servarr.com/13';
sentry.init({ sentry.init({
@@ -13,7 +13,7 @@ class Donations extends Component {
return ( return (
<FieldSet legend={translate('Donations')}> <FieldSet legend={translate('Donations')}>
<div className={styles.logoContainer} title="Radarr"> <div className={styles.logoContainer} title="Radarr">
<Link to="https://radarr.video/donate"> <Link to="https://opencollective.com/radarr">
<img <img
className={styles.logo} className={styles.logo}
src={`${window.Radarr.urlBase}/Content/Images/Icons/logo-radarr.png`} src={`${window.Radarr.urlBase}/Content/Images/Icons/logo-radarr.png`}
@@ -21,7 +21,7 @@ class Donations extends Component {
</Link> </Link>
</div> </div>
<div className={styles.logoContainer} title="Lidarr"> <div className={styles.logoContainer} title="Lidarr">
<Link to="https://lidarr.audio/donate"> <Link to="https://opencollective.com/lidarr">
<img <img
className={styles.logo} className={styles.logo}
src={`${window.Radarr.urlBase}/Content/Images/Icons/logo-lidarr.png`} src={`${window.Radarr.urlBase}/Content/Images/Icons/logo-lidarr.png`}
@@ -29,7 +29,7 @@ class Donations extends Component {
</Link> </Link>
</div> </div>
<div className={styles.logoContainer} title="Readarr"> <div className={styles.logoContainer} title="Readarr">
<Link to="https://readarr.com/donate"> <Link to="https://opencollective.com/readarr">
<img <img
className={styles.logo} className={styles.logo}
src={`${window.Radarr.urlBase}/Content/Images/Icons/logo-readarr.png`} src={`${window.Radarr.urlBase}/Content/Images/Icons/logo-readarr.png`}
@@ -37,7 +37,7 @@ class Donations extends Component {
</Link> </Link>
</div> </div>
<div className={styles.logoContainer} title="Prowlarr"> <div className={styles.logoContainer} title="Prowlarr">
<Link to="https://prowlarr.com/donate"> <Link to="https://opencollective.com/prowlarr">
<img <img
className={styles.logo} className={styles.logo}
src={`${window.Radarr.urlBase}/Content/Images/Icons/logo-prowlarr.png`} src={`${window.Radarr.urlBase}/Content/Images/Icons/logo-prowlarr.png`}
@@ -45,7 +45,7 @@ class Donations extends Component {
</Link> </Link>
</div> </div>
<div className={styles.logoContainer} title="Sonarr"> <div className={styles.logoContainer} title="Sonarr">
<Link to="https://sonarr.tv/donate"> <Link to="https://opencollective.com/sonarr">
<img <img
className={styles.logo} className={styles.logo}
src={`${window.Radarr.urlBase}/Content/Images/Icons/logo-sonarr.png`} src={`${window.Radarr.urlBase}/Content/Images/Icons/logo-sonarr.png`}
@@ -19,7 +19,6 @@ function getInternalLink(source) {
case 'IndexerRssCheck': case 'IndexerRssCheck':
case 'IndexerSearchCheck': case 'IndexerSearchCheck':
case 'IndexerStatusCheck': case 'IndexerStatusCheck':
case 'IndexerJackettAllCheck':
case 'IndexerLongTermStatusCheck': case 'IndexerLongTermStatusCheck':
return ( return (
<IconButton <IconButton
@@ -200,7 +200,7 @@ class QueuedTaskRow extends Component {
{ {
clientUserAgent ? clientUserAgent ?
<span className={styles.userAgent} title={translate('TaskUserAgentTooltip')}> <span className={styles.userAgent} title={translate('TaskUserAgentTooltip')}>
{translate('From')}: {clientUserAgent} {translate('from')}: {clientUserAgent}
</span> : </span> :
null null
} }
@@ -2,7 +2,7 @@ import { kinds } from 'Helpers/Props';
function getStatusStyle(status, monitored, hasFile, isAvailable, returnType, queue = false) { function getStatusStyle(status, monitored, hasFile, isAvailable, returnType, queue = false) {
if (queue) { if (queue) {
return returnType === 'kinds' ? kinds.QUEUE : 'queue'; return returnType === 'kinds' ? kinds.SUCCESS : 'queue';
} }
if (hasFile && monitored) { if (hasFile && monitored) {
+5 -5
View File
@@ -30,7 +30,7 @@
"@fortawesome/free-regular-svg-icons": "5.15.3", "@fortawesome/free-regular-svg-icons": "5.15.3",
"@fortawesome/free-solid-svg-icons": "5.15.3", "@fortawesome/free-solid-svg-icons": "5.15.3",
"@fortawesome/react-fontawesome": "0.1.14", "@fortawesome/react-fontawesome": "0.1.14",
"@microsoft/signalr": "6.0.1", "@microsoft/signalr": "5.0.10",
"@sentry/browser": "6.13.2", "@sentry/browser": "6.13.2",
"@sentry/integrations": "6.13.2", "@sentry/integrations": "6.13.2",
"classnames": "2.3.1", "classnames": "2.3.1",
@@ -82,7 +82,6 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.13.16", "@babel/core": "7.13.16",
"@babel/eslint-parser": "7.13.14",
"@babel/plugin-proposal-class-properties": "7.13.0", "@babel/plugin-proposal-class-properties": "7.13.0",
"@babel/plugin-proposal-decorators": "7.13.15", "@babel/plugin-proposal-decorators": "7.13.15",
"@babel/plugin-proposal-export-default-from": "7.12.13", "@babel/plugin-proposal-export-default-from": "7.12.13",
@@ -95,6 +94,7 @@
"@babel/plugin-syntax-dynamic-import": "7.8.3", "@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/preset-env": "7.13.15", "@babel/preset-env": "7.13.15",
"@babel/preset-react": "7.13.13", "@babel/preset-react": "7.13.13",
"@babel/eslint-parser": "7.13.14",
"autoprefixer": "10.2.5", "autoprefixer": "10.2.5",
"babel-loader": "8.2.2", "babel-loader": "8.2.2",
"babel-plugin-inline-classnames": "2.0.1", "babel-plugin-inline-classnames": "2.0.1",
@@ -125,12 +125,12 @@
"run-sequence": "2.2.1", "run-sequence": "2.2.1",
"streamqueue": "1.1.2", "streamqueue": "1.1.2",
"style-loader": "2.0.0", "style-loader": "2.0.0",
"stylelint": "13.13.0",
"stylelint-order": "4.1.0",
"url-loader": "4.1.1", "url-loader": "4.1.1",
"webpack": "5.35.1", "webpack": "5.35.1",
"webpack-cli": "4.6.0", "webpack-cli": "4.6.0",
"webpack-livereload-plugin": "3.0.1", "webpack-livereload-plugin": "3.0.1",
"worker-loader": "3.0.8" "worker-loader": "3.0.8",
"stylelint": "13.13.0",
"stylelint-order": "4.1.0"
} }
} }
+5 -6
View File
@@ -2,11 +2,10 @@
<!-- Common to all Radarr Projects --> <!-- Common to all Radarr Projects -->
<PropertyGroup> <PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch> <TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<RuntimeIdentifiers>win-x64;win-x86;osx-x64;osx-arm64;linux-x64;linux-musl-x64;linux-arm;linux-musl-arm;linux-arm64;linux-musl-arm64</RuntimeIdentifiers> <RuntimeIdentifiers>win-x64;win-x86;osx-x64;linux-x64;linux-musl-x64;linux-arm;linux-arm64;linux-musl-arm64</RuntimeIdentifiers>
<RadarrRootDir>$(MSBuildThisFileDirectory)..\</RadarrRootDir> <RadarrRootDir>$(MSBuildThisFileDirectory)..\</RadarrRootDir>
@@ -90,13 +89,13 @@
<!-- 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="16.11.0" />
<PackageReference Include="NUnit" Version="3.13.2" /> <PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" /> <PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="NunitXml.TestLogger" Version="3.0.117" /> <PackageReference Include="NunitXml.TestLogger" Version="2.1.62" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TestProject)'=='true' and '$(TargetFramework)'=='net6.0'"> <ItemGroup Condition="'$(TestProject)'=='true' and '$(TargetFramework)'=='net5.0'">
<PackageReference Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" /> <PackageReference Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" />
</ItemGroup> </ItemGroup>
+1 -1
View File
@@ -6,7 +6,7 @@
<add key="dotnet-bsd-crossbuild" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/dotnet-bsd-crossbuild/nuget/v3/index.json" /> <add key="dotnet-bsd-crossbuild" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/dotnet-bsd-crossbuild/nuget/v3/index.json" />
<add key="Mono.Posix.NETStandard" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/Mono.Posix.NETStandard/nuget/v3/index.json" /> <add key="Mono.Posix.NETStandard" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/Mono.Posix.NETStandard/nuget/v3/index.json" />
<add key="SQLite" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/SQLite/nuget/v3/index.json" /> <add key="SQLite" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/SQLite/nuget/v3/index.json" />
<add key="FluentMigrator" value="https://pkgs.dev.azure.com/fluentmigrator/fluentmigrator/_packaging/fluentmigrator/nuget/v3/index.json" />
<add key="coverlet-nightly" value="https://pkgs.dev.azure.com/Servarr/coverlet/_packaging/coverlet-nightly/nuget/v3/index.json" /> <add key="coverlet-nightly" value="https://pkgs.dev.azure.com/Servarr/coverlet/_packaging/coverlet-nightly/nuget/v3/index.json" />
<add key="FFMpegCore" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/FFMpegCore/nuget/v3/index.json" />
</packageSources> </packageSources>
</configuration> </configuration>
+1 -1
View File
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net5.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="NBuilder" Version="6.1.0" /> <PackageReference Include="NBuilder" Version="6.1.0" />
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net5.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Selenium.Support" Version="3.141.0" /> <PackageReference Include="Selenium.Support" Version="3.141.0" />
@@ -4,7 +4,6 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http;
using System.Threading; using System.Threading;
using FluentAssertions; using FluentAssertions;
using Moq; using Moq;
@@ -16,11 +15,8 @@ using NzbDrone.Common.Http;
using NzbDrone.Common.Http.Dispatchers; using NzbDrone.Common.Http.Dispatchers;
using NzbDrone.Common.Http.Proxy; using NzbDrone.Common.Http.Proxy;
using NzbDrone.Common.TPL; using NzbDrone.Common.TPL;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Security;
using NzbDrone.Test.Common; using NzbDrone.Test.Common;
using NzbDrone.Test.Common.Categories; using NzbDrone.Test.Common.Categories;
using HttpClient = NzbDrone.Common.Http.HttpClient;
namespace NzbDrone.Common.Test.Http namespace NzbDrone.Common.Test.Http
{ {
@@ -35,8 +31,6 @@ namespace NzbDrone.Common.Test.Http
private string _httpBinHost; private string _httpBinHost;
private string _httpBinHost2; private string _httpBinHost2;
private System.Net.Http.HttpClient _httpClient = new ();
[OneTimeSetUp] [OneTimeSetUp]
public void FixtureSetUp() public void FixtureSetUp()
{ {
@@ -44,7 +38,7 @@ namespace NzbDrone.Common.Test.Http
var mainHost = "httpbin.servarr.com"; var mainHost = "httpbin.servarr.com";
// Use mirrors for tests that use two hosts // Use mirrors for tests that use two hosts
var candidates = new[] { "httpbin1.servarr.com" }; var candidates = new[] { "eu.httpbin.org", /* "httpbin.org", */ "www.httpbin.org" };
// httpbin.org is broken right now, occassionally redirecting to https if it's unavailable. // httpbin.org is broken right now, occassionally redirecting to https if it's unavailable.
_httpBinHost = mainHost; _httpBinHost = mainHost;
@@ -52,20 +46,29 @@ namespace NzbDrone.Common.Test.Http
TestLogger.Info($"{candidates.Length} TestSites available."); TestLogger.Info($"{candidates.Length} TestSites available.");
_httpBinSleep = 10; _httpBinSleep = _httpBinHosts.Length < 2 ? 100 : 10;
} }
private bool IsTestSiteAvailable(string site) private bool IsTestSiteAvailable(string site)
{ {
try try
{ {
var res = _httpClient.GetAsync($"https://{site}/get").GetAwaiter().GetResult(); var req = WebRequest.Create($"https://{site}/get") as HttpWebRequest;
var res = req.GetResponse() as HttpWebResponse;
if (res.StatusCode != HttpStatusCode.OK) if (res.StatusCode != HttpStatusCode.OK)
{ {
return false; return false;
} }
res = _httpClient.GetAsync($"https://{site}/status/429").GetAwaiter().GetResult(); try
{
req = WebRequest.Create($"https://{site}/status/429") as HttpWebRequest;
res = req.GetResponse() as HttpWebResponse;
}
catch (WebException ex)
{
res = ex.Response as HttpWebResponse;
}
if (res == null || res.StatusCode != (HttpStatusCode)429) if (res == null || res.StatusCode != (HttpStatusCode)429)
{ {
@@ -92,13 +95,10 @@ namespace NzbDrone.Common.Test.Http
Mocker.GetMock<IOsInfo>().Setup(c => c.Name).Returns("TestOS"); Mocker.GetMock<IOsInfo>().Setup(c => c.Name).Returns("TestOS");
Mocker.GetMock<IOsInfo>().Setup(c => c.Version).Returns("9.0.0"); Mocker.GetMock<IOsInfo>().Setup(c => c.Version).Returns("9.0.0");
Mocker.GetMock<IConfigService>().SetupGet(x => x.CertificateValidation).Returns(CertificateValidationType.Enabled);
Mocker.SetConstant<IUserAgentBuilder>(Mocker.Resolve<UserAgentBuilder>()); Mocker.SetConstant<IUserAgentBuilder>(Mocker.Resolve<UserAgentBuilder>());
Mocker.SetConstant<ICacheManager>(Mocker.Resolve<CacheManager>()); Mocker.SetConstant<ICacheManager>(Mocker.Resolve<CacheManager>());
Mocker.SetConstant<ICreateManagedWebProxy>(Mocker.Resolve<ManagedWebProxyFactory>()); Mocker.SetConstant<ICreateManagedWebProxy>(Mocker.Resolve<ManagedWebProxyFactory>());
Mocker.SetConstant<ICertificateValidationService>(new X509CertificateValidationService(Mocker.GetMock<IConfigService>().Object, TestLogger));
Mocker.SetConstant<IRateLimitService>(Mocker.Resolve<RateLimitService>()); Mocker.SetConstant<IRateLimitService>(Mocker.Resolve<RateLimitService>());
Mocker.SetConstant<IEnumerable<IHttpRequestInterceptor>>(Array.Empty<IHttpRequestInterceptor>()); Mocker.SetConstant<IEnumerable<IHttpRequestInterceptor>>(Array.Empty<IHttpRequestInterceptor>());
Mocker.SetConstant<IHttpDispatcher>(Mocker.Resolve<TDispatcher>()); Mocker.SetConstant<IHttpDispatcher>(Mocker.Resolve<TDispatcher>());
@@ -138,28 +138,6 @@ namespace NzbDrone.Common.Test.Http
response.Content.Should().NotBeNullOrWhiteSpace(); response.Content.Should().NotBeNullOrWhiteSpace();
} }
[TestCase(CertificateValidationType.Enabled)]
[TestCase(CertificateValidationType.DisabledForLocalAddresses)]
public void bad_ssl_should_fail_when_remote_validation_enabled(CertificateValidationType validationType)
{
Mocker.GetMock<IConfigService>().SetupGet(x => x.CertificateValidation).Returns(validationType);
var request = new HttpRequest($"https://expired.badssl.com");
Assert.Throws<HttpRequestException>(() => Subject.Execute(request));
ExceptionVerification.ExpectedErrors(1);
}
[Test]
public void bad_ssl_should_pass_if_remote_validation_disabled()
{
Mocker.GetMock<IConfigService>().SetupGet(x => x.CertificateValidation).Returns(CertificateValidationType.Disabled);
var request = new HttpRequest($"https://expired.badssl.com");
Subject.Execute(request);
ExceptionVerification.ExpectedErrors(0);
}
[Test] [Test]
public void should_execute_typed_get() public void should_execute_typed_get()
{ {
@@ -184,44 +162,15 @@ namespace NzbDrone.Common.Test.Http
response.Resource.Data.Should().Be(message); response.Resource.Data.Should().Be(message);
} }
[Test] [TestCase("gzip")]
public void should_execute_post_with_content_type() public void should_execute_get_using_gzip(string compression)
{ {
var message = "{ my: 1 }"; var request = new HttpRequest($"https://{_httpBinHost}/{compression}");
var request = new HttpRequest($"https://{_httpBinHost}/post");
request.SetContent(message);
request.Headers.ContentType = "application/json";
var response = Subject.Post<HttpBinResource>(request);
response.Resource.Data.Should().Be(message);
}
[Test]
public void should_execute_get_using_gzip()
{
var request = new HttpRequest($"https://{_httpBinHost}/gzip");
var response = Subject.Get<HttpBinResource>(request); var response = Subject.Get<HttpBinResource>(request);
response.Resource.Headers["Accept-Encoding"].ToString().Should().Contain("gzip"); response.Resource.Headers["Accept-Encoding"].ToString().Should().Be(compression);
response.Resource.Gzipped.Should().BeTrue(); response.Resource.Gzipped.Should().BeTrue();
response.Resource.Brotli.Should().BeFalse();
}
[Test]
public void should_execute_get_using_brotli()
{
var request = new HttpRequest($"https://{_httpBinHost}/brotli");
var response = Subject.Get<HttpBinResource>(request);
response.Resource.Headers["Accept-Encoding"].ToString().Should().Contain("br");
response.Resource.Gzipped.Should().BeFalse();
response.Resource.Brotli.Should().BeTrue();
} }
[TestCase(HttpStatusCode.Unauthorized)] [TestCase(HttpStatusCode.Unauthorized)]
@@ -388,38 +337,13 @@ namespace NzbDrone.Common.Test.Http
{ {
var file = GetTempFilePath(); var file = GetTempFilePath();
Assert.Throws<HttpException>(() => Subject.DownloadFile("https://download.sonarr.tv/wrongpath", file)); Assert.Throws<WebException>(() => Subject.DownloadFile("https://download.sonarr.tv/wrongpath", file));
File.Exists(file).Should().BeFalse(); File.Exists(file).Should().BeFalse();
ExceptionVerification.ExpectedWarns(1); ExceptionVerification.ExpectedWarns(1);
} }
[Test]
public void should_not_write_redirect_content_to_stream()
{
var file = GetTempFilePath();
using (var fileStream = new FileStream(file, FileMode.Create))
{
var request = new HttpRequest($"http://{_httpBinHost}/redirect/1");
request.AllowAutoRedirect = false;
request.ResponseStream = fileStream;
var response = Subject.Get(request);
response.StatusCode.Should().Be(HttpStatusCode.Moved);
}
ExceptionVerification.ExpectedErrors(1);
File.Exists(file).Should().BeTrue();
var fileInfo = new FileInfo(file);
fileInfo.Length.Should().Be(0);
}
[Test] [Test]
public void should_send_cookie() public void should_send_cookie()
{ {
@@ -819,7 +743,7 @@ namespace NzbDrone.Common.Test.Http
{ {
try try
{ {
string url = $"https://{_httpBinHost}/response-headers?Set-Cookie={Uri.EscapeDataString(malformedCookie)}"; string url = $"https://{_httpBinHost}/response-headers?Set-Cookie={Uri.EscapeUriString(malformedCookie)}";
var requestSet = new HttpRequest(url); var requestSet = new HttpRequest(url);
requestSet.AllowAutoRedirect = false; requestSet.AllowAutoRedirect = false;
@@ -849,7 +773,6 @@ namespace NzbDrone.Common.Test.Http
public string Url { get; set; } public string Url { get; set; }
public string Data { get; set; } public string Data { get; set; }
public bool Gzipped { get; set; } public bool Gzipped { get; set; }
public bool Brotli { get; set; }
} }
public class HttpCookieResource public class HttpCookieResource
@@ -62,7 +62,6 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase("Hardlinking episode file: /home/mySecret/Downloads to /media/abc.mkv")] [TestCase("Hardlinking episode file: /home/mySecret/Downloads to /media/abc.mkv")]
[TestCase("Hardlink '/home/mySecret/Downloads/abs.mkv' to '/media/abc.mkv' failed.")] [TestCase("Hardlink '/home/mySecret/Downloads/abs.mkv' to '/media/abc.mkv' failed.")]
[TestCase("https://notifiarr.com/notifier.php: api=1234530f-422f-4aac-b6b3-01233210aaaa&radarr_health_issue_message=Download")] [TestCase("https://notifiarr.com/notifier.php: api=1234530f-422f-4aac-b6b3-01233210aaaa&radarr_health_issue_message=Download")]
[TestCase("/readarr/signalr/messages/negotiate?access_token=1234530f422f4aacb6b301233210aaaa&negotiateVersion=1")]
// Announce URLs (passkeys) Magnet & Tracker // Announce URLs (passkeys) Magnet & Tracker
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")] [TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")]
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net5.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\NzbDrone.Host\Radarr.Host.csproj" /> <ProjectReference Include="..\NzbDrone.Host\Radarr.Host.csproj" />
@@ -3,8 +3,6 @@ using DryIoc;
using DryIoc.Microsoft.DependencyInjection; using DryIoc.Microsoft.DependencyInjection;
using FluentAssertions; using FluentAssertions;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Composition.Extensions; using NzbDrone.Common.Composition.Extensions;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
@@ -27,15 +25,12 @@ namespace NzbDrone.Common.Test
.AddNzbDroneLogger() .AddNzbDroneLogger()
.AutoAddServices(Bootstrap.ASSEMBLIES) .AutoAddServices(Bootstrap.ASSEMBLIES)
.AddDummyDatabase() .AddDummyDatabase()
.AddStartupContext(new StartupContext("first", "second")); .AddStartupContext(new StartupContext("first", "second"))
.GetServiceProvider();
container.RegisterInstance<IHostLifetime>(new Mock<IHostLifetime>().Object); container.GetRequiredService<IAppFolderFactory>().Register();
var serviceProvider = container.GetServiceProvider(); Mocker.SetConstant<System.IServiceProvider>(container);
serviceProvider.GetRequiredService<IAppFolderFactory>().Register();
Mocker.SetConstant<System.IServiceProvider>(serviceProvider);
var handlers = Subject.BuildAll<IHandle<ApplicationStartedEvent>>() var handlers = Subject.BuildAll<IHandle<ApplicationStartedEvent>>()
.Select(c => c.GetType().FullName); .Select(c => c.GetType().FullName);
+1 -1
View File
@@ -106,7 +106,7 @@ namespace NzbDrone.Common
Stream inStream = File.OpenRead(compressedFile); Stream inStream = File.OpenRead(compressedFile);
Stream gzipStream = new GZipInputStream(inStream); Stream gzipStream = new GZipInputStream(inStream);
TarArchive tarArchive = TarArchive.CreateInputTarArchive(gzipStream, null); TarArchive tarArchive = TarArchive.CreateInputTarArchive(gzipStream);
tarArchive.ExtractContents(destination); tarArchive.ExtractContents(destination);
tarArchive.Close(); tarArchive.Close();
@@ -75,6 +75,10 @@ namespace NzbDrone.Common.Composition
{ {
mappedName = "libsqlite3.so.0"; mappedName = "libsqlite3.so.0";
} }
else if (libraryName == "mediainfo")
{
mappedName = "libmediainfo.so.0";
}
} }
return NativeLibrary.Load(mappedName, assembly, dllImportSearchPath); return NativeLibrary.Load(mappedName, assembly, dllImportSearchPath);
@@ -1,9 +1,9 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Reflection;
using System.Security.Principal; using System.Security.Principal;
using Microsoft.Extensions.Hosting; using System.ServiceProcess;
using Microsoft.Extensions.Hosting.WindowsServices;
using NLog; using NLog;
using NzbDrone.Common.Processes; using NzbDrone.Common.Processes;
@@ -14,13 +14,16 @@ namespace NzbDrone.Common.EnvironmentInfo
private readonly Logger _logger; private readonly Logger _logger;
private readonly DateTime _startTime = DateTime.UtcNow; private readonly DateTime _startTime = DateTime.UtcNow;
public RuntimeInfo(Logger logger, IHostLifetime hostLifetime = null) public RuntimeInfo(IServiceProvider serviceProvider, Logger logger)
{ {
_logger = logger; _logger = logger;
IsWindowsService = hostLifetime is WindowsServiceLifetime; IsWindowsService = !IsUserInteractive &&
OsInfo.IsWindows &&
serviceProvider.ServiceExist(ServiceProvider.SERVICE_NAME) &&
serviceProvider.GetStatus(ServiceProvider.SERVICE_NAME) == ServiceControllerStatus.StartPending;
// net6.0 will return Radarr.dll for entry assembly, we need the actual // net5.0 will return Radarr.dll for entry assembly, we need the actual
// executable name (Radarr on linux). On mono this will return the location of // executable name (Radarr on linux). On mono this will return the location of
// the mono executable itself, which is not what we want. // the mono executable itself, which is not what we want.
var entry = Process.GetCurrentProcess().MainModule; var entry = Process.GetCurrentProcess().MainModule;
@@ -6,6 +6,13 @@ namespace NzbDrone.Common.Extensions
{ {
public static class EnumerableExtensions public static class EnumerableExtensions
{ {
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
var knownKeys = new HashSet<TKey>();
return source.Where(element => knownKeys.Add(keySelector(element)));
}
public static IEnumerable<TFirst> IntersectBy<TFirst, TSecond, TKey>(this IEnumerable<TFirst> first, public static IEnumerable<TFirst> IntersectBy<TFirst, TSecond, TKey>(this IEnumerable<TFirst> first,
Func<TFirst, TKey> firstKeySelector, Func<TFirst, TKey> firstKeySelector,
IEnumerable<TSecond> second, IEnumerable<TSecond> second,

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