mirror of
https://github.com/Readarr/Readarr.git
synced 2026-04-18 21:34:28 -04:00
Compare commits
74 Commits
v0.1.0.124
...
sonarr-pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91049f874b | ||
|
|
63ccc155d6 | ||
|
|
76613316fa | ||
|
|
f33e9f2bbc | ||
|
|
e4a3d7b273 | ||
|
|
46c2e0ba82 | ||
|
|
8616373f96 | ||
|
|
b3f99d8c20 | ||
|
|
c0c0847963 | ||
|
|
c92f9d03c0 | ||
|
|
f7bf1e243d | ||
|
|
20b1f41ac1 | ||
|
|
f703db1e00 | ||
|
|
20f67c8035 | ||
|
|
56ae497bfa | ||
|
|
e760dc56c6 | ||
|
|
7ad26b386c | ||
|
|
199085249f | ||
|
|
b84041d95f | ||
|
|
615facb3c4 | ||
|
|
b4fd1340c2 | ||
|
|
1da27b0978 | ||
|
|
b0a3ddef9c | ||
|
|
a59706ceb4 | ||
|
|
ce58e6ecdb | ||
|
|
d01ce8b908 | ||
|
|
575f995916 | ||
|
|
c17c8815ef | ||
|
|
8445941510 | ||
|
|
32ae18ae6f | ||
|
|
b91ec241a6 | ||
|
|
17ce8187fd | ||
|
|
41615fe026 | ||
|
|
9cff8f31e9 | ||
|
|
21538b972d | ||
|
|
5a7b4d41d8 | ||
|
|
16f67265c9 | ||
|
|
03d2a85821 | ||
|
|
fa68a9559f | ||
|
|
c4a2a432d2 | ||
|
|
0b3eda4de3 | ||
|
|
a1d4b3a0cf | ||
|
|
28f055e838 | ||
|
|
8d91fb81bd | ||
|
|
085913a852 | ||
|
|
8599fc4ee6 | ||
|
|
68f017cd84 | ||
|
|
f34171444e | ||
|
|
10760471e0 | ||
|
|
4ecabcd3d6 | ||
|
|
1af2e14474 | ||
|
|
fe836c56ee | ||
|
|
ae611cca74 | ||
|
|
c446f9835e | ||
|
|
2ff8bf204f | ||
|
|
5aa17e7a67 | ||
|
|
312cf2d364 | ||
|
|
a9852ded2f | ||
|
|
4286f546e9 | ||
|
|
482fe04161 | ||
|
|
cbdc2c51c4 | ||
|
|
f55fda9bac | ||
|
|
3841f7708e | ||
|
|
f2bfed0252 | ||
|
|
bc7e4ea622 | ||
|
|
38f16e6b85 | ||
|
|
af57024c63 | ||
|
|
54faa58a4d | ||
|
|
8ca35a709c | ||
|
|
72a0e38b36 | ||
|
|
b25f8449a2 | ||
|
|
cf0a15a308 | ||
|
|
a677098f0f | ||
|
|
a40cce9c71 |
@@ -19,10 +19,10 @@ indent_size = 4
|
|||||||
dotnet_sort_system_directives_first = true
|
dotnet_sort_system_directives_first = true
|
||||||
|
|
||||||
# Avoid "this." and "Me." if not necessary
|
# Avoid "this." and "Me." if not necessary
|
||||||
dotnet_style_qualification_for_field = false:refactoring
|
dotnet_style_qualification_for_field = false:warning
|
||||||
dotnet_style_qualification_for_property = false:refactoring
|
dotnet_style_qualification_for_property = false:warning
|
||||||
dotnet_style_qualification_for_method = false:refactoring
|
dotnet_style_qualification_for_method = false:warning
|
||||||
dotnet_style_qualification_for_event = false:refactoring
|
dotnet_style_qualification_for_event = false:warning
|
||||||
|
|
||||||
# Indentation preferences
|
# Indentation preferences
|
||||||
csharp_indent_block_contents = true
|
csharp_indent_block_contents = true
|
||||||
@@ -32,10 +32,6 @@ csharp_indent_case_contents_when_block = true
|
|||||||
csharp_indent_switch_labels = true
|
csharp_indent_switch_labels = true
|
||||||
csharp_indent_labels = flush_left
|
csharp_indent_labels = flush_left
|
||||||
|
|
||||||
dotnet_style_qualification_for_field = false:suggestion
|
|
||||||
dotnet_style_qualification_for_property = false:suggestion
|
|
||||||
dotnet_style_qualification_for_method = false:suggestion
|
|
||||||
dotnet_style_qualification_for_event = false:suggestion
|
|
||||||
dotnet_naming_style.instance_field_style.capitalization = camel_case
|
dotnet_naming_style.instance_field_style.capitalization = camel_case
|
||||||
dotnet_naming_style.instance_field_style.required_prefix = _
|
dotnet_naming_style.instance_field_style.required_prefix = _
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ variables:
|
|||||||
testsFolder: './_tests'
|
testsFolder: './_tests'
|
||||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||||
majorVersion: '0.1.0'
|
majorVersion: '0.1.1'
|
||||||
minorVersion: $[counter('minorVersion', 1)]
|
minorVersion: $[counter('minorVersion', 1)]
|
||||||
readarrVersion: '$(majorVersion).$(minorVersion)'
|
readarrVersion: '$(majorVersion).$(minorVersion)'
|
||||||
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
|
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
|
||||||
sentryOrg: 'servarr'
|
sentryOrg: 'servarr'
|
||||||
sentryUrl: 'https://sentry.servarr.com'
|
sentryUrl: 'https://sentry.servarr.com'
|
||||||
dotnetVersion: '6.0.102'
|
dotnetVersion: '6.0.302'
|
||||||
innoVersion: '6.2.0'
|
innoVersion: '6.2.0'
|
||||||
windowsImage: 'windows-2022'
|
windowsImage: 'windows-2022'
|
||||||
linuxImage: 'ubuntu-20.04'
|
linuxImage: 'ubuntu-20.04'
|
||||||
@@ -66,16 +66,13 @@ stages:
|
|||||||
inputs:
|
inputs:
|
||||||
version: $(dotnetVersion)
|
version: $(dotnetVersion)
|
||||||
- bash: |
|
- bash: |
|
||||||
BUNDLEDVERSIONS=${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}/Microsoft.NETCoreSdk.BundledVersions.props
|
SDK_PATH="${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}"
|
||||||
echo $BUNDLEDVERSIONS
|
BUNDLEDVERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
|
||||||
grep osx-x64 $BUNDLEDVERSIONS
|
|
||||||
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
if ! grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
||||||
echo "BSD already enabled"
|
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
|
||||||
else
|
|
||||||
echo "Enabling BSD support"
|
|
||||||
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64/' $BUNDLEDVERSIONS
|
|
||||||
fi
|
fi
|
||||||
displayName: Enable FreeBSD Support
|
displayName: Extra Platform Support
|
||||||
- task: Cache@2
|
- task: Cache@2
|
||||||
inputs:
|
inputs:
|
||||||
key: 'nuget | "$(Agent.OS)" | $(Build.SourcesDirectory)/src/Directory.Packages.props'
|
key: 'nuget | "$(Agent.OS)" | $(Build.SourcesDirectory)/src/Directory.Packages.props'
|
||||||
@@ -87,29 +84,27 @@ stages:
|
|||||||
NUGET_PACKAGES: $(nugetCacheFolder)
|
NUGET_PACKAGES: $(nugetCacheFolder)
|
||||||
- powershell: Get-ChildItem _output\net6.0*,_output\*.Update\* -Recurse | Where { $_.Fullname -notlike "*\publish\*" -and $_.attributes -notlike "*directory*" } | Remove-Item
|
- powershell: Get-ChildItem _output\net6.0*,_output\*.Update\* -Recurse | Where { $_.Fullname -notlike "*\publish\*" -and $_.attributes -notlike "*directory*" } | Remove-Item
|
||||||
displayName: Clean up intermediate output
|
displayName: Clean up intermediate output
|
||||||
- task: PublishPipelineArtifact@1
|
- publish: $(outputFolder)
|
||||||
inputs:
|
artifact: '$(osName)Backend'
|
||||||
path: $(outputFolder)
|
|
||||||
artifact: '$(osName)Backend'
|
|
||||||
artifactType: 'pipeline'
|
|
||||||
parallel: true
|
|
||||||
parallelCount: 100
|
|
||||||
displayName: Publish Backend
|
displayName: Publish Backend
|
||||||
- publish: '$(testsFolder)/net6.0/win-x64/publish'
|
- publish: '$(testsFolder)/net6.0/win-x64/publish'
|
||||||
artifact: WindowsCoreTests
|
artifact: win-x64-tests
|
||||||
displayName: Publish Windows Test Package
|
displayName: Publish win-x64 Test Package
|
||||||
- publish: '$(testsFolder)/net6.0/linux-x64/publish'
|
- publish: '$(testsFolder)/net6.0/linux-x64/publish'
|
||||||
artifact: LinuxCoreTests
|
artifact: linux-x64-tests
|
||||||
displayName: Publish Linux Test Package
|
displayName: Publish linux-x64 Test Package
|
||||||
|
- publish: '$(testsFolder)/net6.0/linux-x86/publish'
|
||||||
|
artifact: linux-x86-tests
|
||||||
|
displayName: Publish linux-x86 Test Package
|
||||||
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
|
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
|
||||||
artifact: LinuxMuslCoreTests
|
artifact: linux-musl-x64-tests
|
||||||
displayName: Publish Linux Musl Test Package
|
displayName: Publish linux-musl-x64 Test Package
|
||||||
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish'
|
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish'
|
||||||
artifact: FreebsdCoreTests
|
artifact: freebsd-x64-tests
|
||||||
displayName: Publish FreeBSD Test Package
|
displayName: Publish freebsd-x64 Test Package
|
||||||
- publish: '$(testsFolder)/net6.0/osx-x64/publish'
|
- publish: '$(testsFolder)/net6.0/osx-x64/publish'
|
||||||
artifact: MacCoreTests
|
artifact: osx-x64-tests
|
||||||
displayName: Publish MacOS Test Package
|
displayName: Publish osx-x64 Test Package
|
||||||
|
|
||||||
- stage: Build_Backend_Other
|
- stage: Build_Backend_Other
|
||||||
displayName: Build Backend (Other OS)
|
displayName: Build Backend (Other OS)
|
||||||
@@ -141,25 +136,29 @@ stages:
|
|||||||
inputs:
|
inputs:
|
||||||
version: $(dotnetVersion)
|
version: $(dotnetVersion)
|
||||||
- bash: |
|
- bash: |
|
||||||
BUNDLEDVERSIONS=${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}/Microsoft.NETCoreSdk.BundledVersions.props
|
SDK_PATH="${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}"
|
||||||
echo $BUNDLEDVERSIONS
|
BUNDLEDVERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
|
||||||
grep osx-x64 $BUNDLEDVERSIONS
|
|
||||||
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
if ! grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
||||||
echo "BSD already enabled"
|
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
|
||||||
else
|
|
||||||
echo "Enabling BSD support"
|
|
||||||
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64/' $BUNDLEDVERSIONS
|
|
||||||
fi
|
fi
|
||||||
displayName: Enable FreeBSD Support
|
displayName: Extra Platform Support
|
||||||
- task: Cache@2
|
- task: Cache@2
|
||||||
inputs:
|
inputs:
|
||||||
key: 'nuget | "$(Agent.OS)" | $(Build.SourcesDirectory)/src/Directory.Packages.props'
|
key: 'nuget | "$(Agent.OS)" | $(Build.SourcesDirectory)/src/Directory.Packages.props'
|
||||||
path: $(nugetCacheFolder)
|
path: $(nugetCacheFolder)
|
||||||
displayName: Cache NuGet packages
|
displayName: Cache NuGet packages
|
||||||
- bash: ./build.sh --backend --enable-bsd
|
- bash: ./build.sh --backend --enable-extra-platforms
|
||||||
displayName: Build Readarr Backend
|
displayName: Build Readarr Backend
|
||||||
env:
|
env:
|
||||||
NUGET_PACKAGES: $(nugetCacheFolder)
|
NUGET_PACKAGES: $(nugetCacheFolder)
|
||||||
|
- bash: |
|
||||||
|
find ${OUTPUTFOLDER} -type f ! -path "*/publish/*" -exec rm -rf {} \;
|
||||||
|
find ${OUTPUTFOLDER} -depth -empty -type d -exec rm -r "{}" \;
|
||||||
|
find ${TESTSFOLDER} -type f ! -path "*/publish/*" -exec rm -rf {} \;
|
||||||
|
find ${TESTSFOLDER} -depth -empty -type d -exec rm -r "{}" \;
|
||||||
|
displayName: Clean up intermediate output
|
||||||
|
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
||||||
|
|
||||||
- stage: Build_Frontend
|
- stage: Build_Frontend
|
||||||
displayName: Frontend
|
displayName: Frontend
|
||||||
@@ -262,35 +261,35 @@ stages:
|
|||||||
artifactName: WindowsFrontend
|
artifactName: WindowsFrontend
|
||||||
targetPath: _output
|
targetPath: _output
|
||||||
displayName: Fetch Frontend
|
displayName: Fetch Frontend
|
||||||
- bash: ./build.sh --packages --enable-bsd
|
- bash: ./build.sh --packages --enable-extra-platforms
|
||||||
displayName: Create Packages
|
displayName: Create Packages
|
||||||
- bash: |
|
- bash: |
|
||||||
find . -name "Readarr" -exec chmod a+x {} \;
|
find . -name "Readarr" -exec chmod a+x {} \;
|
||||||
find . -name "Readarr.Update" -exec chmod a+x {} \;
|
find . -name "Readarr.Update" -exec chmod a+x {} \;
|
||||||
displayName: Set executable bits
|
displayName: Set executable bits
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create Windows Core zip
|
displayName: Create win-x64 zip
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).windows-core-x64.zip'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).windows-core-x64.zip'
|
||||||
archiveType: 'zip'
|
archiveType: 'zip'
|
||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/win-x64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/win-x64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create Windows x86 Core zip
|
displayName: Create win-x86 zip
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).windows-core-x86.zip'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).windows-core-x86.zip'
|
||||||
archiveType: 'zip'
|
archiveType: 'zip'
|
||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/win-x86/net6.0
|
rootFolderOrFile: $(artifactsFolder)/win-x86/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create MacOS x64 Core app
|
displayName: Create osx-x64 app
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).osx-app-core-x64.zip'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).osx-app-core-x64.zip'
|
||||||
archiveType: 'zip'
|
archiveType: 'zip'
|
||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/osx-x64-app/net6.0
|
rootFolderOrFile: $(artifactsFolder)/osx-x64-app/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create MacOS x64 Core tar
|
displayName: Create osx-x64 tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).osx-core-x64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).osx-core-x64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -298,14 +297,14 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/osx-x64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/osx-x64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create MacOS arm64 Core app
|
displayName: Create osx-arm64 app
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).osx-app-core-arm64.zip'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).osx-app-core-arm64.zip'
|
||||||
archiveType: 'zip'
|
archiveType: 'zip'
|
||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/osx-arm64-app/net6.0
|
rootFolderOrFile: $(artifactsFolder)/osx-arm64-app/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create MacOS arm64 Core tar
|
displayName: Create osx-arm64 tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).osx-core-arm64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).osx-core-arm64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -313,7 +312,7 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/osx-arm64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/osx-arm64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create Linux Core tar
|
displayName: Create linux-x64 tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).linux-core-x64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).linux-core-x64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -321,7 +320,7 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/linux-x64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/linux-x64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create Linux Musl Core tar
|
displayName: Create linux-musl-x64 tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).linux-musl-core-x64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).linux-musl-core-x64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -329,7 +328,15 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create ARM32 Linux Core tar
|
displayName: Create linux-x86 tar
|
||||||
|
inputs:
|
||||||
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).linux-core-x86.tar.gz'
|
||||||
|
archiveType: 'tar'
|
||||||
|
tarCompression: 'gz'
|
||||||
|
includeRootFolder: false
|
||||||
|
rootFolderOrFile: $(artifactsFolder)/linux-x86/net6.0
|
||||||
|
- task: ArchiveFiles@2
|
||||||
|
displayName: Create linux-arm tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).linux-core-arm.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).linux-core-arm.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -337,7 +344,7 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/linux-arm/net6.0
|
rootFolderOrFile: $(artifactsFolder)/linux-arm/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create ARM32 Linux Musl Core tar
|
displayName: Create linux-musl-arm tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).linux-musl-core-arm.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).linux-musl-core-arm.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -345,7 +352,7 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm/net6.0
|
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create Linux arm64 Core tar
|
displayName: Create linux-arm64 tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).linux-core-arm64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).linux-core-arm64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -353,7 +360,7 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create ARM64 Linux Musl Core tar
|
displayName: Create linux-musl-arm64 tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).linux-musl-core-arm64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).linux-musl-core-arm64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -361,7 +368,7 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create FreeBSD Core Core tar
|
displayName: Create freebsd-x64 tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).freebsd-core-x64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).freebsd-core-x64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -413,22 +420,22 @@ stages:
|
|||||||
matrix:
|
matrix:
|
||||||
MacCore:
|
MacCore:
|
||||||
osName: 'Mac'
|
osName: 'Mac'
|
||||||
testName: 'MacCore'
|
testName: 'osx-x64'
|
||||||
poolName: 'Azure Pipelines'
|
poolName: 'Azure Pipelines'
|
||||||
imageName: ${{ variables.macImage }}
|
imageName: ${{ variables.macImage }}
|
||||||
WindowsCore:
|
WindowsCore:
|
||||||
osName: 'Windows'
|
osName: 'Windows'
|
||||||
testName: 'WindowsCore'
|
testName: 'win-x64'
|
||||||
poolName: 'Azure Pipelines'
|
poolName: 'Azure Pipelines'
|
||||||
imageName: ${{ variables.windowsImage }}
|
imageName: ${{ variables.windowsImage }}
|
||||||
LinuxCore:
|
LinuxCore:
|
||||||
osName: 'Linux'
|
osName: 'Linux'
|
||||||
testName: 'LinuxCore'
|
testName: 'linux-x64'
|
||||||
poolName: 'Azure Pipelines'
|
poolName: 'Azure Pipelines'
|
||||||
imageName: ${{ variables.linuxImage }}
|
imageName: ${{ variables.linuxImage }}
|
||||||
FreebsdCore:
|
FreebsdCore:
|
||||||
osName: 'Linux'
|
osName: 'Linux'
|
||||||
testName: 'FreebsdCore'
|
testName: 'freebsd-x64'
|
||||||
poolName: 'FreeBSD'
|
poolName: 'FreeBSD'
|
||||||
imageName:
|
imageName:
|
||||||
|
|
||||||
@@ -447,7 +454,7 @@ stages:
|
|||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: '$(testName)Tests'
|
artifactName: '$(testName)-tests'
|
||||||
targetPath: $(testsFolder)
|
targetPath: $(testsFolder)
|
||||||
- powershell: Set-Service SCardSvr -StartupType Manual
|
- powershell: Set-Service SCardSvr -StartupType Manual
|
||||||
displayName: Enable Windows Test Service
|
displayName: Enable Windows Test Service
|
||||||
@@ -475,8 +482,12 @@ stages:
|
|||||||
matrix:
|
matrix:
|
||||||
alpine:
|
alpine:
|
||||||
testName: 'Musl Net Core'
|
testName: 'Musl Net Core'
|
||||||
artifactName: LinuxMuslCoreTests
|
artifactName: linux-musl-x64-tests
|
||||||
containerImage: ghcr.io/servarr/testimages:alpine
|
containerImage: ghcr.io/servarr/testimages:alpine
|
||||||
|
linux-x86:
|
||||||
|
testName: 'linux-x86'
|
||||||
|
artifactName: linux-x86-tests
|
||||||
|
containerImage: ghcr.io/servarr/testimages:linux-x86
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: ${{ variables.linuxImage }}
|
vmImage: ${{ variables.linuxImage }}
|
||||||
@@ -487,9 +498,15 @@ stages:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .net core'
|
displayName: 'Install .NET'
|
||||||
inputs:
|
inputs:
|
||||||
version: $(dotnetVersion)
|
version: $(dotnetVersion)
|
||||||
|
condition: and(succeeded(), ne(variables['testName'], 'linux-x86'))
|
||||||
|
- bash: |
|
||||||
|
SDKURL=$(curl -s https://api.github.com/repos/Servarr/dotnet-linux-x86/releases | jq -rc '.[].assets[].browser_download_url' | grep sdk-${DOTNETVERSION}.*gz$)
|
||||||
|
curl -fsSL $SDKURL | tar xzf - -C /opt/dotnet
|
||||||
|
displayName: 'Install .NET'
|
||||||
|
condition: and(succeeded(), eq(variables['testName'], 'linux-x86'))
|
||||||
- checkout: none
|
- checkout: none
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
@@ -512,6 +529,57 @@ stages:
|
|||||||
testResultsFiles: '**/TestResult.xml'
|
testResultsFiles: '**/TestResult.xml'
|
||||||
testRunTitle: '$(testName) Unit Tests'
|
testRunTitle: '$(testName) Unit Tests'
|
||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
|
|
||||||
|
- job: Unit_LinuxCore_Postgres
|
||||||
|
displayName: Unit Native LinuxCore with Postgres Database
|
||||||
|
variables:
|
||||||
|
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
||||||
|
artifactName: LinuxCoreTests
|
||||||
|
Readarr__Postgres__Host: 'localhost'
|
||||||
|
Readarr__Postgres__Port: '5432'
|
||||||
|
Readarr__Postgres__User: 'readarr'
|
||||||
|
Readarr__Postgres__Password: 'readarr'
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
|
||||||
|
timeoutInMinutes: 10
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: UseDotNet@2
|
||||||
|
displayName: 'Install .net core'
|
||||||
|
inputs:
|
||||||
|
version: $(dotnetVersion)
|
||||||
|
- checkout: none
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: Download Test Artifact
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: 'linux-x64-Tests'
|
||||||
|
targetPath: $(testsFolder)
|
||||||
|
- bash: find ${TESTSFOLDER} -name "Readarr.Test.Dummy" -exec chmod a+x {} \;
|
||||||
|
displayName: Make Test Dummy Executable
|
||||||
|
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
||||||
|
- bash: |
|
||||||
|
docker run -d --name=postgres14 \
|
||||||
|
-e POSTGRES_PASSWORD=readarr \
|
||||||
|
-e POSTGRES_USER=readarr \
|
||||||
|
-p 5432:5432/tcp \
|
||||||
|
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
|
||||||
|
postgres:14
|
||||||
|
displayName: Start postgres
|
||||||
|
- bash: |
|
||||||
|
chmod a+x ${TESTSFOLDER}/test.sh
|
||||||
|
ls -lR ${TESTSFOLDER}
|
||||||
|
${TESTSFOLDER}/test.sh Linux Unit Test
|
||||||
|
displayName: Run Tests
|
||||||
|
- task: PublishTestResults@2
|
||||||
|
displayName: Publish Test Results
|
||||||
|
inputs:
|
||||||
|
testResultsFormat: 'NUnit'
|
||||||
|
testResultsFiles: '**/TestResult.xml'
|
||||||
|
testRunTitle: 'LinuxCore Postgres Unit Tests'
|
||||||
|
failTaskOnFailedTests: true
|
||||||
|
|
||||||
- stage: Integration
|
- stage: Integration
|
||||||
displayName: Integration
|
displayName: Integration
|
||||||
@@ -523,17 +591,17 @@ stages:
|
|||||||
matrix:
|
matrix:
|
||||||
MacCore:
|
MacCore:
|
||||||
osName: 'Mac'
|
osName: 'Mac'
|
||||||
testName: 'MacCore'
|
testName: 'osx-x64'
|
||||||
imageName: ${{ variables.macImage }}
|
imageName: ${{ variables.macImage }}
|
||||||
pattern: 'Readarr.*.osx-core-x64.tar.gz'
|
pattern: 'Readarr.*.osx-core-x64.tar.gz'
|
||||||
WindowsCore:
|
WindowsCore:
|
||||||
osName: 'Windows'
|
osName: 'Windows'
|
||||||
testName: 'WindowsCore'
|
testName: 'win-x64'
|
||||||
imageName: ${{ variables.windowsImage }}
|
imageName: ${{ variables.windowsImage }}
|
||||||
pattern: 'Readarr.*.windows-core-x64.zip'
|
pattern: 'Readarr.*.windows-core-x64.zip'
|
||||||
LinuxCore:
|
LinuxCore:
|
||||||
osName: 'Linux'
|
osName: 'Linux'
|
||||||
testName: 'LinuxCore'
|
testName: 'linux-x64'
|
||||||
imageName: ${{ variables.linuxImage }}
|
imageName: ${{ variables.linuxImage }}
|
||||||
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
||||||
|
|
||||||
@@ -550,7 +618,7 @@ stages:
|
|||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: '$(testName)Tests'
|
artifactName: '$(testName)-tests'
|
||||||
targetPath: $(testsFolder)
|
targetPath: $(testsFolder)
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: Download Build Artifact
|
displayName: Download Build Artifact
|
||||||
@@ -580,6 +648,66 @@ stages:
|
|||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
displayName: Publish Test Results
|
displayName: Publish Test Results
|
||||||
|
|
||||||
|
- job: Integration_LinuxCore_Postgres
|
||||||
|
displayName: Integration Native LinuxCore with Postgres Database
|
||||||
|
variables:
|
||||||
|
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
||||||
|
Readarr__Postgres__Host: 'localhost'
|
||||||
|
Readarr__Postgres__Port: '5432'
|
||||||
|
Readarr__Postgres__User: 'readarr'
|
||||||
|
Readarr__Postgres__Password: 'readarr'
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: UseDotNet@2
|
||||||
|
displayName: 'Install .net core'
|
||||||
|
inputs:
|
||||||
|
version: $(dotnetVersion)
|
||||||
|
- checkout: none
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: Download Test Artifact
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: 'linux-x64-tests'
|
||||||
|
targetPath: $(testsFolder)
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: Download Build Artifact
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: Packages
|
||||||
|
itemPattern: '**/$(pattern)'
|
||||||
|
targetPath: $(Build.ArtifactStagingDirectory)
|
||||||
|
- task: ExtractFiles@1
|
||||||
|
inputs:
|
||||||
|
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||||
|
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||||
|
displayName: Extract Package
|
||||||
|
- bash: |
|
||||||
|
mkdir -p ./bin/
|
||||||
|
cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Readarr/. ./bin/
|
||||||
|
displayName: Move Package Contents
|
||||||
|
- bash: |
|
||||||
|
docker run -d --name=postgres14 \
|
||||||
|
-e POSTGRES_PASSWORD=readarr \
|
||||||
|
-e POSTGRES_USER=readarr \
|
||||||
|
-p 5432:5432/tcp \
|
||||||
|
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
|
||||||
|
postgres:14
|
||||||
|
displayName: Start postgres
|
||||||
|
- bash: |
|
||||||
|
chmod a+x ${TESTSFOLDER}/test.sh
|
||||||
|
${TESTSFOLDER}/test.sh Linux Integration Test
|
||||||
|
displayName: Run Integration Tests
|
||||||
|
- task: PublishTestResults@2
|
||||||
|
inputs:
|
||||||
|
testResultsFormat: 'NUnit'
|
||||||
|
testResultsFiles: '**/TestResult.xml'
|
||||||
|
testRunTitle: 'Integration LinuxCore Postgres Database Integration Tests'
|
||||||
|
failTaskOnFailedTests: true
|
||||||
|
displayName: Publish Test Results
|
||||||
|
|
||||||
- job: Integration_FreeBSD
|
- job: Integration_FreeBSD
|
||||||
displayName: Integration Native FreeBSD
|
displayName: Integration Native FreeBSD
|
||||||
workspace:
|
workspace:
|
||||||
@@ -595,7 +723,7 @@ stages:
|
|||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: 'FreebsdCoreTests'
|
artifactName: 'freebsd-x64-tests'
|
||||||
targetPath: $(testsFolder)
|
targetPath: $(testsFolder)
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: Download Build Artifact
|
displayName: Download Build Artifact
|
||||||
@@ -629,11 +757,15 @@ stages:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
alpine:
|
alpine:
|
||||||
testName: 'Musl Net Core'
|
testName: 'linux-musl-x64'
|
||||||
artifactName: LinuxMuslCoreTests
|
artifactName: linux-musl-x64-tests
|
||||||
containerImage: ghcr.io/servarr/testimages:alpine
|
containerImage: ghcr.io/servarr/testimages:alpine
|
||||||
pattern: 'Readarr.*.linux-musl-core-x64.tar.gz'
|
pattern: 'Readarr.*.linux-musl-core-x64.tar.gz'
|
||||||
|
linux-x86:
|
||||||
|
testName: 'linux-x86'
|
||||||
|
artifactName: linux-x86-tests
|
||||||
|
containerImage: ghcr.io/servarr/testimages:linux-x86
|
||||||
|
pattern: 'Readarr.*.linux-core-x86.tar.gz'
|
||||||
pool:
|
pool:
|
||||||
vmImage: ${{ variables.linuxImage }}
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
|
||||||
@@ -643,9 +775,15 @@ stages:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .net core'
|
displayName: 'Install .NET'
|
||||||
inputs:
|
inputs:
|
||||||
version: $(dotnetVersion)
|
version: $(dotnetVersion)
|
||||||
|
condition: and(succeeded(), ne(variables['testName'], 'linux-x86'))
|
||||||
|
- bash: |
|
||||||
|
SDKURL=$(curl -s https://api.github.com/repos/Servarr/dotnet-linux-x86/releases | jq -rc '.[].assets[].browser_download_url' | grep sdk-${DOTNETVERSION}.*gz$)
|
||||||
|
curl -fsSL $SDKURL | tar xzf - -C /opt/dotnet
|
||||||
|
displayName: 'Install .NET'
|
||||||
|
condition: and(succeeded(), eq(variables['testName'], 'linux-x86'))
|
||||||
- checkout: none
|
- checkout: none
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
@@ -691,14 +829,17 @@ stages:
|
|||||||
matrix:
|
matrix:
|
||||||
Linux:
|
Linux:
|
||||||
osName: 'Linux'
|
osName: 'Linux'
|
||||||
|
artifactName: 'linux-x64'
|
||||||
imageName: ${{ variables.linuxImage }}
|
imageName: ${{ variables.linuxImage }}
|
||||||
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
||||||
Mac:
|
Mac:
|
||||||
osName: 'Mac'
|
osName: 'Mac'
|
||||||
|
artifactName: 'osx-x64'
|
||||||
imageName: ${{ variables.macImage }}
|
imageName: ${{ variables.macImage }}
|
||||||
pattern: 'Readarr.*.osx-core-x64.tar.gz'
|
pattern: 'Readarr.*.osx-core-x64.tar.gz'
|
||||||
Windows:
|
Windows:
|
||||||
osName: 'Windows'
|
osName: 'Windows'
|
||||||
|
artifactName: 'win-x64'
|
||||||
imageName: ${{ variables.windowsImage }}
|
imageName: ${{ variables.windowsImage }}
|
||||||
pattern: 'Readarr.*.windows-core-x64.zip'
|
pattern: 'Readarr.*.windows-core-x64.zip'
|
||||||
|
|
||||||
@@ -715,7 +856,7 @@ stages:
|
|||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: '$(osName)CoreTests'
|
artifactName: '$(artifactName)-tests'
|
||||||
targetPath: $(testsFolder)
|
targetPath: $(testsFolder)
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: Download Build Artifact
|
displayName: Download Build Artifact
|
||||||
|
|||||||
45
build.sh
45
build.sh
@@ -27,15 +27,22 @@ UpdateVersionNumber()
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
EnableBsdSupport()
|
EnableExtraPlatformsInSDK()
|
||||||
{
|
{
|
||||||
#todo enable sdk with
|
SDK_PATH=$(dotnet --list-sdks | grep -P '6\.\d\.\d+' | head -1 | sed 's/\(6\.[0-9]*\.[0-9]*\).*\[\(.*\)\]/\2\/\1/g')
|
||||||
#SDK_PATH=$(dotnet --list-sdks | grep -P '5\.\d\.\d+' | head -1 | sed 's/\(5\.[0-9]*\.[0-9]*\).*\[\(.*\)\]/\2\/\1/g')
|
BUNDLEDVERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
|
||||||
# BUNDLED_VERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
|
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
||||||
|
echo "Extra platforms already enabled"
|
||||||
|
else
|
||||||
|
echo "Enabling extra platform support"
|
||||||
|
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
EnableExtraPlatforms()
|
||||||
|
{
|
||||||
if grep -qv freebsd-x64 src/Directory.Build.props; then
|
if grep -qv freebsd-x64 src/Directory.Build.props; then
|
||||||
sed -i'' -e "s^<RuntimeIdentifiers>\(.*\)</RuntimeIdentifiers>^<RuntimeIdentifiers>\1;freebsd-x64</RuntimeIdentifiers>^g" src/Directory.Build.props
|
sed -i'' -e "s^<RuntimeIdentifiers>\(.*\)</RuntimeIdentifiers>^<RuntimeIdentifiers>\1;freebsd-x64;linux-x86</RuntimeIdentifiers>^g" src/Directory.Build.props
|
||||||
sed -i'' -e "s^<ExcludedRuntimeFrameworkPairs>\(.*\)</ExcludedRuntimeFrameworkPairs>^<ExcludedRuntimeFrameworkPairs>\1;freebsd-x64:net472</ExcludedRuntimeFrameworkPairs>^g" src/Directory.Build.props
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,7 +299,8 @@ if [ $# -eq 0 ]; then
|
|||||||
PACKAGES=YES
|
PACKAGES=YES
|
||||||
INSTALLER=NO
|
INSTALLER=NO
|
||||||
LINT=YES
|
LINT=YES
|
||||||
ENABLE_BSD=NO
|
ENABLE_EXTRA_PLATFORMS=NO
|
||||||
|
ENABLE_EXTRA_PLATFORMS_IN_SDK=NO
|
||||||
fi
|
fi
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]
|
while [[ $# -gt 0 ]]
|
||||||
@@ -304,8 +312,12 @@ case $key in
|
|||||||
BACKEND=YES
|
BACKEND=YES
|
||||||
shift # past argument
|
shift # past argument
|
||||||
;;
|
;;
|
||||||
--enable-bsd)
|
--enable-bsd|--enable-extra-platforms)
|
||||||
ENABLE_BSD=YES
|
ENABLE_EXTRA_PLATFORMS=YES
|
||||||
|
shift # past argument
|
||||||
|
;;
|
||||||
|
--enable-extra-platforms-in-sdk)
|
||||||
|
ENABLE_EXTRA_PLATFORMS_IN_SDK=YES
|
||||||
shift # past argument
|
shift # past argument
|
||||||
;;
|
;;
|
||||||
-r|--runtime)
|
-r|--runtime)
|
||||||
@@ -349,12 +361,17 @@ esac
|
|||||||
done
|
done
|
||||||
set -- "${POSITIONAL[@]}" # restore positional parameters
|
set -- "${POSITIONAL[@]}" # restore positional parameters
|
||||||
|
|
||||||
|
if [ "$ENABLE_EXTRA_PLATFORMS_IN_SDK" = "YES" ];
|
||||||
|
then
|
||||||
|
EnableExtraPlatformsInSDK
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$BACKEND" = "YES" ];
|
if [ "$BACKEND" = "YES" ];
|
||||||
then
|
then
|
||||||
UpdateVersionNumber
|
UpdateVersionNumber
|
||||||
if [ "$ENABLE_BSD" = "YES" ];
|
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
|
||||||
then
|
then
|
||||||
EnableBsdSupport
|
EnableExtraPlatforms
|
||||||
fi
|
fi
|
||||||
Build
|
Build
|
||||||
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
|
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
|
||||||
@@ -364,9 +381,10 @@ then
|
|||||||
PackageTests "net6.0" "linux-x64"
|
PackageTests "net6.0" "linux-x64"
|
||||||
PackageTests "net6.0" "linux-musl-x64"
|
PackageTests "net6.0" "linux-musl-x64"
|
||||||
PackageTests "net6.0" "osx-x64"
|
PackageTests "net6.0" "osx-x64"
|
||||||
if [ "$ENABLE_BSD" = "YES" ];
|
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
|
||||||
then
|
then
|
||||||
PackageTests "net6.0" "freebsd-x64"
|
PackageTests "net6.0" "freebsd-x64"
|
||||||
|
PackageTests "net6.0" "linux-x86"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
PackageTests "$FRAMEWORK" "$RID"
|
PackageTests "$FRAMEWORK" "$RID"
|
||||||
@@ -405,9 +423,10 @@ then
|
|||||||
Package "net6.0" "linux-musl-arm"
|
Package "net6.0" "linux-musl-arm"
|
||||||
Package "net6.0" "osx-x64"
|
Package "net6.0" "osx-x64"
|
||||||
Package "net6.0" "osx-arm64"
|
Package "net6.0" "osx-arm64"
|
||||||
if [ "$ENABLE_BSD" = "YES" ];
|
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
|
||||||
then
|
then
|
||||||
Package "net6.0" "freebsd-x64"
|
Package "net6.0" "freebsd-x64"
|
||||||
|
Package "net6.0" "linux-x86"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
Package "$FRAMEWORK" "$RID"
|
Package "$FRAMEWORK" "$RID"
|
||||||
|
|||||||
@@ -8,17 +8,17 @@ function AuthorMonitorNewItemsOptionsPopoverContent() {
|
|||||||
<DescriptionList>
|
<DescriptionList>
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('AllBooks')}
|
title={translate('AllBooks')}
|
||||||
data="Monitor all new books"
|
data={translate('DataNewAllBooks')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('NewBooks')}
|
title={translate('NewBooks')}
|
||||||
data="Monitor new books released after the newest existing book"
|
data={translate('DataNewBooks')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('None')}
|
title={translate('None')}
|
||||||
data="Don't monitor any new books"
|
data={translate('DataNewNone')}
|
||||||
/>
|
/>
|
||||||
</DescriptionList>
|
</DescriptionList>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,42 +8,42 @@ function AuthorMonitoringOptionsPopoverContent() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Alert>
|
<Alert>
|
||||||
This is a one time adjustment to set which books are monitored
|
{translate('MonitoringOptionsHelpText')}
|
||||||
</Alert>
|
</Alert>
|
||||||
<DescriptionList>
|
<DescriptionList>
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('AllBooks')}
|
title={translate('AllBooks')}
|
||||||
data="Monitor all books"
|
data={translate('DataAllBooks')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('FutureBooks')}
|
title={translate('FutureBooks')}
|
||||||
data="Monitor books that have not released yet"
|
data={translate('DataFutureBooks')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('MissingBooks')}
|
title={translate('MissingBooks')}
|
||||||
data="Monitor books that do not have files or have not released yet"
|
data={translate('DataMissingBooks')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('ExistingBooks')}
|
title={translate('ExistingBooks')}
|
||||||
data="Monitor books that have files or have not released yet"
|
data={translate('DataExistingBooks')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('FirstBook')}
|
title={translate('FirstBook')}
|
||||||
data="Monitor the first book. All other books will be ignored"
|
data={translate('DataFirstBook')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('LatestBook')}
|
title={translate('LatestBook')}
|
||||||
data="Monitor the latest book and future books"
|
data={translate('DataLatestBook')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('None')}
|
title={translate('None')}
|
||||||
data="No books will be monitored"
|
data={translate('DataNone')}
|
||||||
/>
|
/>
|
||||||
</DescriptionList>
|
</DescriptionList>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import AppRoutes from './AppRoutes';
|
|||||||
|
|
||||||
function App({ store, history }) {
|
function App({ store, history }) {
|
||||||
return (
|
return (
|
||||||
<DocumentTitle title="Readarr">
|
<DocumentTitle title={window.Readarr.instanceName}>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ConnectedRouter history={history}>
|
<ConnectedRouter history={history}>
|
||||||
<PageConnector>
|
<PageConnector>
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ class AuthorEditorFooter extends Component {
|
|||||||
isDisabled={!selectedCount || isOrganizingAuthor || isRetaggingAuthor}
|
isDisabled={!selectedCount || isOrganizingAuthor || isRetaggingAuthor}
|
||||||
onPress={onOrganizeAuthorPress}
|
onPress={onOrganizeAuthorPress}
|
||||||
>
|
>
|
||||||
Rename Files
|
{translate('RenameFiles')}
|
||||||
</SpinnerButton>
|
</SpinnerButton>
|
||||||
|
|
||||||
<SpinnerButton
|
<SpinnerButton
|
||||||
@@ -277,7 +277,7 @@ class AuthorEditorFooter extends Component {
|
|||||||
isDisabled={!selectedCount || isOrganizingAuthor || isRetaggingAuthor}
|
isDisabled={!selectedCount || isOrganizingAuthor || isRetaggingAuthor}
|
||||||
onPress={onRetagAuthorPress}
|
onPress={onRetagAuthorPress}
|
||||||
>
|
>
|
||||||
Write Metadata Tags
|
{translate('WriteMetadataTags')}
|
||||||
</SpinnerButton>
|
</SpinnerButton>
|
||||||
|
|
||||||
<SpinnerButton
|
<SpinnerButton
|
||||||
@@ -286,7 +286,7 @@ class AuthorEditorFooter extends Component {
|
|||||||
isDisabled={!selectedCount || isOrganizingAuthor || isRetaggingAuthor}
|
isDisabled={!selectedCount || isOrganizingAuthor || isRetaggingAuthor}
|
||||||
onPress={this.onTagsPress}
|
onPress={this.onTagsPress}
|
||||||
>
|
>
|
||||||
Set Readarr Tags
|
{translate('SetReadarrTags')}
|
||||||
</SpinnerButton>
|
</SpinnerButton>
|
||||||
|
|
||||||
<SpinnerButton
|
<SpinnerButton
|
||||||
@@ -296,7 +296,7 @@ class AuthorEditorFooter extends Component {
|
|||||||
isDisabled={!selectedCount || isDeleting}
|
isDisabled={!selectedCount || isDeleting}
|
||||||
onPress={this.onDeleteSelectedPress}
|
onPress={this.onDeleteSelectedPress}
|
||||||
>
|
>
|
||||||
Delete
|
{translate('Delete')}
|
||||||
</SpinnerButton>
|
</SpinnerButton>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import * as commandNames from 'Commands/commandNames';
|
|||||||
import { toggleBooksMonitored } from 'Store/Actions/bookActions';
|
import { toggleBooksMonitored } from 'Store/Actions/bookActions';
|
||||||
import { clearBookFiles, fetchBookFiles } from 'Store/Actions/bookFileActions';
|
import { clearBookFiles, fetchBookFiles } from 'Store/Actions/bookFileActions';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
|
import { clearEditions, fetchEditions } from 'Store/Actions/editionActions';
|
||||||
import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions';
|
import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions';
|
||||||
import createAllAuthorSelector from 'Store/Selectors/createAllAuthorsSelector';
|
import createAllAuthorSelector from 'Store/Selectors/createAllAuthorsSelector';
|
||||||
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
||||||
@@ -43,11 +44,12 @@ function createMapStateToProps() {
|
|||||||
(state, { titleSlug }) => titleSlug,
|
(state, { titleSlug }) => titleSlug,
|
||||||
selectBookFiles,
|
selectBookFiles,
|
||||||
(state) => state.books,
|
(state) => state.books,
|
||||||
|
(state) => state.editions,
|
||||||
createAllAuthorSelector(),
|
createAllAuthorSelector(),
|
||||||
createCommandsSelector(),
|
createCommandsSelector(),
|
||||||
createUISettingsSelector(),
|
createUISettingsSelector(),
|
||||||
createDimensionsSelector(),
|
createDimensionsSelector(),
|
||||||
(titleSlug, bookFiles, books, authors, commands, uiSettings, dimensions) => {
|
(titleSlug, bookFiles, books, editions, authors, commands, uiSettings, dimensions) => {
|
||||||
const book = books.items.find((b) => b.titleSlug === titleSlug);
|
const book = books.items.find((b) => b.titleSlug === titleSlug);
|
||||||
const author = authors.find((a) => a.id === book.authorId);
|
const author = authors.find((a) => a.id === book.authorId);
|
||||||
const sortedBooks = books.items.filter((b) => b.authorId === book.authorId);
|
const sortedBooks = books.items.filter((b) => b.authorId === book.authorId);
|
||||||
@@ -79,8 +81,8 @@ function createMapStateToProps() {
|
|||||||
isRefreshingCommand.body.bookId === book.id
|
isRefreshingCommand.body.bookId === book.id
|
||||||
);
|
);
|
||||||
|
|
||||||
const isFetching = isBookFilesFetching;
|
const isFetching = isBookFilesFetching || editions.isFetching;
|
||||||
const isPopulated = isBookFilesPopulated;
|
const isPopulated = isBookFilesPopulated && editions.isPopulated;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...book,
|
...book,
|
||||||
@@ -104,6 +106,8 @@ const mapDispatchToProps = {
|
|||||||
executeCommand,
|
executeCommand,
|
||||||
fetchBookFiles,
|
fetchBookFiles,
|
||||||
clearBookFiles,
|
clearBookFiles,
|
||||||
|
fetchEditions,
|
||||||
|
clearEditions,
|
||||||
clearReleases,
|
clearReleases,
|
||||||
cancelFetchReleases,
|
cancelFetchReleases,
|
||||||
toggleBooksMonitored
|
toggleBooksMonitored
|
||||||
@@ -121,7 +125,8 @@ class BookDetailsConnector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (!_.isEqual(getMonitoredEditions(prevProps), getMonitoredEditions(this.props)) ||
|
if (prevProps.id !== this.props.id ||
|
||||||
|
!_.isEqual(getMonitoredEditions(prevProps), getMonitoredEditions(this.props)) ||
|
||||||
(prevProps.anyReleaseOk === false && this.props.anyReleaseOk === true)) {
|
(prevProps.anyReleaseOk === false && this.props.anyReleaseOk === true)) {
|
||||||
this.unpopulate();
|
this.unpopulate();
|
||||||
this.populate();
|
this.populate();
|
||||||
@@ -140,12 +145,14 @@ class BookDetailsConnector extends Component {
|
|||||||
const bookId = this.props.id;
|
const bookId = this.props.id;
|
||||||
|
|
||||||
this.props.fetchBookFiles({ bookId });
|
this.props.fetchBookFiles({ bookId });
|
||||||
|
this.props.fetchEditions({ bookId });
|
||||||
}
|
}
|
||||||
|
|
||||||
unpopulate = () => {
|
unpopulate = () => {
|
||||||
this.props.cancelFetchReleases();
|
this.props.cancelFetchReleases();
|
||||||
this.props.clearReleases();
|
this.props.clearReleases();
|
||||||
this.props.clearBookFiles();
|
this.props.clearBookFiles();
|
||||||
|
this.props.clearEditions();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -195,6 +202,8 @@ BookDetailsConnector.propTypes = {
|
|||||||
titleSlug: PropTypes.string.isRequired,
|
titleSlug: PropTypes.string.isRequired,
|
||||||
fetchBookFiles: PropTypes.func.isRequired,
|
fetchBookFiles: PropTypes.func.isRequired,
|
||||||
clearBookFiles: PropTypes.func.isRequired,
|
clearBookFiles: PropTypes.func.isRequired,
|
||||||
|
fetchEditions: PropTypes.func.isRequired,
|
||||||
|
clearEditions: PropTypes.func.isRequired,
|
||||||
clearReleases: PropTypes.func.isRequired,
|
clearReleases: PropTypes.func.isRequired,
|
||||||
cancelFetchReleases: PropTypes.func.isRequired,
|
cancelFetchReleases: PropTypes.func.isRequired,
|
||||||
toggleBooksMonitored: PropTypes.func.isRequired,
|
toggleBooksMonitored: PropTypes.func.isRequired,
|
||||||
|
|||||||
@@ -8,15 +8,25 @@ import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
|||||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||||
import BookDetailsHeader from './BookDetailsHeader';
|
import BookDetailsHeader from './BookDetailsHeader';
|
||||||
|
|
||||||
|
const selectOverview = createSelector(
|
||||||
|
(state) => state.editions,
|
||||||
|
(editions) => {
|
||||||
|
const monitored = editions.items.find((e) => e.monitored === true);
|
||||||
|
return monitored?.overview;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createBookSelector(),
|
createBookSelector(),
|
||||||
|
selectOverview,
|
||||||
createUISettingsSelector(),
|
createUISettingsSelector(),
|
||||||
createDimensionsSelector(),
|
createDimensionsSelector(),
|
||||||
(book, uiSettings, dimensions) => {
|
(book, overview, uiSettings, dimensions) => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...book,
|
...book,
|
||||||
|
overview,
|
||||||
shortDateFormat: uiSettings.shortDateFormat,
|
shortDateFormat: uiSettings.shortDateFormat,
|
||||||
isSmallScreen: dimensions.isSmallScreen
|
isSmallScreen: dimensions.isSmallScreen
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class EditBookModalContent extends Component {
|
|||||||
editions
|
editions
|
||||||
} = item;
|
} = item;
|
||||||
|
|
||||||
const hasFile = statistics ? statistics.bookFileCount : 0;
|
const hasFile = statistics ? statistics.bookFileCount > 0 : false;
|
||||||
const errorMessage = getErrorMessage(error, 'Unable to load editions');
|
const errorMessage = getErrorMessage(error, 'Unable to load editions');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -4,7 +4,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 { saveBook, setBookValue } from 'Store/Actions/bookActions';
|
import { saveBook, setBookValue } from 'Store/Actions/bookActions';
|
||||||
import { clearEditions, fetchEditions } from 'Store/Actions/editionActions';
|
import { saveEditions } from 'Store/Actions/editionActions';
|
||||||
import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
|
import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
|
||||||
import createBookSelector from 'Store/Selectors/createBookSelector';
|
import createBookSelector from 'Store/Selectors/createBookSelector';
|
||||||
import selectSettings from 'Store/Selectors/selectSettings';
|
import selectSettings from 'Store/Selectors/selectSettings';
|
||||||
@@ -26,17 +26,14 @@ function createMapStateToProps() {
|
|||||||
const {
|
const {
|
||||||
isFetching,
|
isFetching,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
error,
|
error
|
||||||
items
|
|
||||||
} = editionState;
|
} = editionState;
|
||||||
|
|
||||||
book.editions = items;
|
|
||||||
|
|
||||||
const bookSettings = _.pick(book, [
|
const bookSettings = _.pick(book, [
|
||||||
'monitored',
|
'monitored',
|
||||||
'anyEditionOk',
|
'anyEditionOk'
|
||||||
'editions'
|
|
||||||
]);
|
]);
|
||||||
|
bookSettings.editions = editionState.items;
|
||||||
|
|
||||||
const settings = selectSettings(bookSettings, pendingChanges, saveError);
|
const settings = selectSettings(bookSettings, pendingChanges, saveError);
|
||||||
|
|
||||||
@@ -58,10 +55,9 @@ function createMapStateToProps() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
dispatchFetchEditions: fetchEditions,
|
|
||||||
dispatchClearEditions: clearEditions,
|
|
||||||
dispatchSetBookValue: setBookValue,
|
dispatchSetBookValue: setBookValue,
|
||||||
dispatchSaveBook: saveBook
|
dispatchSaveBook: saveBook,
|
||||||
|
dispatchSaveEditions: saveEditions
|
||||||
};
|
};
|
||||||
|
|
||||||
class EditBookModalContentConnector extends Component {
|
class EditBookModalContentConnector extends Component {
|
||||||
@@ -69,20 +65,12 @@ class EditBookModalContentConnector extends Component {
|
|||||||
//
|
//
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.dispatchFetchEditions({ bookId: this.props.bookId });
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
if (prevProps.isSaving && !this.props.isSaving && !this.props.saveError) {
|
if (prevProps.isSaving && !this.props.isSaving && !this.props.saveError) {
|
||||||
this.props.onModalClose();
|
this.props.onModalClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.props.dispatchClearEditions();
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
@@ -94,6 +82,9 @@ class EditBookModalContentConnector extends Component {
|
|||||||
this.props.dispatchSaveBook({
|
this.props.dispatchSaveBook({
|
||||||
id: this.props.bookId
|
id: this.props.bookId
|
||||||
});
|
});
|
||||||
|
this.props.dispatchSaveEditions({
|
||||||
|
id: this.props.bookId
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -114,10 +105,9 @@ EditBookModalContentConnector.propTypes = {
|
|||||||
bookId: PropTypes.number,
|
bookId: PropTypes.number,
|
||||||
isSaving: PropTypes.bool.isRequired,
|
isSaving: PropTypes.bool.isRequired,
|
||||||
saveError: PropTypes.object,
|
saveError: PropTypes.object,
|
||||||
dispatchFetchEditions: PropTypes.func.isRequired,
|
|
||||||
dispatchClearEditions: PropTypes.func.isRequired,
|
|
||||||
dispatchSetBookValue: PropTypes.func.isRequired,
|
dispatchSetBookValue: PropTypes.func.isRequired,
|
||||||
dispatchSaveBook: PropTypes.func.isRequired,
|
dispatchSaveBook: PropTypes.func.isRequired,
|
||||||
|
dispatchSaveEditions: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { connect } from 'react-redux';
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import * as commandNames from 'Commands/commandNames';
|
import * as commandNames from 'Commands/commandNames';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
|
import createBookAuthorSelector from 'Store/Selectors/createBookAuthorSelector';
|
||||||
import createBookQualityProfileSelector from 'Store/Selectors/createBookQualityProfileSelector';
|
import createBookQualityProfileSelector from 'Store/Selectors/createBookQualityProfileSelector';
|
||||||
import createBookSelector from 'Store/Selectors/createBookSelector';
|
import createBookSelector from 'Store/Selectors/createBookSelector';
|
||||||
import createExecutingCommandsSelector from 'Store/Selectors/createExecutingCommandsSelector';
|
import createExecutingCommandsSelector from 'Store/Selectors/createExecutingCommandsSelector';
|
||||||
@@ -32,11 +33,13 @@ function selectShowSearchAction() {
|
|||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createBookSelector(),
|
createBookSelector(),
|
||||||
|
createBookAuthorSelector(),
|
||||||
createBookQualityProfileSelector(),
|
createBookQualityProfileSelector(),
|
||||||
selectShowSearchAction(),
|
selectShowSearchAction(),
|
||||||
createExecutingCommandsSelector(),
|
createExecutingCommandsSelector(),
|
||||||
(
|
(
|
||||||
book,
|
book,
|
||||||
|
author,
|
||||||
qualityProfile,
|
qualityProfile,
|
||||||
showSearchAction,
|
showSearchAction,
|
||||||
executingCommands
|
executingCommands
|
||||||
@@ -54,7 +57,7 @@ function createMapStateToProps() {
|
|||||||
const isRefreshingBook = executingCommands.some((command) => {
|
const isRefreshingBook = executingCommands.some((command) => {
|
||||||
return (
|
return (
|
||||||
(command.name === commandNames.REFRESH_AUTHOR &&
|
(command.name === commandNames.REFRESH_AUTHOR &&
|
||||||
command.body.authorId === book.author.id) ||
|
command.body.authorId === book.authorId) ||
|
||||||
(command.name === commandNames.REFRESH_BOOK &&
|
(command.name === commandNames.REFRESH_BOOK &&
|
||||||
command.body.bookId === book.id)
|
command.body.bookId === book.id)
|
||||||
);
|
);
|
||||||
@@ -63,7 +66,7 @@ function createMapStateToProps() {
|
|||||||
const isSearchingBook = executingCommands.some((command) => {
|
const isSearchingBook = executingCommands.some((command) => {
|
||||||
return (
|
return (
|
||||||
(command.name === commandNames.AUTHOR_SEARCH &&
|
(command.name === commandNames.AUTHOR_SEARCH &&
|
||||||
command.body.authorId === book.author.id) ||
|
command.body.authorId === book.authorId) ||
|
||||||
(command.name === commandNames.BOOK_SEARCH &&
|
(command.name === commandNames.BOOK_SEARCH &&
|
||||||
command.body.bookIds.includes(book.id))
|
command.body.bookIds.includes(book.id))
|
||||||
);
|
);
|
||||||
@@ -71,6 +74,7 @@ function createMapStateToProps() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...book,
|
...book,
|
||||||
|
author,
|
||||||
qualityProfile,
|
qualityProfile,
|
||||||
showSearchAction,
|
showSearchAction,
|
||||||
isRefreshingBook,
|
isRefreshingBook,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
|||||||
import { icons } from 'Helpers/Props';
|
import { icons } from 'Helpers/Props';
|
||||||
import dimensions from 'Styles/Variables/dimensions';
|
import dimensions from 'Styles/Variables/dimensions';
|
||||||
import fonts from 'Styles/Variables/fonts';
|
import fonts from 'Styles/Variables/fonts';
|
||||||
|
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||||
import stripHtml from 'Utilities/String/stripHtml';
|
import stripHtml from 'Utilities/String/stripHtml';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import BookIndexOverviewInfo from './BookIndexOverviewInfo';
|
import BookIndexOverviewInfo from './BookIndexOverviewInfo';
|
||||||
@@ -42,10 +43,26 @@ class BookIndexOverview extends Component {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isEditAuthorModalOpen: false,
|
isEditAuthorModalOpen: false,
|
||||||
isDeleteAuthorModalOpen: false
|
isDeleteAuthorModalOpen: false,
|
||||||
|
overview: ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { id } = this.props;
|
||||||
|
|
||||||
|
// Note that this component is lazy loaded by the virtualised view.
|
||||||
|
// We want to avoid storing overviews for *all* books which is
|
||||||
|
// why it's not put into the redux store
|
||||||
|
const promise = createAjaxRequest({
|
||||||
|
url: `/book/${id}/overview`
|
||||||
|
}).request;
|
||||||
|
|
||||||
|
promise.done((data) => {
|
||||||
|
this.setState({ overview: data.overview });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
@@ -84,7 +101,6 @@ class BookIndexOverview extends Component {
|
|||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
overview,
|
|
||||||
monitored,
|
monitored,
|
||||||
titleSlug,
|
titleSlug,
|
||||||
nextAiring,
|
nextAiring,
|
||||||
@@ -118,6 +134,7 @@ class BookIndexOverview extends Component {
|
|||||||
} = statistics;
|
} = statistics;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
overview,
|
||||||
isEditAuthorModalOpen,
|
isEditAuthorModalOpen,
|
||||||
isDeleteAuthorModalOpen
|
isDeleteAuthorModalOpen
|
||||||
} = this.state;
|
} = this.state;
|
||||||
@@ -267,7 +284,6 @@ class BookIndexOverview extends Component {
|
|||||||
BookIndexOverview.propTypes = {
|
BookIndexOverview.propTypes = {
|
||||||
id: PropTypes.number.isRequired,
|
id: PropTypes.number.isRequired,
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
overview: PropTypes.string.isRequired,
|
|
||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
titleSlug: PropTypes.string.isRequired,
|
titleSlug: PropTypes.string.isRequired,
|
||||||
nextAiring: PropTypes.string,
|
nextAiring: PropTypes.string,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import SelectInput from 'Components/Form/SelectInput';
|
|||||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||||
import PageContentFooter from 'Components/Page/PageContentFooter';
|
import PageContentFooter from 'Components/Page/PageContentFooter';
|
||||||
import { kinds } from 'Helpers/Props';
|
import { kinds } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import styles from './BookshelfFooter.css';
|
import styles from './BookshelfFooter.css';
|
||||||
|
|
||||||
const NO_CHANGE = 'noChange';
|
const NO_CHANGE = 'noChange';
|
||||||
@@ -114,7 +115,7 @@ class BookshelfFooter extends Component {
|
|||||||
|
|
||||||
<div className={styles.inputContainer}>
|
<div className={styles.inputContainer}>
|
||||||
<div className={styles.label}>
|
<div className={styles.label}>
|
||||||
Monitor Existing Books
|
{translate('MonitorExistingBooks')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MonitorBooksSelectInput
|
<MonitorBooksSelectInput
|
||||||
@@ -128,7 +129,7 @@ class BookshelfFooter extends Component {
|
|||||||
|
|
||||||
<div className={styles.inputContainer}>
|
<div className={styles.inputContainer}>
|
||||||
<div className={styles.label}>
|
<div className={styles.label}>
|
||||||
Monitor New Books
|
{translate('MonitorNewBooks')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MonitorNewItemsSelectInput
|
<MonitorNewItemsSelectInput
|
||||||
@@ -152,7 +153,7 @@ class BookshelfFooter extends Component {
|
|||||||
isDisabled={!selectedCount || noChanges}
|
isDisabled={!selectedCount || noChanges}
|
||||||
onPress={this.onUpdateSelectedPress}
|
onPress={this.onUpdateSelectedPress}
|
||||||
>
|
>
|
||||||
Update Selected
|
{translate('UpdateSelected')}
|
||||||
</SpinnerButton>
|
</SpinnerButton>
|
||||||
</div>
|
</div>
|
||||||
</PageContentFooter>
|
</PageContentFooter>
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ class DateFilterBuilderRowValue extends Component {
|
|||||||
<TextInput
|
<TextInput
|
||||||
name={NAME}
|
name={NAME}
|
||||||
value={filterValue}
|
value={filterValue}
|
||||||
|
type="date"
|
||||||
placeholder="yyyy-mm-dd"
|
placeholder="yyyy-mm-dd"
|
||||||
onChange={this.onValueChange}
|
onChange={this.onValueChange}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
import { inputTypes } from 'Helpers/Props';
|
import { inputTypes } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
// import translate from 'Utilities/String/translate';
|
// import translate from 'Utilities/String/translate';
|
||||||
import AutoCompleteInput from './AutoCompleteInput';
|
import AutoCompleteInput from './AutoCompleteInput';
|
||||||
import BookEditionSelectInputConnector from './BookEditionSelectInputConnector';
|
import BookEditionSelectInputConnector from './BookEditionSelectInputConnector';
|
||||||
@@ -216,7 +217,7 @@ function FormInputGroup(props) {
|
|||||||
<Link
|
<Link
|
||||||
to={helpLink}
|
to={helpLink}
|
||||||
>
|
>
|
||||||
More Info
|
{translate('MoreInfo')}
|
||||||
</Link>
|
</Link>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ function PageContent(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary errorComponent={PageContentError}>
|
<ErrorBoundary errorComponent={PageContentError}>
|
||||||
<DocumentTitle title={title ? `${title} - Readarr` : 'Readarr'}>
|
<DocumentTitle title={title ? `${title} - ${window.Readarr.instanceName}` : window.Readarr.instanceName}>
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ 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 Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
import ModalBody from 'Components/Modal/ModalBody';
|
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';
|
||||||
@@ -9,7 +10,8 @@ import ModalHeader from 'Components/Modal/ModalHeader';
|
|||||||
import Table from 'Components/Table/Table';
|
import Table from 'Components/Table/Table';
|
||||||
import TableBody from 'Components/Table/TableBody';
|
import TableBody from 'Components/Table/TableBody';
|
||||||
import { scrollDirections } from 'Helpers/Props';
|
import { scrollDirections } from 'Helpers/Props';
|
||||||
import SelectEditionRow from './SelectEditionRow';
|
import translate from 'Utilities/String/translate';
|
||||||
|
import SelectEditionRowConnector from './SelectEditionRowConnector';
|
||||||
import styles from './SelectEditionModalContent.css';
|
import styles from './SelectEditionModalContent.css';
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
@@ -33,15 +35,30 @@ class SelectEditionModalContent extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
books,
|
books,
|
||||||
|
isPopulated,
|
||||||
|
isFetching,
|
||||||
|
error,
|
||||||
onEditionSelect,
|
onEditionSelect,
|
||||||
onModalClose,
|
onModalClose,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
if (!isPopulated && !error) {
|
||||||
|
return (<LoadingIndicator />);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isFetching && error) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{translate('LoadingEditionsFailed')}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
Manual Import - Select Edition
|
{translate('ManualImportSelectEdition')}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody
|
<ModalBody
|
||||||
@@ -60,7 +77,7 @@ class SelectEditionModalContent extends Component {
|
|||||||
{
|
{
|
||||||
books.map((item) => {
|
books.map((item) => {
|
||||||
return (
|
return (
|
||||||
<SelectEditionRow
|
<SelectEditionRowConnector
|
||||||
key={item.book.id}
|
key={item.book.id}
|
||||||
matchedEditionId={item.matchedEditionId}
|
matchedEditionId={item.matchedEditionId}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
@@ -76,7 +93,7 @@ class SelectEditionModalContent extends Component {
|
|||||||
|
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button onPress={onModalClose}>
|
<Button onPress={onModalClose}>
|
||||||
Cancel
|
{translate('Cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
@@ -86,6 +103,9 @@ class SelectEditionModalContent extends Component {
|
|||||||
|
|
||||||
SelectEditionModalContent.propTypes = {
|
SelectEditionModalContent.propTypes = {
|
||||||
books: PropTypes.arrayOf(PropTypes.object).isRequired,
|
books: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
isFetching: PropTypes.bool,
|
||||||
|
isPopulated: PropTypes.bool,
|
||||||
|
error: PropTypes.object,
|
||||||
onEditionSelect: PropTypes.func.isRequired,
|
onEditionSelect: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,27 +1,71 @@
|
|||||||
import PropTypes from 'prop-types';
|
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 { createSelector } from 'reselect';
|
||||||
|
import { clearEditions, fetchEditions } from 'Store/Actions/editionActions';
|
||||||
import {
|
import {
|
||||||
saveInteractiveImportItem,
|
saveInteractiveImportItem,
|
||||||
updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions';
|
updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions';
|
||||||
|
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||||
import SelectEditionModalContent from './SelectEditionModalContent';
|
import SelectEditionModalContent from './SelectEditionModalContent';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return {};
|
return createSelector(
|
||||||
|
(state) => state.editions,
|
||||||
|
(editions) => {
|
||||||
|
const {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error
|
||||||
|
} = editions;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
|
fetchEditions,
|
||||||
|
clearEditions,
|
||||||
updateInteractiveImportItem,
|
updateInteractiveImportItem,
|
||||||
saveInteractiveImportItem
|
saveInteractiveImportItem
|
||||||
};
|
};
|
||||||
|
|
||||||
class SelectEditionModalContentConnector extends Component {
|
class SelectEditionModalContentConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
registerPagePopulator(this.populate);
|
||||||
|
this.populate();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
unregisterPagePopulator(this.populate);
|
||||||
|
this.unpopulate();
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// Control
|
||||||
|
|
||||||
|
populate = () => {
|
||||||
|
const bookId = this.props.books.map((b) => b.book.id);
|
||||||
|
|
||||||
|
this.props.fetchEditions({ bookId });
|
||||||
|
}
|
||||||
|
|
||||||
|
unpopulate = () => {
|
||||||
|
this.props.clearEditions();
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onEditionSelect = (bookId, foreignEditionId) => {
|
onEditionSelect = (bookId, foreignEditionId) => {
|
||||||
console.log(`book: ${bookId} id: ${foreignEditionId} ${typeof foreignEditionId}`);
|
|
||||||
const ids = this.props.importIdsByBook[bookId];
|
const ids = this.props.importIdsByBook[bookId];
|
||||||
|
|
||||||
ids.forEach((id) => {
|
ids.forEach((id) => {
|
||||||
@@ -55,6 +99,8 @@ class SelectEditionModalContentConnector extends Component {
|
|||||||
SelectEditionModalContentConnector.propTypes = {
|
SelectEditionModalContentConnector.propTypes = {
|
||||||
importIdsByBook: PropTypes.object.isRequired,
|
importIdsByBook: PropTypes.object.isRequired,
|
||||||
books: PropTypes.arrayOf(PropTypes.object).isRequired,
|
books: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
fetchEditions: PropTypes.func.isRequired,
|
||||||
|
clearEditions: PropTypes.func.isRequired,
|
||||||
updateInteractiveImportItem: PropTypes.func.isRequired,
|
updateInteractiveImportItem: PropTypes.func.isRequired,
|
||||||
saveInteractiveImportItem: PropTypes.func.isRequired,
|
saveInteractiveImportItem: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import SelectEditionRow from './SelectEditionRow';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
(state, { id }) => id,
|
||||||
|
(state) => state.editions,
|
||||||
|
(id, editionState) => {
|
||||||
|
const editions = editionState.items.filter((e) => e.bookId === id);
|
||||||
|
return { editions };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelectEditionRowConnector extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<SelectEditionRow
|
||||||
|
{...this.props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectEditionRowConnector.PropTypes = {
|
||||||
|
editions: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps)(SelectEditionRowConnector);
|
||||||
@@ -138,7 +138,7 @@ function InteractiveSearch(props) {
|
|||||||
items.map((item) => {
|
items.map((item) => {
|
||||||
return (
|
return (
|
||||||
<InteractiveSearchRow
|
<InteractiveSearchRow
|
||||||
key={item.guid}
|
key={`${item.indexerId}-${item.guid}`}
|
||||||
{...item}
|
{...item}
|
||||||
searchPayload={searchPayload}
|
searchPayload={searchPayload}
|
||||||
longDateFormat={longDateFormat}
|
longDateFormat={longDateFormat}
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ class AddAuthorOptionsForm extends Component {
|
|||||||
|
|
||||||
<FormGroup className={showMetadataProfile ? undefined : styles.hideMetadataProfile}>
|
<FormGroup className={showMetadataProfile ? undefined : styles.hideMetadataProfile}>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
Metadata Profile
|
{translate('MetadataProfile')}
|
||||||
|
|
||||||
{
|
{
|
||||||
includeNoneMetadataProfile &&
|
includeNoneMetadataProfile &&
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ function HostSettings(props) {
|
|||||||
bindAddress,
|
bindAddress,
|
||||||
port,
|
port,
|
||||||
urlBase,
|
urlBase,
|
||||||
|
instanceName,
|
||||||
enableSsl,
|
enableSsl,
|
||||||
sslPort,
|
sslPort,
|
||||||
sslCertPath,
|
sslCertPath,
|
||||||
@@ -78,6 +79,22 @@ function HostSettings(props) {
|
|||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup
|
||||||
|
advancedSettings={advancedSettings}
|
||||||
|
isAdvanced={true}
|
||||||
|
>
|
||||||
|
<FormLabel>{translate('InstanceName')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.TEXT}
|
||||||
|
name="instanceName"
|
||||||
|
helpText={translate('InstanceNameHelpText')}
|
||||||
|
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...instanceName}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup
|
<FormGroup
|
||||||
advancedSettings={advancedSettings}
|
advancedSettings={advancedSettings}
|
||||||
isAdvanced={true}
|
isAdvanced={true}
|
||||||
|
|||||||
@@ -28,17 +28,17 @@ function ImportListMonitoringOptionsPopoverContent() {
|
|||||||
<DescriptionList>
|
<DescriptionList>
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('None')}
|
title={translate('None')}
|
||||||
data="Do not monitor authors or books"
|
data={translate('DataListMonitorNone')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('SpecificBook')}
|
title={translate('SpecificBook')}
|
||||||
data="Monitor authors but only monitor books explicitly included in the list"
|
data={translate('DataListMonitorSpecificBook')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('AllAuthorBooks')}
|
title={translate('AllAuthorBooks')}
|
||||||
data="Monitor authors and all books for each author included on the import list"
|
data={translate('DataListMonitorAll')}
|
||||||
/>
|
/>
|
||||||
</DescriptionList>
|
</DescriptionList>
|
||||||
);
|
);
|
||||||
@@ -89,7 +89,7 @@ function EditImportListModalContent(props) {
|
|||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
{id ? 'Edit List' : 'Add List'}
|
{id ? translate('EditList') : translate('AddList')}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
@@ -148,7 +148,7 @@ function EditImportListModalContent(props) {
|
|||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
Monitor
|
{translate('Monitor')}
|
||||||
|
|
||||||
<Popover
|
<Popover
|
||||||
anchor={
|
anchor={
|
||||||
@@ -318,7 +318,7 @@ function EditImportListModalContent(props) {
|
|||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
onPress={onDeleteImportListPress}
|
onPress={onDeleteImportListPress}
|
||||||
>
|
>
|
||||||
Delete
|
{translate('Delete')}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,13 +327,13 @@ function EditImportListModalContent(props) {
|
|||||||
error={saveError}
|
error={saveError}
|
||||||
onPress={onTestPress}
|
onPress={onTestPress}
|
||||||
>
|
>
|
||||||
Test
|
{translate('Test')}
|
||||||
</SpinnerErrorButton>
|
</SpinnerErrorButton>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onPress={onModalClose}
|
onPress={onModalClose}
|
||||||
>
|
>
|
||||||
Cancel
|
{translate('Cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<SpinnerErrorButton
|
<SpinnerErrorButton
|
||||||
@@ -341,7 +341,7 @@ function EditImportListModalContent(props) {
|
|||||||
error={saveError}
|
error={saveError}
|
||||||
onPress={onSavePress}
|
onPress={onSavePress}
|
||||||
>
|
>
|
||||||
Save
|
{translate('Save')}
|
||||||
</SpinnerErrorButton>
|
</SpinnerErrorButton>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|||||||
@@ -346,7 +346,7 @@ function EditRootFolderModalContent(props) {
|
|||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
Convert to format
|
{translate('ConvertToFormat')}
|
||||||
<Popover
|
<Popover
|
||||||
anchor={
|
anchor={
|
||||||
<Icon
|
<Icon
|
||||||
@@ -371,7 +371,7 @@ function EditRootFolderModalContent(props) {
|
|||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
Calibre Output Profile
|
{translate('CalibreOutputProfile')}
|
||||||
<Popover
|
<Popover
|
||||||
anchor={
|
anchor={
|
||||||
<Icon
|
<Icon
|
||||||
@@ -423,14 +423,14 @@ function EditRootFolderModalContent(props) {
|
|||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
onPress={onDeleteRootFolderPress}
|
onPress={onDeleteRootFolderPress}
|
||||||
>
|
>
|
||||||
Delete
|
{translate('Delete')}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onPress={onModalClose}
|
onPress={onModalClose}
|
||||||
>
|
>
|
||||||
Cancel
|
{translate('Cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<SpinnerErrorButton
|
<SpinnerErrorButton
|
||||||
@@ -438,7 +438,7 @@ function EditRootFolderModalContent(props) {
|
|||||||
error={saveError}
|
error={saveError}
|
||||||
onPress={onSavePress}
|
onPress={onSavePress}
|
||||||
>
|
>
|
||||||
Save
|
{translate('Save')}
|
||||||
</SpinnerErrorButton>
|
</SpinnerErrorButton>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|||||||
@@ -78,12 +78,8 @@ export const actionHandlers = handleThunks({
|
|||||||
} = payload;
|
} = payload;
|
||||||
|
|
||||||
const promise = createAjaxRequest({
|
const promise = createAjaxRequest({
|
||||||
url: '/history/failed',
|
url: `/history/failed/${historyId}`,
|
||||||
method: 'POST',
|
method: 'POST'
|
||||||
data: {
|
|
||||||
id: historyId
|
|
||||||
},
|
|
||||||
dataType: 'json'
|
|
||||||
}).request;
|
}).request;
|
||||||
|
|
||||||
promise.done(() => {
|
promise.done(() => {
|
||||||
|
|||||||
@@ -86,12 +86,8 @@ export const actionHandlers = handleThunks({
|
|||||||
} = payload;
|
} = payload;
|
||||||
|
|
||||||
const promise = createAjaxRequest({
|
const promise = createAjaxRequest({
|
||||||
url: '/history/failed',
|
url: `/history/failed/${historyId}`,
|
||||||
method: 'POST',
|
method: 'POST'
|
||||||
data: {
|
|
||||||
id: historyId
|
|
||||||
},
|
|
||||||
dataType: 'json'
|
|
||||||
}).request;
|
}).request;
|
||||||
|
|
||||||
promise.done(() => {
|
promise.done(() => {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { createAction } from 'redux-actions';
|
import { createAction } from 'redux-actions';
|
||||||
|
import { batchActions } from 'redux-batched-actions';
|
||||||
import { createThunk, handleThunks } from 'Store/thunks';
|
import { createThunk, handleThunks } from 'Store/thunks';
|
||||||
|
import getProviderState from 'Utilities/State/getProviderState';
|
||||||
|
import { updateItem } from './baseActions';
|
||||||
import createFetchHandler from './Creators/createFetchHandler';
|
import createFetchHandler from './Creators/createFetchHandler';
|
||||||
import createHandleActions from './Creators/createHandleActions';
|
import createHandleActions from './Creators/createHandleActions';
|
||||||
import createClearReducer from './Creators/Reducers/createClearReducer';
|
import createClearReducer from './Creators/Reducers/createClearReducer';
|
||||||
@@ -25,18 +28,39 @@ export const defaultState = {
|
|||||||
|
|
||||||
export const FETCH_EDITIONS = 'editions/fetchEditions';
|
export const FETCH_EDITIONS = 'editions/fetchEditions';
|
||||||
export const CLEAR_EDITIONS = 'editions/clearEditions';
|
export const CLEAR_EDITIONS = 'editions/clearEditions';
|
||||||
|
export const SAVE_EDITIONS = 'editions/saveEditions';
|
||||||
|
|
||||||
//
|
//
|
||||||
// Action Creators
|
// Action Creators
|
||||||
|
|
||||||
export const fetchEditions = createThunk(FETCH_EDITIONS);
|
export const fetchEditions = createThunk(FETCH_EDITIONS);
|
||||||
export const clearEditions = createAction(CLEAR_EDITIONS);
|
export const clearEditions = createAction(CLEAR_EDITIONS);
|
||||||
|
export const saveEditions = createThunk(SAVE_EDITIONS);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Action Handlers
|
// Action Handlers
|
||||||
|
|
||||||
export const actionHandlers = handleThunks({
|
export const actionHandlers = handleThunks({
|
||||||
[FETCH_EDITIONS]: createFetchHandler(section, '/edition')
|
[FETCH_EDITIONS]: createFetchHandler(section, '/edition'),
|
||||||
|
|
||||||
|
[SAVE_EDITIONS]: function(getState, payload, dispatch) {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
...otherPayload
|
||||||
|
} = payload;
|
||||||
|
|
||||||
|
const saveData = getProviderState({ id, ...otherPayload }, getState, 'books');
|
||||||
|
|
||||||
|
dispatch(batchActions([
|
||||||
|
...saveData.editions.map((edition) => {
|
||||||
|
return updateItem({
|
||||||
|
id: edition.id,
|
||||||
|
section: 'editions',
|
||||||
|
...edition
|
||||||
|
});
|
||||||
|
})
|
||||||
|
]));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -246,11 +246,8 @@ export const actionHandlers = handleThunks({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const promise = createAjaxRequest({
|
const promise = createAjaxRequest({
|
||||||
url: '/history/failed',
|
url: `/history/failed/${id}`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: {
|
|
||||||
id
|
|
||||||
},
|
|
||||||
dataType: 'json'
|
dataType: 'json'
|
||||||
}).request;
|
}).request;
|
||||||
|
|
||||||
|
|||||||
@@ -177,7 +177,8 @@ export const defaultState = {
|
|||||||
{
|
{
|
||||||
name: 'size',
|
name: 'size',
|
||||||
label: 'Size',
|
label: 'Size',
|
||||||
type: filterBuilderTypes.NUMBER
|
type: filterBuilderTypes.NUMBER,
|
||||||
|
valueType: filterBuilderValueTypes.BYTES
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'seeders',
|
name: 'seeders',
|
||||||
|
|||||||
15
frontend/src/Store/Selectors/createBookAuthorSelector.js
Normal file
15
frontend/src/Store/Selectors/createBookAuthorSelector.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import createBookSelector from './createBookSelector';
|
||||||
|
|
||||||
|
function createBookAuthorSelector() {
|
||||||
|
return createSelector(
|
||||||
|
createBookSelector(),
|
||||||
|
(state) => state.authors.itemMap,
|
||||||
|
(state) => state.authors.items,
|
||||||
|
(book, authorMap, allAuthors) => {
|
||||||
|
return allAuthors[authorMap[book.authorId]];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createBookAuthorSelector;
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { createSelector, createSelectorCreator, defaultMemoize } from 'reselect';
|
import { createSelector, createSelectorCreator, defaultMemoize } from 'reselect';
|
||||||
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
||||||
import createClientSideCollectionSelector from './createClientSideCollectionSelector';
|
import createBooksClientSideCollectionSelector from './createBooksClientSideCollectionSelector';
|
||||||
|
|
||||||
function createUnoptimizedSelector(uiSection) {
|
function createUnoptimizedSelector(uiSection) {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createClientSideCollectionSelector('books', uiSection),
|
createBooksClientSideCollectionSelector(uiSection),
|
||||||
(books) => {
|
(books) => {
|
||||||
const items = books.items.map((s) => {
|
const items = books.items.map((s) => {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import createBookSelector from './createBookSelector';
|
import createBookAuthorSelector from './createBookAuthorSelector';
|
||||||
|
|
||||||
function createBookQualityProfileSelector() {
|
function createBookQualityProfileSelector() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.settings.qualityProfiles.items,
|
(state) => state.settings.qualityProfiles.items,
|
||||||
createBookSelector(),
|
createBookAuthorSelector(),
|
||||||
(qualityProfiles, book) => {
|
(qualityProfiles, author) => {
|
||||||
if (!book) {
|
if (!author) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return qualityProfiles.find((profile) => {
|
return qualityProfiles.find((profile) => profile.id === author.qualityProfileId);
|
||||||
return profile.id === book.author.qualityProfileId;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import filterCollection from 'Utilities/Array/filterCollection';
|
||||||
|
import sortCollection from 'Utilities/Array/sortCollection';
|
||||||
|
import createCustomFiltersSelector from './createCustomFiltersSelector';
|
||||||
|
|
||||||
|
function createBooksClientSideCollectionSelector(uiSection) {
|
||||||
|
return createSelector(
|
||||||
|
(state) => _.get(state, 'books'),
|
||||||
|
(state) => _.get(state, 'authors'),
|
||||||
|
(state) => _.get(state, uiSection),
|
||||||
|
createCustomFiltersSelector('books', uiSection),
|
||||||
|
(bookState, authorState, uiSectionState = {}, customFilters) => {
|
||||||
|
const state = Object.assign({}, bookState, uiSectionState, { customFilters });
|
||||||
|
|
||||||
|
const books = state.items;
|
||||||
|
for (const book of books) {
|
||||||
|
book.author = authorState.items[authorState.itemMap[book.authorId]];
|
||||||
|
}
|
||||||
|
|
||||||
|
const filtered = filterCollection(books, state);
|
||||||
|
const sorted = sortCollection(filtered, state);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...bookState,
|
||||||
|
...uiSectionState,
|
||||||
|
customFilters,
|
||||||
|
items: sorted,
|
||||||
|
totalItems: state.items.length
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createBooksClientSideCollectionSelector;
|
||||||
@@ -1,116 +1,8 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { filterTypePredicates, filterTypes, sortDirections } from 'Helpers/Props';
|
import filterCollection from 'Utilities/Array/filterCollection';
|
||||||
import findSelectedFilters from 'Utilities/Filter/findSelectedFilters';
|
import sortCollection from 'Utilities/Array/sortCollection';
|
||||||
|
import createCustomFiltersSelector from './createCustomFiltersSelector';
|
||||||
function getSortClause(sortKey, sortDirection, sortPredicates) {
|
|
||||||
if (sortPredicates && sortPredicates.hasOwnProperty(sortKey)) {
|
|
||||||
return function(item) {
|
|
||||||
return sortPredicates[sortKey](item, sortDirection);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(item) {
|
|
||||||
return item[sortKey];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function filter(items, state) {
|
|
||||||
const {
|
|
||||||
selectedFilterKey,
|
|
||||||
filters,
|
|
||||||
customFilters,
|
|
||||||
filterPredicates
|
|
||||||
} = state;
|
|
||||||
|
|
||||||
if (!selectedFilterKey) {
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedFilters = findSelectedFilters(selectedFilterKey, filters, customFilters);
|
|
||||||
|
|
||||||
return _.filter(items, (item) => {
|
|
||||||
let i = 0;
|
|
||||||
let accepted = true;
|
|
||||||
|
|
||||||
while (accepted && i < selectedFilters.length) {
|
|
||||||
const {
|
|
||||||
key,
|
|
||||||
value,
|
|
||||||
type = filterTypes.EQUAL
|
|
||||||
} = selectedFilters[i];
|
|
||||||
|
|
||||||
if (filterPredicates && filterPredicates.hasOwnProperty(key)) {
|
|
||||||
const predicate = filterPredicates[key];
|
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
accepted = value.some((v) => predicate(item, v, type));
|
|
||||||
} else {
|
|
||||||
accepted = predicate(item, value, type);
|
|
||||||
}
|
|
||||||
} else if (item.hasOwnProperty(key)) {
|
|
||||||
const predicate = filterTypePredicates[type];
|
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
if (
|
|
||||||
type === filterTypes.NOT_CONTAINS ||
|
|
||||||
type === filterTypes.NOT_EQUAL
|
|
||||||
) {
|
|
||||||
accepted = value.every((v) => predicate(item[key], v));
|
|
||||||
} else {
|
|
||||||
accepted = value.some((v) => predicate(item[key], v));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
accepted = predicate(item[key], value);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Default to false if the filter can't be tested
|
|
||||||
accepted = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return accepted;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function sort(items, state) {
|
|
||||||
const {
|
|
||||||
sortKey,
|
|
||||||
sortDirection,
|
|
||||||
sortPredicates,
|
|
||||||
secondarySortKey,
|
|
||||||
secondarySortDirection
|
|
||||||
} = state;
|
|
||||||
|
|
||||||
const clauses = [];
|
|
||||||
const orders = [];
|
|
||||||
|
|
||||||
clauses.push(getSortClause(sortKey, sortDirection, sortPredicates));
|
|
||||||
orders.push(sortDirection === sortDirections.ASCENDING ? 'asc' : 'desc');
|
|
||||||
|
|
||||||
if (secondarySortKey &&
|
|
||||||
secondarySortDirection &&
|
|
||||||
(sortKey !== secondarySortKey ||
|
|
||||||
sortDirection !== secondarySortDirection)) {
|
|
||||||
clauses.push(getSortClause(secondarySortKey, secondarySortDirection, sortPredicates));
|
|
||||||
orders.push(secondarySortDirection === sortDirections.ASCENDING ? 'asc' : 'desc');
|
|
||||||
}
|
|
||||||
|
|
||||||
return _.orderBy(items, clauses, orders);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createCustomFiltersSelector(type, alternateType) {
|
|
||||||
return createSelector(
|
|
||||||
(state) => state.customFilters.items,
|
|
||||||
(customFilters) => {
|
|
||||||
return customFilters.filter((customFilter) => {
|
|
||||||
return customFilter.type === type || customFilter.type === alternateType;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createClientSideCollectionSelector(section, uiSection) {
|
function createClientSideCollectionSelector(section, uiSection) {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
@@ -120,8 +12,8 @@ function createClientSideCollectionSelector(section, uiSection) {
|
|||||||
(sectionState, uiSectionState = {}, customFilters) => {
|
(sectionState, uiSectionState = {}, customFilters) => {
|
||||||
const state = Object.assign({}, sectionState, uiSectionState, { customFilters });
|
const state = Object.assign({}, sectionState, uiSectionState, { customFilters });
|
||||||
|
|
||||||
const filtered = filter(state.items, state);
|
const filtered = filterCollection(state.items, state);
|
||||||
const sorted = sort(filtered, state);
|
const sorted = sortCollection(filtered, state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...sectionState,
|
...sectionState,
|
||||||
|
|||||||
14
frontend/src/Store/Selectors/createCustomFiltersSelector.js
Normal file
14
frontend/src/Store/Selectors/createCustomFiltersSelector.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
|
function createCustomFiltersSelector(type, alternateType) {
|
||||||
|
return createSelector(
|
||||||
|
(state) => state.customFilters.items,
|
||||||
|
(customFilters) => {
|
||||||
|
return customFilters.filter((customFilter) => {
|
||||||
|
return customFilter.type === type || customFilter.type === alternateType;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createCustomFiltersSelector;
|
||||||
@@ -24,6 +24,8 @@ class About extends Component {
|
|||||||
isDocker,
|
isDocker,
|
||||||
runtimeVersion,
|
runtimeVersion,
|
||||||
migrationVersion,
|
migrationVersion,
|
||||||
|
databaseVersion,
|
||||||
|
databaseType,
|
||||||
appData,
|
appData,
|
||||||
startupPath,
|
startupPath,
|
||||||
mode,
|
mode,
|
||||||
@@ -77,6 +79,11 @@ class About extends Component {
|
|||||||
data={migrationVersion}
|
data={migrationVersion}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<DescriptionListItem
|
||||||
|
title={translate('Database')}
|
||||||
|
data={`${titleCase(databaseType)} ${databaseVersion}`}
|
||||||
|
/>
|
||||||
|
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('AppDataDirectory')}
|
title={translate('AppDataDirectory')}
|
||||||
data={appData}
|
data={appData}
|
||||||
@@ -118,6 +125,8 @@ About.propTypes = {
|
|||||||
runtimeVersion: PropTypes.string.isRequired,
|
runtimeVersion: PropTypes.string.isRequired,
|
||||||
isDocker: PropTypes.bool.isRequired,
|
isDocker: PropTypes.bool.isRequired,
|
||||||
migrationVersion: PropTypes.number.isRequired,
|
migrationVersion: PropTypes.number.isRequired,
|
||||||
|
databaseType: PropTypes.string.isRequired,
|
||||||
|
databaseVersion: PropTypes.string.isRequired,
|
||||||
appData: PropTypes.string.isRequired,
|
appData: PropTypes.string.isRequired,
|
||||||
startupPath: PropTypes.string.isRequired,
|
startupPath: PropTypes.string.isRequired,
|
||||||
mode: PropTypes.string.isRequired,
|
mode: PropTypes.string.isRequired,
|
||||||
|
|||||||
72
frontend/src/Utilities/Array/filterCollection.js
Normal file
72
frontend/src/Utilities/Array/filterCollection.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import { filterTypePredicates, filterTypes } from 'Helpers/Props';
|
||||||
|
import findSelectedFilters from 'Utilities/Filter/findSelectedFilters';
|
||||||
|
|
||||||
|
function filterCollection(items, state) {
|
||||||
|
const {
|
||||||
|
selectedFilterKey,
|
||||||
|
filters,
|
||||||
|
customFilters,
|
||||||
|
filterPredicates
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
if (!selectedFilterKey) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedFilters = findSelectedFilters(selectedFilterKey, filters, customFilters);
|
||||||
|
|
||||||
|
return _.filter(items, (item) => {
|
||||||
|
let i = 0;
|
||||||
|
let accepted = true;
|
||||||
|
|
||||||
|
while (accepted && i < selectedFilters.length) {
|
||||||
|
const {
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
type = filterTypes.EQUAL
|
||||||
|
} = selectedFilters[i];
|
||||||
|
|
||||||
|
if (filterPredicates && filterPredicates.hasOwnProperty(key)) {
|
||||||
|
const predicate = filterPredicates[key];
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
if (
|
||||||
|
type === filterTypes.NOT_CONTAINS ||
|
||||||
|
type === filterTypes.NOT_EQUAL
|
||||||
|
) {
|
||||||
|
accepted = value.every((v) => predicate(item, v, type));
|
||||||
|
} else {
|
||||||
|
accepted = value.some((v) => predicate(item, v, type));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
accepted = predicate(item, value, type);
|
||||||
|
}
|
||||||
|
} else if (item.hasOwnProperty(key)) {
|
||||||
|
const predicate = filterTypePredicates[type];
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
if (
|
||||||
|
type === filterTypes.NOT_CONTAINS ||
|
||||||
|
type === filterTypes.NOT_EQUAL
|
||||||
|
) {
|
||||||
|
accepted = value.every((v) => predicate(item[key], v));
|
||||||
|
} else {
|
||||||
|
accepted = value.some((v) => predicate(item[key], v));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
accepted = predicate(item[key], value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Default to false if the filter can't be tested
|
||||||
|
accepted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return accepted;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default filterCollection;
|
||||||
42
frontend/src/Utilities/Array/sortCollection.js
Normal file
42
frontend/src/Utilities/Array/sortCollection.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import { sortDirections } from 'Helpers/Props';
|
||||||
|
|
||||||
|
function getSortClause(sortKey, sortDirection, sortPredicates) {
|
||||||
|
if (sortPredicates && sortPredicates.hasOwnProperty(sortKey)) {
|
||||||
|
return function(item) {
|
||||||
|
return sortPredicates[sortKey](item, sortDirection);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return function(item) {
|
||||||
|
return item[sortKey];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortCollection(items, state) {
|
||||||
|
const {
|
||||||
|
sortKey,
|
||||||
|
sortDirection,
|
||||||
|
sortPredicates,
|
||||||
|
secondarySortKey,
|
||||||
|
secondarySortDirection
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
const clauses = [];
|
||||||
|
const orders = [];
|
||||||
|
|
||||||
|
clauses.push(getSortClause(sortKey, sortDirection, sortPredicates));
|
||||||
|
orders.push(sortDirection === sortDirections.ASCENDING ? 'asc' : 'desc');
|
||||||
|
|
||||||
|
if (secondarySortKey &&
|
||||||
|
secondarySortDirection &&
|
||||||
|
(sortKey !== secondarySortKey ||
|
||||||
|
sortDirection !== secondarySortDirection)) {
|
||||||
|
clauses.push(getSortClause(secondarySortKey, secondarySortDirection, sortPredicates));
|
||||||
|
orders.push(secondarySortDirection === sortDirections.ASCENDING ? 'asc' : 'desc');
|
||||||
|
}
|
||||||
|
|
||||||
|
return _.orderBy(items, clauses, orders);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default sortCollection;
|
||||||
@@ -33,9 +33,9 @@
|
|||||||
"@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": "6.0.7",
|
||||||
"@sentry/browser": "6.10.0",
|
"@sentry/browser": "6.18.2",
|
||||||
"@sentry/integrations": "6.10.0",
|
"@sentry/integrations": "6.18.2",
|
||||||
"ansi-colors": "4.1.1",
|
"ansi-colors": "4.1.1",
|
||||||
"classnames": "2.3.1",
|
"classnames": "2.3.1",
|
||||||
"clipboard": "2.0.8",
|
"clipboard": "2.0.8",
|
||||||
@@ -134,5 +134,8 @@
|
|||||||
"webpack-cli": "4.7.2",
|
"webpack-cli": "4.7.2",
|
||||||
"webpack-livereload-plugin": "3.0.1",
|
"webpack-livereload-plugin": "3.0.1",
|
||||||
"worker-loader": "3.0.8"
|
"worker-loader": "3.0.8"
|
||||||
|
},
|
||||||
|
"volta": {
|
||||||
|
"node": "16.17.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
|
|||||||
|
|
||||||
[Tasks]
|
[Tasks]
|
||||||
Name: "desktopIcon"; Description: "{cm:CreateDesktopIcon}"
|
Name: "desktopIcon"; Description: "{cm:CreateDesktopIcon}"
|
||||||
Name: "windowsService"; Description: "Install Windows Service (Starts when the computer starts)"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
|
Name: "windowsService"; Description: "Install Windows Service (Starts when the computer starts as the LocalService user, you will need to change the user to access network shares)"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
|
||||||
Name: "startupShortcut"; Description: "Create shortcut in Startup folder (Starts when you log into Windows)"; GroupDescription: "Start automatically"; Flags: exclusive
|
Name: "startupShortcut"; Description: "Create shortcut in Startup folder (Starts when you log into Windows)"; GroupDescription: "Start automatically"; Flags: exclusive
|
||||||
Name: "none"; Description: "Do not start automatically"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
|
Name: "none"; Description: "Do not start automatically"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
|
||||||
|
|
||||||
|
|||||||
@@ -4,60 +4,62 @@
|
|||||||
<PackageVersion Include="AutoFixture" Version="4.17.0" />
|
<PackageVersion Include="AutoFixture" Version="4.17.0" />
|
||||||
<PackageVersion Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" PrivateAssets="all" />
|
<PackageVersion Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" PrivateAssets="all" />
|
||||||
<PackageVersion Include="Dapper" Version="2.0.123" />
|
<PackageVersion Include="Dapper" Version="2.0.123" />
|
||||||
<PackageVersion Include="DryIoc.dll" Version="4.8.6" />
|
<PackageVersion Include="DryIoc.dll" Version="5.2.0" />
|
||||||
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="5.1.0" />
|
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.0.2" />
|
||||||
<PackageVersion Include="Equ" Version="2.3.0" />
|
<PackageVersion Include="Equ" Version="2.3.0" />
|
||||||
<PackageVersion Include="FluentAssertions" Version="5.10.3" />
|
<PackageVersion Include="FluentAssertions" Version="5.10.3" />
|
||||||
<PackageVersion Include="FluentMigrator.Runner.SQLite" Version="4.0.0-alpha.289" />
|
<PackageVersion Include="Servarr.FluentMigrator.Runner" Version="3.3.2.9" />
|
||||||
<PackageVersion Include="FluentMigrator.Runner" Version="4.0.0-alpha.289" />
|
<PackageVersion Include="Servarr.FluentMigrator.Runner.SQLite" Version="3.3.2.9" />
|
||||||
|
<PackageVersion Include="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" />
|
||||||
<PackageVersion Include="FluentValidation" Version="8.6.2" />
|
<PackageVersion Include="FluentValidation" Version="8.6.2" />
|
||||||
<PackageVersion Include="Ical.Net" Version="4.2.0" />
|
<PackageVersion Include="Ical.Net" Version="4.2.0" />
|
||||||
<PackageVersion Include="ImpromptuInterface" Version="7.0.1" />
|
<PackageVersion Include="ImpromptuInterface" Version="7.0.1" />
|
||||||
<PackageVersion Include="LazyCache" Version="2.4.0" />
|
<PackageVersion Include="LazyCache" Version="2.4.0" />
|
||||||
<PackageVersion Include="Mailkit" Version="3.1.1" />
|
<PackageVersion Include="Mailkit" Version="3.3.0" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.2" />
|
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.7" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="6.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||||
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||||
<PackageVersion Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr18" />
|
<PackageVersion Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr22" />
|
||||||
<PackageVersion Include="MonoTorrent" Version="2.0.4" />
|
<PackageVersion Include="Moq" Version="4.17.2" />
|
||||||
<PackageVersion Include="Moq" Version="4.16.1" />
|
<PackageVersion Include="MonoTorrent" Version="2.0.6" />
|
||||||
<PackageVersion Include="NBuilder" Version="6.1.0" />
|
<PackageVersion Include="NBuilder" Version="6.1.0" />
|
||||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageVersion Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageVersion Include="NLog.Extensions.Logging" Version="1.7.4" />
|
<PackageVersion Include="NLog.Extensions.Logging" Version="1.7.4" />
|
||||||
<PackageVersion Include="NLog" Version="4.7.13" />
|
<PackageVersion Include="NLog" Version="4.7.14" />
|
||||||
|
<PackageVersion Include="NLog.Targets.Syslog" Version="6.0.2" />
|
||||||
|
<PackageVersion Include="Npgsql" Version="6.0.3" />
|
||||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.2.1" />
|
<PackageVersion Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||||
<PackageVersion Include="NUnit" Version="3.13.2" />
|
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||||
<PackageVersion Include="NunitXml.TestLogger" Version="3.0.117" />
|
<PackageVersion Include="NunitXml.TestLogger" Version="3.0.117" />
|
||||||
<PackageVersion Include="PdfSharpCore" Version="1.3.12" />
|
<PackageVersion Include="PdfSharpCore" Version="1.3.32" />
|
||||||
<PackageVersion Include="RestSharp.Serializers.SystemTextJson" Version="106.15.0" />
|
<PackageVersion Include="RestSharp.Serializers.SystemTextJson" Version="106.15.0" />
|
||||||
<PackageVersion Include="RestSharp" Version="106.15.0" />
|
<PackageVersion Include="RestSharp" Version="106.15.0" />
|
||||||
<PackageVersion Include="Selenium.Support" Version="3.141.0" />
|
<PackageVersion Include="Selenium.Support" Version="3.141.0" />
|
||||||
<PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" />
|
<PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" />
|
||||||
<PackageVersion Include="Sentry" Version="3.13.0" />
|
<PackageVersion Include="Sentry" Version="3.20.1" />
|
||||||
<PackageVersion Include="SharpZipLib" Version="1.3.3" />
|
<PackageVersion Include="SharpZipLib" Version="1.3.3" />
|
||||||
<PackageVersion Include="SixLabors.ImageSharp" Version="1.0.4" />
|
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.3" />
|
||||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.1.118" />
|
<PackageVersion Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||||
<PackageVersion Include="System.Buffers" Version="4.5.1" />
|
<PackageVersion Include="System.Buffers" Version="4.5.1" />
|
||||||
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
|
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
|
||||||
<PackageVersion Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
<PackageVersion Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||||
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="16.1.10" />
|
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="17.0.24" />
|
||||||
<PackageVersion Include="System.IO.Abstractions" Version="16.1.10" />
|
<PackageVersion Include="System.IO.Abstractions" Version="17.0.24" />
|
||||||
<PackageVersion Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
|
<PackageVersion Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
|
||||||
<PackageVersion Include="System.Memory" Version="4.5.4" />
|
<PackageVersion Include="System.Memory" Version="4.5.5" />
|
||||||
<PackageVersion Include="System.Reflection.TypeExtensions" Version="4.7.0" />
|
<PackageVersion Include="System.Reflection.TypeExtensions" Version="4.7.0" />
|
||||||
<PackageVersion Include="System.Resources.Extensions" Version="6.0.0" />
|
<PackageVersion Include="System.Resources.Extensions" Version="6.0.0" />
|
||||||
<PackageVersion Include="System.Runtime.Loader" Version="4.3.0" />
|
<PackageVersion Include="System.Runtime.Loader" Version="4.3.0" />
|
||||||
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
|
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
|
||||||
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="6.0.0" />
|
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="6.0.0" />
|
||||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
<PackageVersion Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
||||||
<PackageVersion Include="System.Text.Json" Version="6.0.2" />
|
<PackageVersion Include="System.Text.Json" Version="6.0.5" />
|
||||||
<PackageVersion Include="System.ValueTuple" Version="4.5.0" />
|
<PackageVersion Include="System.ValueTuple" Version="4.5.0" />
|
||||||
<PackageVersion Include="TagLibSharp-Lidarr" Version="2.2.0.19" />
|
<PackageVersion Include="TagLibSharp-Lidarr" Version="2.2.0.19" />
|
||||||
<PackageVersion Include="Unity" Version="5.11.10" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -1,12 +1,33 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<configuration>
|
<configuration>
|
||||||
<packageSources>
|
<packageSources>
|
||||||
<clear />
|
<clear />
|
||||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||||
<add key="FluentMigrator" value="https://pkgs.dev.azure.com/fluentmigrator/fluentmigrator/_packaging/fluentmigrator/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="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="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="FluentMigrator" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/FluentMigrator/nuget/v3/index.json" />
|
||||||
</packageSources>
|
</packageSources>
|
||||||
|
<packageSourceMapping>
|
||||||
|
<!-- key value for <packageSource> should match key values from <packageSources> element -->
|
||||||
|
<packageSource key="nuget.org">
|
||||||
|
<package pattern="*" />
|
||||||
|
</packageSource>
|
||||||
|
<packageSource key="dotnet-bsd-crossbuild">
|
||||||
|
<package pattern="*" />
|
||||||
|
</packageSource>
|
||||||
|
<packageSource key="Mono.Posix.NETStandard">
|
||||||
|
<package pattern="Mono.Posix.NETStandard" />
|
||||||
|
</packageSource>
|
||||||
|
<packageSource key="SQLite">
|
||||||
|
<package pattern="System.Data.SQLite.Core.Servarr" />
|
||||||
|
</packageSource>
|
||||||
|
<packageSource key="coverlet-nightly">
|
||||||
|
<package pattern="coverlet.*" />
|
||||||
|
</packageSource>
|
||||||
|
<packageSource key="FluentMigrator">
|
||||||
|
<package pattern="Servarr.FluentMigrator*"/>
|
||||||
|
</packageSource>
|
||||||
|
</packageSourceMapping>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace NzbDrone.Automation.Test
|
|||||||
|
|
||||||
driver.Manage().Window.Size = new System.Drawing.Size(1920, 1080);
|
driver.Manage().Window.Size = new System.Drawing.Size(1920, 1080);
|
||||||
|
|
||||||
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger());
|
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), null);
|
||||||
_runner.KillAll();
|
_runner.KillAll();
|
||||||
_runner.Start();
|
_runner.Start();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.IO.Abstractions;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
@@ -10,6 +11,12 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
public abstract class DiskProviderFixtureBase<TSubject> : TestBase<TSubject>
|
public abstract class DiskProviderFixtureBase<TSubject> : TestBase<TSubject>
|
||||||
where TSubject : class, IDiskProvider
|
where TSubject : class, IDiskProvider
|
||||||
{
|
{
|
||||||
|
[SetUp]
|
||||||
|
public void BaseSetup()
|
||||||
|
{
|
||||||
|
Mocker.SetConstant<IFileSystem>(new FileSystem());
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void writealltext_should_truncate_existing()
|
public void writealltext_should_truncate_existing()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.IO.Abstractions;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
@@ -10,6 +11,12 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
public abstract class FreeSpaceFixtureBase<TSubject> : TestBase<TSubject>
|
public abstract class FreeSpaceFixtureBase<TSubject> : TestBase<TSubject>
|
||||||
where TSubject : class, IDiskProvider
|
where TSubject : class, IDiskProvider
|
||||||
{
|
{
|
||||||
|
[SetUp]
|
||||||
|
public void BaseSetup()
|
||||||
|
{
|
||||||
|
Mocker.SetConstant<IFileSystem>(new FileSystem());
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_get_free_space_for_folder()
|
public void should_get_free_space_for_folder()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Instrumentation;
|
using NzbDrone.Common.Instrumentation;
|
||||||
|
|
||||||
@@ -60,6 +60,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
|||||||
[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")]
|
[TestCase("/readarr/signalr/messages/negotiate?access_token=1234530f422f4aacb6b301233210aaaa&negotiateVersion=1")]
|
||||||
|
[TestCase(@"[Info] MigrationController: *** Migrating Database=readarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;Enlist=False ***")]
|
||||||
|
|
||||||
// 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""}")]
|
||||||
@@ -70,6 +71,11 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
|||||||
[TestCase(@"tracker"":""https://xxx.yyy/tracker.php/9pr04sg601233210imaveql2tyu8xyui/announce""}")]
|
[TestCase(@"tracker"":""https://xxx.yyy/tracker.php/9pr04sg601233210imaveql2tyu8xyui/announce""}")]
|
||||||
[TestCase(@"tracker"":""https://xxx.yyy/announce/9pr04sg601233210imaveql2tyu8xyui""}")]
|
[TestCase(@"tracker"":""https://xxx.yyy/announce/9pr04sg601233210imaveql2tyu8xyui""}")]
|
||||||
[TestCase(@"tracker"":""https://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui""}")]
|
[TestCase(@"tracker"":""https://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui""}")]
|
||||||
|
[TestCase(@"tracker"":""http://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui"",""info"":""http://xxx.yyy/info?a=b""")]
|
||||||
|
|
||||||
|
// Webhooks - Notifiarr
|
||||||
|
[TestCase(@"https://xxx.yyy/api/v1/notification/readarr/9pr04sg6-0123-3210-imav-eql2tyu8xyui")]
|
||||||
|
|
||||||
public void should_clean_message(string message)
|
public void should_clean_message(string message)
|
||||||
{
|
{
|
||||||
var cleansedMessage = CleanseLogMessage.Cleanse(message);
|
var cleansedMessage = CleanseLogMessage.Cleanse(message);
|
||||||
@@ -80,6 +86,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
|||||||
|
|
||||||
//GoodReads
|
//GoodReads
|
||||||
[TestCase(@"{""signatureMethod"": ""hmacSha1"",""signatureTreatment"": ""escaped"",""type"": ""protectedResource"",""method"": ""GET"",""token"": ""mytoken"",""tokenSecret"": ""mytokensecret"",""requestUrl"": ""https://www.goodreads.com/review/list.xml"",""parameters"": { ""_nc"": ""1"", ""v"": ""2"", ""id"": ""999999999"", ""shelf"": ""currently-reading"", ""per_page"": ""200"", ""page"": ""1""}")]
|
[TestCase(@"{""signatureMethod"": ""hmacSha1"",""signatureTreatment"": ""escaped"",""type"": ""protectedResource"",""method"": ""GET"",""token"": ""mytoken"",""tokenSecret"": ""mytokensecret"",""requestUrl"": ""https://www.goodreads.com/review/list.xml"",""parameters"": { ""_nc"": ""1"", ""v"": ""2"", ""id"": ""999999999"", ""shelf"": ""currently-reading"", ""per_page"": ""200"", ""page"": ""1""}")]
|
||||||
|
[TestCase(@"https://www.goodreads.com/series/311911?key=1234530f422f4aacb6b301233210aaaa&_nc=1&format=xml")]
|
||||||
public void should_cleanGoodRead_message(string message)
|
public void should_cleanGoodRead_message(string message)
|
||||||
{
|
{
|
||||||
var cleansedMessage = CleanseLogMessage.Cleanse(message);
|
var cleansedMessage = CleanseLogMessage.Cleanse(message);
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ using DryIoc.Microsoft.DependencyInjection;
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Moq;
|
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;
|
||||||
using NzbDrone.Common.Instrumentation.Extensions;
|
using NzbDrone.Common.Instrumentation.Extensions;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Datastore.Extensions;
|
using NzbDrone.Core.Datastore.Extensions;
|
||||||
using NzbDrone.Core.Lifecycle;
|
using NzbDrone.Core.Lifecycle;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
@@ -29,7 +31,8 @@ namespace NzbDrone.Common.Test
|
|||||||
.AddDummyDatabase()
|
.AddDummyDatabase()
|
||||||
.AddStartupContext(new StartupContext("first", "second"));
|
.AddStartupContext(new StartupContext("first", "second"));
|
||||||
|
|
||||||
container.RegisterInstance<IHostLifetime>(new Mock<IHostLifetime>().Object);
|
container.RegisterInstance(new Mock<IHostLifetime>().Object);
|
||||||
|
container.RegisterInstance(new Mock<IOptions<PostgresOptions>>().Object);
|
||||||
|
|
||||||
var serviceProvider = container.GetServiceProvider();
|
var serviceProvider = container.GetServiceProvider();
|
||||||
serviceProvider.GetRequiredService<IAppFolderFactory>().Register();
|
serviceProvider.GetRequiredService<IAppFolderFactory>().Register();
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ namespace NzbDrone.Common.Http
|
|||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
var result = string.Format("Res: [{0}] {1}: {2}.{3}", Request.Method, Request.Url, (int)StatusCode, StatusCode);
|
var result = string.Format("Res: [{0}] {1}: {2}.{3} ({4} bytes)", Request.Method, Request.Url, (int)StatusCode, StatusCode, ResponseData?.Length ?? 0);
|
||||||
|
|
||||||
if (HasHttpError && Headers.ContentType.IsNotNullOrWhiteSpace() && !Headers.ContentType.Equals("text/html", StringComparison.InvariantCultureIgnoreCase))
|
if (HasHttpError && Headers.ContentType.IsNotNullOrWhiteSpace() && !Headers.ContentType.Equals("text/html", StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
@@ -11,13 +11,14 @@ namespace NzbDrone.Common.Instrumentation
|
|||||||
private static readonly Regex[] CleansingRules = new[]
|
private static readonly Regex[] CleansingRules = new[]
|
||||||
{
|
{
|
||||||
// Url
|
// Url
|
||||||
new Regex(@"(?<=\?|&|: )(apikey|(?:access[-_]?)?token|passkey|auth|authkey|user|uid|api|[a-z_]*apikey|account|passwd)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
new Regex(@"(?<=\?|&|: )((?:api|auth|pass)?key|(?:access[-_]?)?token|auth|user|uid|api|[a-z_]*apikey|account|passwd)=(?<secret>[^&=""]+?)(?=[ ""&=]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||||
new Regex(@"(?<=\?|&)[^=]*?(username|password)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
new Regex(@"(?<=\?|&)[^=]*?(username|password)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||||
new Regex(@"torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
new Regex(@"torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||||
new Regex(@"torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
new Regex(@"torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||||
new Regex(@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
new Regex(@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||||
new Regex(@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
|
new Regex(@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
|
||||||
new Regex(@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
new Regex(@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||||
|
new Regex(@"(?<=[?& ;])[^=]*?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||||
|
|
||||||
// Trackers Announce Keys; Designed for Qbit Json; should work for all in theory
|
// Trackers Announce Keys; Designed for Qbit Json; should work for all in theory
|
||||||
new Regex(@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
|
new Regex(@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
|
||||||
@@ -46,7 +47,11 @@ namespace NzbDrone.Common.Instrumentation
|
|||||||
new Regex(@"(?<=\?|&)(authkey|torrent_pass)=(?<secret>[^&=]+?)(?=""|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
new Regex(@"(?<=\?|&)(authkey|torrent_pass)=(?<secret>[^&=]+?)(?=""|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||||
|
|
||||||
// Good Reads
|
// Good Reads
|
||||||
new Regex(@"(?<=""(token|tokensecret)"":\s)""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase)
|
new Regex(@"(?<=""(token|tokensecret)"":\s)""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||||
|
|
||||||
|
// Webhooks
|
||||||
|
// Notifiarr
|
||||||
|
new Regex(@"api/v[0-9]/notification/readarr/(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase)
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly Regex CleanseRemoteIPRegex = new Regex(@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);
|
private static readonly Regex CleanseRemoteIPRegex = new Regex(@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);
|
||||||
|
|||||||
@@ -127,7 +127,18 @@ namespace NzbDrone.Common.Processes
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.Trace("Setting environment variable '{0}' to '{1}'", environmentVariable.Key, environmentVariable.Value);
|
_logger.Trace("Setting environment variable '{0}' to '{1}'", environmentVariable.Key, environmentVariable.Value);
|
||||||
startInfo.EnvironmentVariables.Add(environmentVariable.Key.ToString(), environmentVariable.Value.ToString());
|
|
||||||
|
var key = environmentVariable.Key.ToString();
|
||||||
|
var value = environmentVariable.Value?.ToString();
|
||||||
|
|
||||||
|
if (startInfo.EnvironmentVariables.ContainsKey(key))
|
||||||
|
{
|
||||||
|
startInfo.EnvironmentVariables[key] = value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
startInfo.EnvironmentVariables.Add(key, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace NzbDrone.Core.Test.Datastore
|
|||||||
public void SingleOrDefault_should_return_null_on_empty_db()
|
public void SingleOrDefault_should_return_null_on_empty_db()
|
||||||
{
|
{
|
||||||
Mocker.Resolve<IDatabase>()
|
Mocker.Resolve<IDatabase>()
|
||||||
.OpenConnection().Query<Author>("SELECT * FROM Authors")
|
.OpenConnection().Query<Author>("SELECT * FROM \"Authors\"")
|
||||||
.SingleOrDefault(c => c.CleanName == "SomeTitle")
|
.SingleOrDefault(c => c.CleanName == "SomeTitle")
|
||||||
.Should()
|
.Should()
|
||||||
.BeNull();
|
.BeNull();
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ namespace NzbDrone.Core.Test.Datastore
|
|||||||
public void should_lazy_load_author_for_trackfile()
|
public void should_lazy_load_author_for_trackfile()
|
||||||
{
|
{
|
||||||
var db = Mocker.Resolve<IDatabase>();
|
var db = Mocker.Resolve<IDatabase>();
|
||||||
var tracks = db.Query<BookFile>(new SqlBuilder()).ToList();
|
var tracks = db.Query<BookFile>(new SqlBuilder(db.DatabaseType)).ToList();
|
||||||
|
|
||||||
Assert.IsNotEmpty(tracks);
|
Assert.IsNotEmpty(tracks);
|
||||||
foreach (var track in tracks)
|
foreach (var track in tracks)
|
||||||
@@ -94,7 +94,7 @@ namespace NzbDrone.Core.Test.Datastore
|
|||||||
public void should_lazy_load_trackfile_if_not_joined()
|
public void should_lazy_load_trackfile_if_not_joined()
|
||||||
{
|
{
|
||||||
var db = Mocker.Resolve<IDatabase>();
|
var db = Mocker.Resolve<IDatabase>();
|
||||||
var tracks = db.Query<Book>(new SqlBuilder()).ToList();
|
var tracks = db.Query<Book>(new SqlBuilder(db.DatabaseType)).ToList();
|
||||||
|
|
||||||
foreach (var track in tracks)
|
foreach (var track in tracks)
|
||||||
{
|
{
|
||||||
@@ -109,7 +109,7 @@ namespace NzbDrone.Core.Test.Datastore
|
|||||||
{
|
{
|
||||||
var db = Mocker.Resolve<IDatabase>();
|
var db = Mocker.Resolve<IDatabase>();
|
||||||
var files = MediaFileRepository.Query(db,
|
var files = MediaFileRepository.Query(db,
|
||||||
new SqlBuilder()
|
new SqlBuilder(db.DatabaseType)
|
||||||
.Join<BookFile, Edition>((t, a) => t.EditionId == a.Id)
|
.Join<BookFile, Edition>((t, a) => t.EditionId == a.Id)
|
||||||
.Join<Edition, Book>((e, b) => e.BookId == b.Id)
|
.Join<Edition, Book>((e, b) => e.BookId == b.Id)
|
||||||
.Join<Book, Author>((book, author) => book.AuthorMetadataId == author.AuthorMetadataId)
|
.Join<Book, Author>((book, author) => book.AuthorMetadataId == author.AuthorMetadataId)
|
||||||
|
|||||||
211
src/NzbDrone.Core.Test/Datastore/WhereBuilderPostgresFixture.cs
Normal file
211
src/NzbDrone.Core.Test/Datastore/WhereBuilderPostgresFixture.cs
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Books;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.Datastore
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class WhereBuilderPostgresFixture : CoreTest
|
||||||
|
{
|
||||||
|
private WhereBuilderPostgres _subject;
|
||||||
|
|
||||||
|
[OneTimeSetUp]
|
||||||
|
public void MapTables()
|
||||||
|
{
|
||||||
|
// Generate table mapping
|
||||||
|
Mocker.Resolve<DbFactory>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private WhereBuilderPostgres Where(Expression<Func<Author, bool>> filter)
|
||||||
|
{
|
||||||
|
return new WhereBuilderPostgres(filter, true, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private WhereBuilderPostgres WhereMetadata(Expression<Func<AuthorMetadata, bool>> filter)
|
||||||
|
{
|
||||||
|
return new WhereBuilderPostgres(filter, true, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void postgres_where_equal_const()
|
||||||
|
{
|
||||||
|
_subject = Where(x => x.Id == 10);
|
||||||
|
|
||||||
|
_subject.ToString().Should().Be($"(\"Authors\".\"Id\" = @Clause1_P1)");
|
||||||
|
_subject.Parameters.Get<int>("Clause1_P1").Should().Be(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void postgres_where_equal_variable()
|
||||||
|
{
|
||||||
|
var id = 10;
|
||||||
|
_subject = Where(x => x.Id == id);
|
||||||
|
|
||||||
|
_subject.ToString().Should().Be($"(\"Authors\".\"Id\" = @Clause1_P1)");
|
||||||
|
_subject.Parameters.Get<int>("Clause1_P1").Should().Be(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void postgres_where_equal_property()
|
||||||
|
{
|
||||||
|
var author = new Author { Id = 10 };
|
||||||
|
_subject = Where(x => x.Id == author.Id);
|
||||||
|
|
||||||
|
_subject.Parameters.ParameterNames.Should().HaveCount(1);
|
||||||
|
_subject.ToString().Should().Be($"(\"Authors\".\"Id\" = @Clause1_P1)");
|
||||||
|
_subject.Parameters.Get<int>("Clause1_P1").Should().Be(author.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void postgres_where_equal_joined_property()
|
||||||
|
{
|
||||||
|
_subject = Where(x => x.QualityProfile.Value.Id == 1);
|
||||||
|
|
||||||
|
_subject.Parameters.ParameterNames.Should().HaveCount(1);
|
||||||
|
_subject.ToString().Should().Be($"(\"QualityProfiles\".\"Id\" = @Clause1_P1)");
|
||||||
|
_subject.Parameters.Get<int>("Clause1_P1").Should().Be(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void postgres_where_throws_without_concrete_condition_if_requiresConcreteCondition()
|
||||||
|
{
|
||||||
|
Expression<Func<Author, Author, bool>> filter = (x, y) => x.Id == y.Id;
|
||||||
|
_subject = new WhereBuilderPostgres(filter, true, 0);
|
||||||
|
Assert.Throws<InvalidOperationException>(() => _subject.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void postgres_where_allows_abstract_condition_if_not_requiresConcreteCondition()
|
||||||
|
{
|
||||||
|
Expression<Func<Author, Author, bool>> filter = (x, y) => x.Id == y.Id;
|
||||||
|
_subject = new WhereBuilderPostgres(filter, false, 0);
|
||||||
|
_subject.ToString().Should().Be($"(\"Authors\".\"Id\" = \"Authors\".\"Id\")");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void postgres_where_string_is_null()
|
||||||
|
{
|
||||||
|
_subject = Where(x => x.CleanName == null);
|
||||||
|
|
||||||
|
_subject.ToString().Should().Be($"(\"Authors\".\"CleanName\" IS NULL)");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void postgres_where_string_is_null_value()
|
||||||
|
{
|
||||||
|
string cleanName = null;
|
||||||
|
_subject = Where(x => x.CleanName == cleanName);
|
||||||
|
|
||||||
|
_subject.ToString().Should().Be($"(\"Authors\".\"CleanName\" IS NULL)");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void postgres_where_equal_null_property()
|
||||||
|
{
|
||||||
|
var author = new Author { CleanName = null };
|
||||||
|
_subject = Where(x => x.CleanName == author.CleanName);
|
||||||
|
|
||||||
|
_subject.ToString().Should().Be($"(\"Authors\".\"CleanName\" IS NULL)");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void postgres_where_column_contains_string()
|
||||||
|
{
|
||||||
|
var test = "small";
|
||||||
|
_subject = Where(x => x.CleanName.Contains(test));
|
||||||
|
|
||||||
|
_subject.ToString().Should().Be($"(\"Authors\".\"CleanName\" ILIKE '%' || @Clause1_P1 || '%')");
|
||||||
|
_subject.Parameters.Get<string>("Clause1_P1").Should().Be(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void postgres_where_string_contains_column()
|
||||||
|
{
|
||||||
|
var test = "small";
|
||||||
|
_subject = Where(x => test.Contains(x.CleanName));
|
||||||
|
|
||||||
|
_subject.ToString().Should().Be($"(@Clause1_P1 ILIKE '%' || \"Authors\".\"CleanName\" || '%')");
|
||||||
|
_subject.Parameters.Get<string>("Clause1_P1").Should().Be(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void postgres_where_column_starts_with_string()
|
||||||
|
{
|
||||||
|
var test = "small";
|
||||||
|
_subject = Where(x => x.CleanName.StartsWith(test));
|
||||||
|
|
||||||
|
_subject.ToString().Should().Be($"(\"Authors\".\"CleanName\" ILIKE @Clause1_P1 || '%')");
|
||||||
|
_subject.Parameters.Get<string>("Clause1_P1").Should().Be(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void postgres_where_column_ends_with_string()
|
||||||
|
{
|
||||||
|
var test = "small";
|
||||||
|
_subject = Where(x => x.CleanName.EndsWith(test));
|
||||||
|
|
||||||
|
_subject.ToString().Should().Be($"(\"Authors\".\"CleanName\" ILIKE '%' || @Clause1_P1)");
|
||||||
|
_subject.Parameters.Get<string>("Clause1_P1").Should().Be(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void postgres_where_in_list()
|
||||||
|
{
|
||||||
|
var list = new List<int> { 1, 2, 3 };
|
||||||
|
_subject = Where(x => list.Contains(x.Id));
|
||||||
|
|
||||||
|
_subject.ToString().Should().Be($"(\"Authors\".\"Id\" = ANY (('{{1, 2, 3}}')))");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void postgres_where_in_list_2()
|
||||||
|
{
|
||||||
|
var list = new List<int> { 1, 2, 3 };
|
||||||
|
_subject = Where(x => x.CleanName == "test" && list.Contains(x.Id));
|
||||||
|
|
||||||
|
_subject.ToString().Should().Be($"((\"Authors\".\"CleanName\" = @Clause1_P1) AND (\"Authors\".\"Id\" = ANY (('{{1, 2, 3}}'))))");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void postgres_where_in_string_list()
|
||||||
|
{
|
||||||
|
var list = new List<string> { "first", "second", "third" };
|
||||||
|
|
||||||
|
_subject = Where(x => list.Contains(x.CleanName));
|
||||||
|
|
||||||
|
_subject.ToString().Should().Be($"(\"Authors\".\"CleanName\" = ANY (@Clause1_P1))");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void enum_as_int()
|
||||||
|
{
|
||||||
|
_subject = WhereMetadata(x => x.Status == AuthorStatusType.Continuing);
|
||||||
|
|
||||||
|
_subject.ToString().Should().Be($"(\"AuthorMetadata\".\"Status\" = @Clause1_P1)");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void enum_in_list()
|
||||||
|
{
|
||||||
|
var allowed = new List<AuthorStatusType> { AuthorStatusType.Continuing, AuthorStatusType.Ended };
|
||||||
|
_subject = WhereMetadata(x => allowed.Contains(x.Status));
|
||||||
|
|
||||||
|
_subject.ToString().Should().Be($"(\"AuthorMetadata\".\"Status\" = ANY (@Clause1_P1))");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void enum_in_array()
|
||||||
|
{
|
||||||
|
var allowed = new AuthorStatusType[] { AuthorStatusType.Continuing, AuthorStatusType.Ended };
|
||||||
|
_subject = WhereMetadata(x => allowed.Contains(x.Status));
|
||||||
|
|
||||||
|
_subject.ToString().Should().Be($"(\"AuthorMetadata\".\"Status\" = ANY (@Clause1_P1))");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,9 +11,9 @@ using NzbDrone.Core.Test.Framework;
|
|||||||
namespace NzbDrone.Core.Test.Datastore
|
namespace NzbDrone.Core.Test.Datastore
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class WhereBuilderFixture : CoreTest
|
public class WhereBuilderSqliteFixture : CoreTest
|
||||||
{
|
{
|
||||||
private WhereBuilder _subject;
|
private WhereBuilderSqlite _subject;
|
||||||
|
|
||||||
[OneTimeSetUp]
|
[OneTimeSetUp]
|
||||||
public void MapTables()
|
public void MapTables()
|
||||||
@@ -22,14 +22,14 @@ namespace NzbDrone.Core.Test.Datastore
|
|||||||
Mocker.Resolve<DbFactory>();
|
Mocker.Resolve<DbFactory>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private WhereBuilder Where(Expression<Func<Author, bool>> filter)
|
private WhereBuilderSqlite Where(Expression<Func<Author, bool>> filter)
|
||||||
{
|
{
|
||||||
return new WhereBuilder(filter, true, 0);
|
return new WhereBuilderSqlite(filter, true, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private WhereBuilder WhereMetadata(Expression<Func<AuthorMetadata, bool>> filter)
|
private WhereBuilderSqlite WhereMetadata(Expression<Func<AuthorMetadata, bool>> filter)
|
||||||
{
|
{
|
||||||
return new WhereBuilder(filter, true, 0);
|
return new WhereBuilderSqlite(filter, true, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -76,7 +76,7 @@ namespace NzbDrone.Core.Test.Datastore
|
|||||||
public void where_throws_without_concrete_condition_if_requiresConcreteCondition()
|
public void where_throws_without_concrete_condition_if_requiresConcreteCondition()
|
||||||
{
|
{
|
||||||
Expression<Func<Author, Author, bool>> filter = (x, y) => x.Id == y.Id;
|
Expression<Func<Author, Author, bool>> filter = (x, y) => x.Id == y.Id;
|
||||||
_subject = new WhereBuilder(filter, true, 0);
|
_subject = new WhereBuilderSqlite(filter, true, 0);
|
||||||
Assert.Throws<InvalidOperationException>(() => _subject.ToString());
|
Assert.Throws<InvalidOperationException>(() => _subject.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ namespace NzbDrone.Core.Test.Datastore
|
|||||||
public void where_allows_abstract_condition_if_not_requiresConcreteCondition()
|
public void where_allows_abstract_condition_if_not_requiresConcreteCondition()
|
||||||
{
|
{
|
||||||
Expression<Func<Author, Author, bool>> filter = (x, y) => x.Id == y.Id;
|
Expression<Func<Author, Author, bool>> filter = (x, y) => x.Id == y.Id;
|
||||||
_subject = new WhereBuilder(filter, false, 0);
|
_subject = new WhereBuilderSqlite(filter, false, 0);
|
||||||
_subject.ToString().Should().Be($"(\"Authors\".\"Id\" = \"Authors\".\"Id\")");
|
_subject.ToString().Should().Be($"(\"Authors\".\"Id\" = \"Authors\".\"Id\")");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,12 +1,18 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data.SQLite;
|
using System.Data.SQLite;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Npgsql;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
using NzbDrone.Test.Common.Datastore;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.Framework
|
namespace NzbDrone.Core.Test.Framework
|
||||||
{
|
{
|
||||||
@@ -47,6 +53,7 @@ namespace NzbDrone.Core.Test.Framework
|
|||||||
public abstract class DbTest : CoreTest
|
public abstract class DbTest : CoreTest
|
||||||
{
|
{
|
||||||
private ITestDatabase _db;
|
private ITestDatabase _db;
|
||||||
|
private DatabaseType _databaseType;
|
||||||
|
|
||||||
protected virtual MigrationType MigrationType => MigrationType.Main;
|
protected virtual MigrationType MigrationType => MigrationType.Main;
|
||||||
|
|
||||||
@@ -65,8 +72,7 @@ namespace NzbDrone.Core.Test.Framework
|
|||||||
|
|
||||||
protected virtual ITestDatabase WithTestDb(MigrationContext migrationContext)
|
protected virtual ITestDatabase WithTestDb(MigrationContext migrationContext)
|
||||||
{
|
{
|
||||||
var factory = Mocker.Resolve<DbFactory>();
|
var database = CreateDatabase(migrationContext);
|
||||||
var database = factory.Create(migrationContext);
|
|
||||||
Mocker.SetConstant(database);
|
Mocker.SetConstant(database);
|
||||||
|
|
||||||
switch (MigrationType)
|
switch (MigrationType)
|
||||||
@@ -98,6 +104,65 @@ namespace NzbDrone.Core.Test.Framework
|
|||||||
return testDb;
|
return testDb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IDatabase CreateDatabase(MigrationContext migrationContext)
|
||||||
|
{
|
||||||
|
if (_databaseType == DatabaseType.PostgreSQL)
|
||||||
|
{
|
||||||
|
CreatePostgresDb();
|
||||||
|
}
|
||||||
|
|
||||||
|
var factory = Mocker.Resolve<DbFactory>();
|
||||||
|
|
||||||
|
// If a special migration test or log migration then create new
|
||||||
|
if (migrationContext.BeforeMigration != null || _databaseType == DatabaseType.PostgreSQL)
|
||||||
|
{
|
||||||
|
return factory.Create(migrationContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateSqliteDatabase(factory, migrationContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreatePostgresDb()
|
||||||
|
{
|
||||||
|
var options = Mocker.Resolve<IOptions<PostgresOptions>>().Value;
|
||||||
|
PostgresDatabase.Create(options, MigrationType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DropPostgresDb()
|
||||||
|
{
|
||||||
|
var options = Mocker.Resolve<IOptions<PostgresOptions>>().Value;
|
||||||
|
PostgresDatabase.Drop(options, MigrationType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IDatabase CreateSqliteDatabase(IDbFactory factory, MigrationContext migrationContext)
|
||||||
|
{
|
||||||
|
// Otherwise try to use a cached migrated db
|
||||||
|
var cachedDb = SqliteDatabase.GetCachedDb(migrationContext.MigrationType);
|
||||||
|
var testDb = GetTestSqliteDb(migrationContext.MigrationType);
|
||||||
|
if (File.Exists(cachedDb))
|
||||||
|
{
|
||||||
|
TestLogger.Info($"Using cached initial database {cachedDb}");
|
||||||
|
File.Copy(cachedDb, testDb);
|
||||||
|
return factory.Create(migrationContext);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var db = factory.Create(migrationContext);
|
||||||
|
GC.Collect();
|
||||||
|
GC.WaitForPendingFinalizers();
|
||||||
|
SQLiteConnection.ClearAllPools();
|
||||||
|
|
||||||
|
TestLogger.Info("Caching database");
|
||||||
|
File.Copy(testDb, cachedDb);
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetTestSqliteDb(MigrationType type)
|
||||||
|
{
|
||||||
|
return type == MigrationType.Main ? TestFolderInfo.GetDatabase() : TestFolderInfo.GetLogDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual void SetupLogging()
|
protected virtual void SetupLogging()
|
||||||
{
|
{
|
||||||
Mocker.SetConstant<ILoggerProvider>(NullLoggerProvider.Instance);
|
Mocker.SetConstant<ILoggerProvider>(NullLoggerProvider.Instance);
|
||||||
@@ -108,6 +173,13 @@ namespace NzbDrone.Core.Test.Framework
|
|||||||
WithTempAsAppPath();
|
WithTempAsAppPath();
|
||||||
SetupLogging();
|
SetupLogging();
|
||||||
|
|
||||||
|
// populate the possible postgres options
|
||||||
|
var postgresOptions = PostgresDatabase.GetTestOptions();
|
||||||
|
_databaseType = postgresOptions.Host.IsNotNullOrWhiteSpace() ? DatabaseType.PostgreSQL : DatabaseType.SQLite;
|
||||||
|
|
||||||
|
// Set up remaining container services
|
||||||
|
Mocker.SetConstant(Options.Create(postgresOptions));
|
||||||
|
Mocker.SetConstant<IConfigFileProvider>(Mocker.Resolve<ConfigFileProvider>());
|
||||||
Mocker.SetConstant<IConnectionStringFactory>(Mocker.Resolve<ConnectionStringFactory>());
|
Mocker.SetConstant<IConnectionStringFactory>(Mocker.Resolve<ConnectionStringFactory>());
|
||||||
Mocker.SetConstant<IMigrationController>(Mocker.Resolve<MigrationController>());
|
Mocker.SetConstant<IMigrationController>(Mocker.Resolve<MigrationController>());
|
||||||
|
|
||||||
@@ -127,12 +199,19 @@ namespace NzbDrone.Core.Test.Framework
|
|||||||
// Make sure there are no lingering connections. (When this happens it means we haven't disposed something properly)
|
// Make sure there are no lingering connections. (When this happens it means we haven't disposed something properly)
|
||||||
GC.Collect();
|
GC.Collect();
|
||||||
GC.WaitForPendingFinalizers();
|
GC.WaitForPendingFinalizers();
|
||||||
|
|
||||||
SQLiteConnection.ClearAllPools();
|
SQLiteConnection.ClearAllPools();
|
||||||
|
NpgsqlConnection.ClearAllPools();
|
||||||
|
|
||||||
if (TestFolderInfo != null)
|
if (TestFolderInfo != null)
|
||||||
{
|
{
|
||||||
DeleteTempFolder(TestFolderInfo.AppDataFolder);
|
DeleteTempFolder(TestFolderInfo.AppDataFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_databaseType == DatabaseType.PostgreSQL)
|
||||||
|
{
|
||||||
|
DropPostgresDb();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
using System.IO.Abstractions.TestingHelpers;
|
using System.IO.Abstractions;
|
||||||
|
using System.IO.Abstractions.TestingHelpers;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using Unity.Resolution;
|
using NzbDrone.Test.Common.AutoMoq;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.Framework
|
namespace NzbDrone.Core.Test.Framework
|
||||||
{
|
{
|
||||||
@@ -14,12 +15,9 @@ namespace NzbDrone.Core.Test.Framework
|
|||||||
[SetUp]
|
[SetUp]
|
||||||
public void FileSystemTestSetup()
|
public void FileSystemTestSetup()
|
||||||
{
|
{
|
||||||
FileSystem = new MockFileSystem();
|
FileSystem = (MockFileSystem)Mocker.Resolve<IFileSystem>(FileSystemType.Mock);
|
||||||
|
|
||||||
DiskProvider = Mocker.Resolve<IDiskProvider>("ActualDiskProvider", new ResolverOverride[]
|
DiskProvider = Mocker.Resolve<IDiskProvider>(FileSystemType.Mock);
|
||||||
{
|
|
||||||
new ParameterOverride("fileSystem", FileSystem)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ namespace NzbDrone.Core.Test.Framework
|
|||||||
where T : ModelBase, new();
|
where T : ModelBase, new();
|
||||||
IDirectDataMapper GetDirectDataMapper();
|
IDirectDataMapper GetDirectDataMapper();
|
||||||
IDbConnection OpenConnection();
|
IDbConnection OpenConnection();
|
||||||
|
DatabaseType DatabaseType { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestDatabase : ITestDatabase
|
public class TestDatabase : ITestDatabase
|
||||||
@@ -30,6 +31,8 @@ namespace NzbDrone.Core.Test.Framework
|
|||||||
private readonly IDatabase _dbConnection;
|
private readonly IDatabase _dbConnection;
|
||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
|
|
||||||
|
public DatabaseType DatabaseType => _dbConnection.DatabaseType;
|
||||||
|
|
||||||
public TestDatabase(IDatabase dbConnection)
|
public TestDatabase(IDatabase dbConnection)
|
||||||
{
|
{
|
||||||
_eventAggregator = new Mock<IEventAggregator>().Object;
|
_eventAggregator = new Mock<IEventAggregator>().Object;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Abstractions;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
@@ -133,6 +135,13 @@ namespace NzbDrone.Core.Test.MediaCoverTests
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "Media", "NonExistant.mp4");
|
||||||
|
var fileInfo = new FileInfo(path);
|
||||||
|
|
||||||
|
Mocker.GetMock<IDiskProvider>()
|
||||||
|
.Setup(c => c.GetFileInfo(It.IsAny<string>()))
|
||||||
|
.Returns((FileInfoBase)fileInfo);
|
||||||
|
|
||||||
Subject.ConvertToLocalUrls(12, MediaCoverEntity.Author, covers);
|
Subject.ConvertToLocalUrls(12, MediaCoverEntity.Author, covers);
|
||||||
|
|
||||||
covers.Single().Url.Should().Be("/MediaCover/12/banner" + extension);
|
covers.Single().Url.Should().Be("/MediaCover/12/banner" + extension);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ using NzbDrone.Core.Configuration;
|
|||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Test.Common;
|
using NzbDrone.Test.Common;
|
||||||
|
using NzbDrone.Test.Common.AutoMoq;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.MediaFiles.AudioTagServiceFixture
|
namespace NzbDrone.Core.Test.MediaFiles.AudioTagServiceFixture
|
||||||
{
|
{
|
||||||
@@ -55,9 +56,7 @@ namespace NzbDrone.Core.Test.MediaFiles.AudioTagServiceFixture
|
|||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
_diskProvider = Mocker.Resolve<IDiskProvider>("ActualDiskProvider");
|
_diskProvider = Mocker.Resolve<IDiskProvider>(FileSystemType.Actual);
|
||||||
|
|
||||||
Mocker.SetConstant<IDiskProvider>(_diskProvider);
|
|
||||||
|
|
||||||
Mocker.GetMock<IConfigService>()
|
Mocker.GetMock<IConfigService>()
|
||||||
.Setup(x => x.WriteAudioTags)
|
.Setup(x => x.WriteAudioTags)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("Harry Potter and the sorcerer's stone", 3)]
|
[TestCase("Harry Potter and the sorcerer's stone", 3)]
|
||||||
[TestCase("B0192CTMYG", 42844155)]
|
[TestCase("B0192CTMYG", 61209488)]
|
||||||
[TestCase("9780439554930", 48517161)]
|
[TestCase("9780439554930", 48517161)]
|
||||||
public void successful_book_search(string title, int expected)
|
public void successful_book_search(string title, int expected)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
|
using FluentAssertions.Equivalency;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.Books;
|
using NzbDrone.Core.Books;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.MusicTests.BookRepositoryTests
|
namespace NzbDrone.Core.Test.MusicTests.BookRepositoryTests
|
||||||
@@ -21,6 +23,13 @@ namespace NzbDrone.Core.Test.MusicTests.BookRepositoryTests
|
|||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
|
AssertionOptions.AssertEquivalencyUsing(options =>
|
||||||
|
{
|
||||||
|
options.Using<DateTime>(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation.ToUniversalTime())).WhenTypeIs<DateTime>();
|
||||||
|
options.Using<DateTime?>(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation.Value.ToUniversalTime())).WhenTypeIs<DateTime?>();
|
||||||
|
return options;
|
||||||
|
});
|
||||||
|
|
||||||
_author = new Author
|
_author = new Author
|
||||||
{
|
{
|
||||||
Name = "Alien Ant Farm",
|
Name = "Alien Ant Farm",
|
||||||
@@ -143,7 +152,7 @@ namespace NzbDrone.Core.Test.MusicTests.BookRepositoryTests
|
|||||||
GivenMultipleBooks();
|
GivenMultipleBooks();
|
||||||
|
|
||||||
var result = _bookRepo.GetNextBooks(new[] { _author.AuthorMetadataId });
|
var result = _bookRepo.GetNextBooks(new[] { _author.AuthorMetadataId });
|
||||||
result.Should().BeEquivalentTo(_books.Take(1));
|
result.Should().BeEquivalentTo(_books.Take(1), BookComparerOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -152,7 +161,11 @@ namespace NzbDrone.Core.Test.MusicTests.BookRepositoryTests
|
|||||||
GivenMultipleBooks();
|
GivenMultipleBooks();
|
||||||
|
|
||||||
var result = _bookRepo.GetLastBooks(new[] { _author.AuthorMetadataId });
|
var result = _bookRepo.GetLastBooks(new[] { _author.AuthorMetadataId });
|
||||||
result.Should().BeEquivalentTo(_books.Skip(2).Take(1));
|
result.Should().BeEquivalentTo(_books.Skip(2).Take(1), BookComparerOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private EquivalencyAssertionOptions<Book> BookComparerOptions(EquivalencyAssertionOptions<Book> opts) => opts.ComparingByMembers<Book>()
|
||||||
|
.Excluding(ctx => ctx.SelectedMemberInfo.MemberType.IsGenericType && ctx.SelectedMemberInfo.MemberType.GetGenericTypeDefinition() == typeof(LazyLoaded<>))
|
||||||
|
.Excluding(x => x.AuthorId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ using System.Collections.Generic;
|
|||||||
using System.Data.SQLite;
|
using System.Data.SQLite;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
|
using Npgsql;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Books;
|
using NzbDrone.Core.Books;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Profiles.Metadata;
|
using NzbDrone.Core.Profiles.Metadata;
|
||||||
using NzbDrone.Core.Profiles.Qualities;
|
using NzbDrone.Core.Profiles.Qualities;
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
@@ -145,7 +147,14 @@ namespace NzbDrone.Core.Test.MusicTests.AuthorRepositoryTests
|
|||||||
_authorRepo.Insert(author1);
|
_authorRepo.Insert(author1);
|
||||||
|
|
||||||
Action insertDupe = () => _authorRepo.Insert(author2);
|
Action insertDupe = () => _authorRepo.Insert(author2);
|
||||||
insertDupe.Should().Throw<SQLiteException>();
|
if (Db.DatabaseType == DatabaseType.PostgreSQL)
|
||||||
|
{
|
||||||
|
insertDupe.Should().Throw<PostgresException>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
insertDupe.Should().Throw<SQLiteException>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ namespace NzbDrone.Core.Test.MusicTests
|
|||||||
private void GivenBooksForRefresh(List<Book> books)
|
private void GivenBooksForRefresh(List<Book> books)
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IBookService>(MockBehavior.Strict)
|
Mocker.GetMock<IBookService>(MockBehavior.Strict)
|
||||||
.Setup(s => s.GetBooksForRefresh(It.IsAny<int>(), It.IsAny<IEnumerable<string>>()))
|
.Setup(s => s.GetBooksForRefresh(It.IsAny<int>(), It.IsAny<List<string>>()))
|
||||||
.Returns(books);
|
.Returns(books);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +238,7 @@ namespace NzbDrone.Core.Test.MusicTests
|
|||||||
|
|
||||||
Mocker.GetMock<IBookService>(MockBehavior.Strict)
|
Mocker.GetMock<IBookService>(MockBehavior.Strict)
|
||||||
.InSequence(seq)
|
.InSequence(seq)
|
||||||
.Setup(x => x.GetBooksForRefresh(It.IsAny<int>(), It.IsAny<IEnumerable<string>>()))
|
.Setup(x => x.GetBooksForRefresh(It.IsAny<int>(), It.IsAny<List<string>>()))
|
||||||
.Returns(new List<Book>());
|
.Returns(new List<Book>());
|
||||||
|
|
||||||
// Update called twice for a move/merge
|
// Update called twice for a move/merge
|
||||||
@@ -298,7 +298,7 @@ namespace NzbDrone.Core.Test.MusicTests
|
|||||||
|
|
||||||
Mocker.GetMock<IBookService>(MockBehavior.Strict)
|
Mocker.GetMock<IBookService>(MockBehavior.Strict)
|
||||||
.InSequence(seq)
|
.InSequence(seq)
|
||||||
.Setup(x => x.GetBooksForRefresh(clash.AuthorMetadataId, It.IsAny<IEnumerable<string>>()))
|
.Setup(x => x.GetBooksForRefresh(clash.AuthorMetadataId, It.IsAny<List<string>>()))
|
||||||
.Returns(_books);
|
.Returns(_books);
|
||||||
|
|
||||||
// Update called twice for a move/merge
|
// Update called twice for a move/merge
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ namespace NzbDrone.Core.Test.UpdateTests
|
|||||||
[Test]
|
[Test]
|
||||||
public void finds_update_when_version_lower()
|
public void finds_update_when_version_lower()
|
||||||
{
|
{
|
||||||
NotBsd();
|
|
||||||
UseRealHttp();
|
UseRealHttp();
|
||||||
Subject.GetLatestUpdate("nightly", new Version(0, 1)).Should().NotBeNull();
|
Subject.GetLatestUpdate("nightly", new Version(0, 1)).Should().NotBeNull();
|
||||||
}
|
}
|
||||||
@@ -43,8 +42,6 @@ namespace NzbDrone.Core.Test.UpdateTests
|
|||||||
[Test]
|
[Test]
|
||||||
public void should_get_recent_updates()
|
public void should_get_recent_updates()
|
||||||
{
|
{
|
||||||
NotBsd();
|
|
||||||
|
|
||||||
const string branch = "nightly";
|
const string branch = "nightly";
|
||||||
UseRealHttp();
|
UseRealHttp();
|
||||||
var recent = Subject.GetRecentUpdates(branch, new Version(0, 1));
|
var recent = Subject.GetRecentUpdates(branch, new Version(0, 1));
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace NzbDrone.Core.AuthorStats
|
|||||||
|
|
||||||
public class AuthorStatisticsRepository : IAuthorStatisticsRepository
|
public class AuthorStatisticsRepository : IAuthorStatisticsRepository
|
||||||
{
|
{
|
||||||
private const string _selectTemplate = "SELECT /**select**/ FROM Editions /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/";
|
private const string _selectTemplate = "SELECT /**select**/ FROM \"Editions\" /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/";
|
||||||
|
|
||||||
private readonly IMainDatabase _database;
|
private readonly IMainDatabase _database;
|
||||||
|
|
||||||
@@ -45,14 +45,14 @@ namespace NzbDrone.Core.AuthorStats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SqlBuilder Builder() => new SqlBuilder()
|
private SqlBuilder Builder() => new SqlBuilder(_database.DatabaseType)
|
||||||
.Select(@"Authors.Id AS AuthorId,
|
.Select(@"""Authors"".""Id"" AS ""AuthorId"",
|
||||||
Books.Id AS BookId,
|
""Books"".""Id"" AS ""BookId"",
|
||||||
SUM(COALESCE(BookFiles.Size, 0)) AS SizeOnDisk,
|
SUM(COALESCE(""BookFiles"".""Size"", 0)) AS ""SizeOnDisk"",
|
||||||
1 AS TotalBookCount,
|
1 AS ""TotalBookCount"",
|
||||||
CASE WHEN BookFiles.Id IS NULL THEN 0 ELSE 1 END AS AvailableBookCount,
|
CASE WHEN MIN(""BookFiles"".""Id"") IS NULL THEN 0 ELSE 1 END AS ""AvailableBookCount"",
|
||||||
CASE WHEN (Books.Monitored = 1 AND (Books.ReleaseDate < @currentDate) OR Books.ReleaseDate IS NULL) OR BookFiles.Id IS NOT NULL THEN 1 ELSE 0 END AS BookCount,
|
CASE WHEN (""Books"".""Monitored"" = true AND (""Books"".""ReleaseDate"" < @currentDate) OR ""Books"".""ReleaseDate"" IS NULL) OR MIN(""BookFiles"".""Id"") IS NOT NULL THEN 1 ELSE 0 END AS ""BookCount"",
|
||||||
CASE WHEN BookFiles.Id IS NULL THEN 0 ELSE COUNT(BookFiles.Id) END AS BookFileCount")
|
CASE WHEN MIN(""BookFiles"".""Id"") IS NULL THEN 0 ELSE COUNT(""BookFiles"".""Id"") END AS ""BookFileCount""")
|
||||||
.Join<Edition, Book>((e, b) => e.BookId == b.Id)
|
.Join<Edition, Book>((e, b) => e.BookId == b.Id)
|
||||||
.Join<Book, Author>((book, author) => book.AuthorMetadataId == author.AuthorMetadataId)
|
.Join<Book, Author>((book, author) => book.AuthorMetadataId == author.AuthorMetadataId)
|
||||||
.LeftJoin<Edition, BookFile>((t, f) => t.Id == f.EditionId)
|
.LeftJoin<Edition, BookFile>((t, f) => t.Id == f.EditionId)
|
||||||
|
|||||||
@@ -15,12 +15,14 @@ namespace NzbDrone.Core.AuthorStats
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class AuthorStatisticsService : IAuthorStatisticsService,
|
public class AuthorStatisticsService : IAuthorStatisticsService,
|
||||||
|
IHandle<AuthorAddedEvent>,
|
||||||
IHandle<AuthorUpdatedEvent>,
|
IHandle<AuthorUpdatedEvent>,
|
||||||
IHandle<AuthorDeletedEvent>,
|
IHandle<AuthorDeletedEvent>,
|
||||||
IHandle<BookAddedEvent>,
|
IHandle<BookAddedEvent>,
|
||||||
IHandle<BookDeletedEvent>,
|
IHandle<BookDeletedEvent>,
|
||||||
IHandle<BookImportedEvent>,
|
IHandle<BookImportedEvent>,
|
||||||
IHandle<BookEditedEvent>,
|
IHandle<BookEditedEvent>,
|
||||||
|
IHandle<BookUpdatedEvent>,
|
||||||
IHandle<BookFileDeletedEvent>
|
IHandle<BookFileDeletedEvent>
|
||||||
{
|
{
|
||||||
private readonly IAuthorStatisticsRepository _authorStatisticsRepository;
|
private readonly IAuthorStatisticsRepository _authorStatisticsRepository;
|
||||||
@@ -68,6 +70,13 @@ namespace NzbDrone.Core.AuthorStats
|
|||||||
return authorStatistics;
|
return authorStatistics;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[EventHandleOrder(EventHandleOrder.First)]
|
||||||
|
public void Handle(AuthorAddedEvent message)
|
||||||
|
{
|
||||||
|
_cache.Remove("AllAuthors");
|
||||||
|
_cache.Remove(message.Author.Id.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
[EventHandleOrder(EventHandleOrder.First)]
|
[EventHandleOrder(EventHandleOrder.First)]
|
||||||
public void Handle(AuthorUpdatedEvent message)
|
public void Handle(AuthorUpdatedEvent message)
|
||||||
{
|
{
|
||||||
@@ -110,6 +119,13 @@ namespace NzbDrone.Core.AuthorStats
|
|||||||
_cache.Remove(message.Book.AuthorId.ToString());
|
_cache.Remove(message.Book.AuthorId.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[EventHandleOrder(EventHandleOrder.First)]
|
||||||
|
public void Handle(BookUpdatedEvent message)
|
||||||
|
{
|
||||||
|
_cache.Remove("AllAuthors");
|
||||||
|
_cache.Remove(message.Book.AuthorId.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
[EventHandleOrder(EventHandleOrder.First)]
|
[EventHandleOrder(EventHandleOrder.First)]
|
||||||
public void Handle(BookFileDeletedEvent message)
|
public void Handle(BookFileDeletedEvent message)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ namespace NzbDrone.Core.Backup
|
|||||||
|
|
||||||
_archiveService.CreateZip(backupPath, _diskProvider.GetFiles(_backupTempFolder, SearchOption.TopDirectoryOnly));
|
_archiveService.CreateZip(backupPath, _diskProvider.GetFiles(_backupTempFolder, SearchOption.TopDirectoryOnly));
|
||||||
|
|
||||||
|
Cleanup();
|
||||||
|
|
||||||
_logger.ProgressDebug("Backup zip created");
|
_logger.ProgressDebug("Backup zip created");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,9 +183,12 @@ namespace NzbDrone.Core.Backup
|
|||||||
|
|
||||||
private void BackupDatabase()
|
private void BackupDatabase()
|
||||||
{
|
{
|
||||||
_logger.ProgressDebug("Backing up database");
|
if (_maindDb.DatabaseType == DatabaseType.SQLite)
|
||||||
|
{
|
||||||
|
_logger.ProgressDebug("Backing up database");
|
||||||
|
|
||||||
_makeDatabaseBackup.BackupDatabase(_maindDb, _backupTempFolder);
|
_makeDatabaseBackup.BackupDatabase(_maindDb, _backupTempFolder);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BackupConfigFile()
|
private void BackupConfigFile()
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Blocklisting
|
|||||||
return Query(b => b.AuthorId == authorId);
|
return Query(b => b.AuthorId == authorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SqlBuilder PagedBuilder() => new SqlBuilder()
|
protected override SqlBuilder PagedBuilder() => new SqlBuilder(_database.DatabaseType)
|
||||||
.Join<Blocklist, Author>((b, m) => b.AuthorId == m.Id)
|
.Join<Blocklist, Author>((b, m) => b.AuthorId == m.Id)
|
||||||
.Join<Author, AuthorMetadata>((l, r) => l.AuthorMetadataId == r.Id);
|
.Join<Author, AuthorMetadata>((l, r) => l.AuthorMetadataId == r.Id);
|
||||||
protected override IEnumerable<Blocklist> PagedQuery(SqlBuilder builder) => _database.QueryJoined<Blocklist, Author, AuthorMetadata>(builder,
|
protected override IEnumerable<Blocklist> PagedQuery(SqlBuilder builder) => _database.QueryJoined<Blocklist, Author, AuthorMetadata>(builder,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ namespace NzbDrone.Core.Books
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SqlBuilder Builder() => new SqlBuilder()
|
protected override SqlBuilder Builder() => new SqlBuilder(_database.DatabaseType)
|
||||||
.Join<Author, AuthorMetadata>((a, m) => a.AuthorMetadataId == m.Id);
|
.Join<Author, AuthorMetadata>((a, m) => a.AuthorMetadataId == m.Id);
|
||||||
|
|
||||||
protected override List<Author> Query(SqlBuilder builder) => Query(_database, builder).ToList();
|
protected override List<Author> Query(SqlBuilder builder) => Query(_database, builder).ToList();
|
||||||
@@ -60,7 +60,7 @@ namespace NzbDrone.Core.Books
|
|||||||
{
|
{
|
||||||
using (var conn = _database.OpenConnection())
|
using (var conn = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
var strSql = "SELECT Id AS [Key], Path AS [Value] FROM Authors";
|
var strSql = "SELECT \"Id\" AS \"Key\", \"Path\" AS \"Value\" FROM \"Authors\"";
|
||||||
return conn.Query<KeyValuePair<int, string>>(strSql).ToDictionary(x => x.Key, x => x.Value);
|
return conn.Query<KeyValuePair<int, string>>(strSql).ToDictionary(x => x.Key, x => x.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace NzbDrone.Core.Books
|
|||||||
List<Book> GetLastBooks(IEnumerable<int> authorMetadataIds);
|
List<Book> GetLastBooks(IEnumerable<int> authorMetadataIds);
|
||||||
List<Book> GetNextBooks(IEnumerable<int> authorMetadataIds);
|
List<Book> GetNextBooks(IEnumerable<int> authorMetadataIds);
|
||||||
List<Book> GetBooksByAuthorMetadataId(int authorMetadataId);
|
List<Book> GetBooksByAuthorMetadataId(int authorMetadataId);
|
||||||
List<Book> GetBooksForRefresh(int authorMetadataId, IEnumerable<string> foreignIds);
|
List<Book> GetBooksForRefresh(int authorMetadataId, List<string> foreignIds);
|
||||||
List<Book> GetBooksByFileIds(IEnumerable<int> fileIds);
|
List<Book> GetBooksByFileIds(IEnumerable<int> fileIds);
|
||||||
Book FindByTitle(int authorMetadataId, string title);
|
Book FindByTitle(int authorMetadataId, string title);
|
||||||
Book FindById(string foreignBookId);
|
Book FindById(string foreignBookId);
|
||||||
@@ -44,17 +44,35 @@ namespace NzbDrone.Core.Books
|
|||||||
public List<Book> GetLastBooks(IEnumerable<int> authorMetadataIds)
|
public List<Book> GetLastBooks(IEnumerable<int> authorMetadataIds)
|
||||||
{
|
{
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
return Query(Builder().Where<Book>(x => authorMetadataIds.Contains(x.AuthorMetadataId) && x.ReleaseDate < now)
|
|
||||||
.GroupBy<Book>(x => x.AuthorMetadataId)
|
var inner = Builder()
|
||||||
.Having("Books.ReleaseDate = MAX(Books.ReleaseDate)"));
|
.Select("MIN(\"Books\".\"Id\") as id, MAX(\"Books\".\"ReleaseDate\") as date")
|
||||||
|
.Where<Book>(x => authorMetadataIds.Contains(x.AuthorMetadataId) && x.ReleaseDate < now)
|
||||||
|
.GroupBy<Book>(x => x.AuthorMetadataId)
|
||||||
|
.AddSelectTemplate(typeof(Book));
|
||||||
|
|
||||||
|
var outer = Builder()
|
||||||
|
.Join($"({inner.RawSql}) ids on ids.id = \"Books\".\"Id\" and ids.date = \"Books\".\"ReleaseDate\"")
|
||||||
|
.AddParameters(inner.Parameters);
|
||||||
|
|
||||||
|
return Query(outer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Book> GetNextBooks(IEnumerable<int> authorMetadataIds)
|
public List<Book> GetNextBooks(IEnumerable<int> authorMetadataIds)
|
||||||
{
|
{
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
return Query(Builder().Where<Book>(x => authorMetadataIds.Contains(x.AuthorMetadataId) && x.ReleaseDate > now)
|
|
||||||
.GroupBy<Book>(x => x.AuthorMetadataId)
|
var inner = Builder()
|
||||||
.Having("Books.ReleaseDate = MIN(Books.ReleaseDate)"));
|
.Select("MIN(\"Books\".\"Id\") as id, MIN(\"Books\".\"ReleaseDate\") as date")
|
||||||
|
.Where<Book>(x => authorMetadataIds.Contains(x.AuthorMetadataId) && x.ReleaseDate > now)
|
||||||
|
.GroupBy<Book>(x => x.AuthorMetadataId)
|
||||||
|
.AddSelectTemplate(typeof(Book));
|
||||||
|
|
||||||
|
var outer = Builder()
|
||||||
|
.Join($"({inner.RawSql}) ids on ids.id = \"Books\".\"Id\" and ids.date = \"Books\".\"ReleaseDate\"")
|
||||||
|
.AddParameters(inner.Parameters);
|
||||||
|
|
||||||
|
return Query(outer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Book> GetBooksByAuthorMetadataId(int authorMetadataId)
|
public List<Book> GetBooksByAuthorMetadataId(int authorMetadataId)
|
||||||
@@ -62,14 +80,14 @@ namespace NzbDrone.Core.Books
|
|||||||
return Query(s => s.AuthorMetadataId == authorMetadataId);
|
return Query(s => s.AuthorMetadataId == authorMetadataId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Book> GetBooksForRefresh(int authorMetadataId, IEnumerable<string> foreignIds)
|
public List<Book> GetBooksForRefresh(int authorMetadataId, List<string> foreignIds)
|
||||||
{
|
{
|
||||||
return Query(a => a.AuthorMetadataId == authorMetadataId || foreignIds.Contains(a.ForeignBookId));
|
return Query(a => a.AuthorMetadataId == authorMetadataId || foreignIds.Contains(a.ForeignBookId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Book> GetBooksByFileIds(IEnumerable<int> fileIds)
|
public List<Book> GetBooksByFileIds(IEnumerable<int> fileIds)
|
||||||
{
|
{
|
||||||
return Query(new SqlBuilder()
|
return Query(new SqlBuilder(_database.DatabaseType)
|
||||||
.Join<Book, Edition>((b, e) => b.Id == e.BookId)
|
.Join<Book, Edition>((b, e) => b.Id == e.BookId)
|
||||||
.Join<Edition, BookFile>((l, r) => l.Id == r.EditionId)
|
.Join<Edition, BookFile>((l, r) => l.Id == r.EditionId)
|
||||||
.Where<BookFile>(f => fileIds.Contains(f.Id)))
|
.Where<BookFile>(f => fileIds.Contains(f.Id)))
|
||||||
@@ -125,7 +143,7 @@ namespace NzbDrone.Core.Books
|
|||||||
{
|
{
|
||||||
foreach (var belowCutoff in profile.QualityIds)
|
foreach (var belowCutoff in profile.QualityIds)
|
||||||
{
|
{
|
||||||
clauses.Add(string.Format("(Authors.[QualityProfileId] = {0} AND BookFiles.Quality LIKE '%_quality_: {1},%')", profile.ProfileId, belowCutoff));
|
clauses.Add(string.Format("(\"Authors\".\"QualityProfileId\" = {0} AND \"BookFiles\".\"Quality\" LIKE '%_quality_: {1},%')", profile.ProfileId, belowCutoff));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +154,7 @@ namespace NzbDrone.Core.Books
|
|||||||
{
|
{
|
||||||
pagingSpec.Records = GetPagedRecords(BooksWhereCutoffUnmetBuilder(qualitiesBelowCutoff), pagingSpec, PagedQuery);
|
pagingSpec.Records = GetPagedRecords(BooksWhereCutoffUnmetBuilder(qualitiesBelowCutoff), pagingSpec, PagedQuery);
|
||||||
|
|
||||||
var countTemplate = $"SELECT COUNT(*) FROM (SELECT /**select**/ FROM {TableMapping.Mapper.TableNameMapping(typeof(Book))} /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/)";
|
var countTemplate = $"SELECT COUNT(*) FROM (SELECT /**select**/ FROM \"{TableMapping.Mapper.TableNameMapping(typeof(Book))}\" /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/) AS \"Inner\"";
|
||||||
pagingSpec.TotalRecords = GetPagedRecordCount(BooksWhereCutoffUnmetBuilder(qualitiesBelowCutoff).Select(typeof(Book)), pagingSpec, countTemplate);
|
pagingSpec.TotalRecords = GetPagedRecordCount(BooksWhereCutoffUnmetBuilder(qualitiesBelowCutoff).Select(typeof(Book)), pagingSpec, countTemplate);
|
||||||
|
|
||||||
return pagingSpec;
|
return pagingSpec;
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ namespace NzbDrone.Core.Books
|
|||||||
{
|
{
|
||||||
List<Edition> GetAllMonitoredEditions();
|
List<Edition> GetAllMonitoredEditions();
|
||||||
Edition FindByForeignEditionId(string foreignEditionId);
|
Edition FindByForeignEditionId(string foreignEditionId);
|
||||||
List<Edition> FindByBook(int id);
|
List<Edition> FindByBook(IEnumerable<int> ids);
|
||||||
List<Edition> FindByAuthor(int id);
|
List<Edition> FindByAuthor(int id);
|
||||||
List<Edition> FindByAuthorMetadataId(int id, bool onlyMonitored);
|
List<Edition> FindByAuthorMetadataId(int id, bool onlyMonitored);
|
||||||
Edition FindByTitle(int authorMetadataId, string title);
|
Edition FindByTitle(int authorMetadataId, string title);
|
||||||
List<Edition> GetEditionsForRefresh(int bookId, IEnumerable<string> foreignEditionIds);
|
List<Edition> GetEditionsForRefresh(int bookId, List<string> foreignEditionIds);
|
||||||
List<Edition> SetMonitored(Edition edition);
|
List<Edition> SetMonitored(Edition edition);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,19 +38,19 @@ namespace NzbDrone.Core.Books
|
|||||||
return edition;
|
return edition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Edition> GetEditionsForRefresh(int bookId, IEnumerable<string> foreignEditionIds)
|
public List<Edition> GetEditionsForRefresh(int bookId, List<string> foreignEditionIds)
|
||||||
{
|
{
|
||||||
return Query(r => r.BookId == bookId || foreignEditionIds.Contains(r.ForeignEditionId));
|
return Query(r => r.BookId == bookId || foreignEditionIds.Contains(r.ForeignEditionId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Edition> FindByBook(int id)
|
public List<Edition> FindByBook(IEnumerable<int> ids)
|
||||||
{
|
{
|
||||||
// populate the books and author metadata also
|
// populate the books and author metadata also
|
||||||
// this hopefully speeds up the track matching a lot
|
// this hopefully speeds up the track matching a lot
|
||||||
var builder = new SqlBuilder()
|
var builder = new SqlBuilder(_database.DatabaseType)
|
||||||
.LeftJoin<Edition, Book>((e, b) => e.BookId == b.Id)
|
.LeftJoin<Edition, Book>((e, b) => e.BookId == b.Id)
|
||||||
.LeftJoin<Book, AuthorMetadata>((b, a) => b.AuthorMetadataId == a.Id)
|
.LeftJoin<Book, AuthorMetadata>((b, a) => b.AuthorMetadataId == a.Id)
|
||||||
.Where<Edition>(r => r.BookId == id);
|
.Where<Edition>(r => ids.Contains(r.BookId));
|
||||||
|
|
||||||
return _database.QueryJoined<Edition, Book, AuthorMetadata>(builder, (edition, book, metadata) =>
|
return _database.QueryJoined<Edition, Book, AuthorMetadata>(builder, (edition, book, metadata) =>
|
||||||
{
|
{
|
||||||
@@ -96,7 +96,7 @@ namespace NzbDrone.Core.Books
|
|||||||
|
|
||||||
public List<Edition> SetMonitored(Edition edition)
|
public List<Edition> SetMonitored(Edition edition)
|
||||||
{
|
{
|
||||||
var allEditions = FindByBook(edition.BookId);
|
var allEditions = FindByBook(new[] { edition.BookId });
|
||||||
allEditions.ForEach(r => r.Monitored = r.Id == edition.Id);
|
allEditions.ForEach(r => r.Monitored = r.Id == edition.Id);
|
||||||
Ensure.That(allEditions.Count(x => x.Monitored) == 1).IsTrue();
|
Ensure.That(allEditions.Count(x => x.Monitored) == 1).IsTrue();
|
||||||
UpdateMany(allEditions);
|
UpdateMany(allEditions);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace NzbDrone.Core.Books
|
|||||||
public interface ISeriesRepository : IBasicRepository<Series>
|
public interface ISeriesRepository : IBasicRepository<Series>
|
||||||
{
|
{
|
||||||
Series FindById(string foreignSeriesId);
|
Series FindById(string foreignSeriesId);
|
||||||
List<Series> FindById(IEnumerable<string> foreignSeriesId);
|
List<Series> FindById(List<string> foreignSeriesId);
|
||||||
List<Series> GetByAuthorMetadataId(int authorMetadataId);
|
List<Series> GetByAuthorMetadataId(int authorMetadataId);
|
||||||
List<Series> GetByAuthorId(int authorId);
|
List<Series> GetByAuthorId(int authorId);
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ namespace NzbDrone.Core.Books
|
|||||||
return Query(x => x.ForeignSeriesId == foreignSeriesId).SingleOrDefault();
|
return Query(x => x.ForeignSeriesId == foreignSeriesId).SingleOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Series> FindById(IEnumerable<string> foreignSeriesId)
|
public List<Series> FindById(List<string> foreignSeriesId)
|
||||||
{
|
{
|
||||||
return Query(x => foreignSeriesId.Contains(x.ForeignSeriesId));
|
return Query(x => foreignSeriesId.Contains(x.ForeignSeriesId));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace NzbDrone.Core.Books
|
|||||||
List<Book> GetNextBooksByAuthorMetadataId(IEnumerable<int> authorMetadataIds);
|
List<Book> GetNextBooksByAuthorMetadataId(IEnumerable<int> authorMetadataIds);
|
||||||
List<Book> GetLastBooksByAuthorMetadataId(IEnumerable<int> authorMetadataIds);
|
List<Book> GetLastBooksByAuthorMetadataId(IEnumerable<int> authorMetadataIds);
|
||||||
List<Book> GetBooksByAuthorMetadataId(int authorMetadataId);
|
List<Book> GetBooksByAuthorMetadataId(int authorMetadataId);
|
||||||
List<Book> GetBooksForRefresh(int authorMetadataId, IEnumerable<string> foreignIds);
|
List<Book> GetBooksForRefresh(int authorMetadataId, List<string> foreignIds);
|
||||||
List<Book> GetBooksByFileIds(IEnumerable<int> fileIds);
|
List<Book> GetBooksByFileIds(IEnumerable<int> fileIds);
|
||||||
Book AddBook(Book newBook, bool doRefresh = true);
|
Book AddBook(Book newBook, bool doRefresh = true);
|
||||||
Book FindById(string foreignId);
|
Book FindById(string foreignId);
|
||||||
@@ -206,7 +206,7 @@ namespace NzbDrone.Core.Books
|
|||||||
return _bookRepository.GetBooksByAuthorMetadataId(authorMetadataId).ToList();
|
return _bookRepository.GetBooksByAuthorMetadataId(authorMetadataId).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Book> GetBooksForRefresh(int authorMetadataId, IEnumerable<string> foreignIds)
|
public List<Book> GetBooksForRefresh(int authorMetadataId, List<string> foreignIds)
|
||||||
{
|
{
|
||||||
return _bookRepository.GetBooksForRefresh(authorMetadataId, foreignIds);
|
return _bookRepository.GetBooksForRefresh(authorMetadataId, foreignIds);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ namespace NzbDrone.Core.Books
|
|||||||
void InsertMany(List<Edition> editions);
|
void InsertMany(List<Edition> editions);
|
||||||
void UpdateMany(List<Edition> editions);
|
void UpdateMany(List<Edition> editions);
|
||||||
void DeleteMany(List<Edition> editions);
|
void DeleteMany(List<Edition> editions);
|
||||||
List<Edition> GetEditionsForRefresh(int bookId, IEnumerable<string> foreignEditionIds);
|
List<Edition> GetEditionsForRefresh(int bookId, List<string> foreignEditionIds);
|
||||||
List<Edition> GetEditionsByBook(int bookId);
|
List<Edition> GetEditionsByBook(int bookId);
|
||||||
|
List<Edition> GetEditionsByBook(IEnumerable<int> bookIds);
|
||||||
List<Edition> GetEditionsByAuthor(int authorId);
|
List<Edition> GetEditionsByAuthor(int authorId);
|
||||||
Edition FindByTitle(int authorMetadataId, string title);
|
Edition FindByTitle(int authorMetadataId, string title);
|
||||||
Edition FindByTitleInexact(int authorMetadataId, string title);
|
Edition FindByTitleInexact(int authorMetadataId, string title);
|
||||||
@@ -72,14 +73,19 @@ namespace NzbDrone.Core.Books
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Edition> GetEditionsForRefresh(int bookId, IEnumerable<string> foreignEditionIds)
|
public List<Edition> GetEditionsForRefresh(int bookId, List<string> foreignEditionIds)
|
||||||
{
|
{
|
||||||
return _editionRepository.GetEditionsForRefresh(bookId, foreignEditionIds);
|
return _editionRepository.GetEditionsForRefresh(bookId, foreignEditionIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Edition> GetEditionsByBook(int bookId)
|
public List<Edition> GetEditionsByBook(int bookId)
|
||||||
{
|
{
|
||||||
return _editionRepository.FindByBook(bookId);
|
return _editionRepository.FindByBook(new[] { bookId });
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Edition> GetEditionsByBook(IEnumerable<int> bookIds)
|
||||||
|
{
|
||||||
|
return _editionRepository.FindByBook(bookIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Edition> GetEditionsByAuthor(int authorId)
|
public List<Edition> GetEditionsByAuthor(int authorId)
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ namespace NzbDrone.Core.Books
|
|||||||
protected override List<Book> GetLocalChildren(Author entity, List<Book> remoteChildren)
|
protected override List<Book> GetLocalChildren(Author entity, List<Book> remoteChildren)
|
||||||
{
|
{
|
||||||
return _bookService.GetBooksForRefresh(entity.AuthorMetadataId,
|
return _bookService.GetBooksForRefresh(entity.AuthorMetadataId,
|
||||||
remoteChildren.Select(x => x.ForeignBookId));
|
remoteChildren.Select(x => x.ForeignBookId).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Tuple<Book, List<Book>> GetMatchingExistingChildren(List<Book> existingChildren, Book remote)
|
protected override Tuple<Book, List<Book>> GetMatchingExistingChildren(List<Book> existingChildren, Book remote)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using NzbDrone.Core.MediaFiles;
|
|||||||
using NzbDrone.Core.Messaging.Commands;
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.MetadataSource;
|
using NzbDrone.Core.MetadataSource;
|
||||||
|
using NzbDrone.Core.RootFolders;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Books
|
namespace NzbDrone.Core.Books
|
||||||
{
|
{
|
||||||
@@ -30,6 +31,7 @@ namespace NzbDrone.Core.Books
|
|||||||
{
|
{
|
||||||
private readonly IBookService _bookService;
|
private readonly IBookService _bookService;
|
||||||
private readonly IAuthorService _authorService;
|
private readonly IAuthorService _authorService;
|
||||||
|
private readonly IRootFolderService _rootFolderService;
|
||||||
private readonly IAddAuthorService _addAuthorService;
|
private readonly IAddAuthorService _addAuthorService;
|
||||||
private readonly IEditionService _editionService;
|
private readonly IEditionService _editionService;
|
||||||
private readonly IProvideAuthorInfo _authorInfo;
|
private readonly IProvideAuthorInfo _authorInfo;
|
||||||
@@ -44,6 +46,7 @@ namespace NzbDrone.Core.Books
|
|||||||
|
|
||||||
public RefreshBookService(IBookService bookService,
|
public RefreshBookService(IBookService bookService,
|
||||||
IAuthorService authorService,
|
IAuthorService authorService,
|
||||||
|
IRootFolderService rootFolderService,
|
||||||
IAddAuthorService addAuthorService,
|
IAddAuthorService addAuthorService,
|
||||||
IEditionService editionService,
|
IEditionService editionService,
|
||||||
IAuthorMetadataService authorMetadataService,
|
IAuthorMetadataService authorMetadataService,
|
||||||
@@ -60,6 +63,7 @@ namespace NzbDrone.Core.Books
|
|||||||
{
|
{
|
||||||
_bookService = bookService;
|
_bookService = bookService;
|
||||||
_authorService = authorService;
|
_authorService = authorService;
|
||||||
|
_rootFolderService = rootFolderService;
|
||||||
_addAuthorService = addAuthorService;
|
_addAuthorService = addAuthorService;
|
||||||
_editionService = editionService;
|
_editionService = editionService;
|
||||||
_authorInfo = authorInfo;
|
_authorInfo = authorInfo;
|
||||||
@@ -142,7 +146,7 @@ namespace NzbDrone.Core.Books
|
|||||||
Metadata = remote.AuthorMetadata.Value,
|
Metadata = remote.AuthorMetadata.Value,
|
||||||
MetadataProfileId = oldAuthor.MetadataProfileId,
|
MetadataProfileId = oldAuthor.MetadataProfileId,
|
||||||
QualityProfileId = oldAuthor.QualityProfileId,
|
QualityProfileId = oldAuthor.QualityProfileId,
|
||||||
RootFolderPath = oldAuthor.RootFolderPath,
|
RootFolderPath = _rootFolderService.GetBestRootFolderPath(oldAuthor.Path),
|
||||||
Monitored = oldAuthor.Monitored,
|
Monitored = oldAuthor.Monitored,
|
||||||
Tags = oldAuthor.Tags
|
Tags = oldAuthor.Tags
|
||||||
};
|
};
|
||||||
@@ -246,7 +250,7 @@ namespace NzbDrone.Core.Books
|
|||||||
|
|
||||||
protected override List<Edition> GetLocalChildren(Book entity, List<Edition> remoteChildren)
|
protected override List<Edition> GetLocalChildren(Book entity, List<Edition> remoteChildren)
|
||||||
{
|
{
|
||||||
return _editionService.GetEditionsForRefresh(entity.Id, remoteChildren.Select(x => x.ForeignEditionId));
|
return _editionService.GetEditionsForRefresh(entity.Id, remoteChildren.Select(x => x.ForeignEditionId).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Tuple<Edition, List<Edition>> GetMatchingExistingChildren(List<Edition> existingChildren, Edition remote)
|
protected override Tuple<Edition, List<Edition>> GetMatchingExistingChildren(List<Edition> existingChildren, Edition remote)
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ namespace NzbDrone.Core.Books
|
|||||||
var updated = false;
|
var updated = false;
|
||||||
|
|
||||||
var existingByAuthor = _seriesService.GetByAuthorMetadataId(authorMetadataId);
|
var existingByAuthor = _seriesService.GetByAuthorMetadataId(authorMetadataId);
|
||||||
var existingBySeries = _seriesService.FindById(remoteSeries.Select(x => x.ForeignSeriesId));
|
var existingBySeries = _seriesService.FindById(remoteSeries.Select(x => x.ForeignSeriesId).ToList());
|
||||||
var existing = existingByAuthor.Concat(existingBySeries).GroupBy(x => x.ForeignSeriesId).Select(x => x.First()).ToList();
|
var existing = existingByAuthor.Concat(existingBySeries).GroupBy(x => x.ForeignSeriesId).Select(x => x.First()).ToList();
|
||||||
|
|
||||||
var books = _bookService.GetBooksByAuthorMetadataId(authorMetadataId);
|
var books = _bookService.GetBooksByAuthorMetadataId(authorMetadataId);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace NzbDrone.Core.Books
|
|||||||
public interface ISeriesService
|
public interface ISeriesService
|
||||||
{
|
{
|
||||||
Series FindById(string foreignSeriesId);
|
Series FindById(string foreignSeriesId);
|
||||||
List<Series> FindById(IEnumerable<string> foreignSeriesId);
|
List<Series> FindById(List<string> foreignSeriesId);
|
||||||
List<Series> GetByAuthorMetadataId(int authorMetadataId);
|
List<Series> GetByAuthorMetadataId(int authorMetadataId);
|
||||||
List<Series> GetByAuthorId(int authorId);
|
List<Series> GetByAuthorId(int authorId);
|
||||||
void Delete(int seriesId);
|
void Delete(int seriesId);
|
||||||
@@ -27,7 +27,7 @@ namespace NzbDrone.Core.Books
|
|||||||
return _seriesRepository.FindById(foreignSeriesId);
|
return _seriesRepository.FindById(foreignSeriesId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Series> FindById(IEnumerable<string> foreignSeriesId)
|
public List<Series> FindById(List<string> foreignSeriesId)
|
||||||
{
|
{
|
||||||
return _seriesRepository.FindById(foreignSeriesId);
|
return _seriesRepository.FindById(foreignSeriesId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ namespace NzbDrone.Core.Books
|
|||||||
public class AddAuthorValidator : AbstractValidator<Author>, IAddAuthorValidator
|
public class AddAuthorValidator : AbstractValidator<Author>, IAddAuthorValidator
|
||||||
{
|
{
|
||||||
public AddAuthorValidator(RootFolderValidator rootFolderValidator,
|
public AddAuthorValidator(RootFolderValidator rootFolderValidator,
|
||||||
|
RecycleBinValidator recycleBinValidator,
|
||||||
AuthorPathValidator authorPathValidator,
|
AuthorPathValidator authorPathValidator,
|
||||||
AuthorAncestorValidator authorAncestorValidator,
|
AuthorAncestorValidator authorAncestorValidator,
|
||||||
QualityProfileExistsValidator qualityProfileExistsValidator,
|
QualityProfileExistsValidator qualityProfileExistsValidator,
|
||||||
@@ -21,6 +22,7 @@ namespace NzbDrone.Core.Books
|
|||||||
RuleFor(c => c.Path).Cascade(CascadeMode.StopOnFirstFailure)
|
RuleFor(c => c.Path).Cascade(CascadeMode.StopOnFirstFailure)
|
||||||
.IsValidPath()
|
.IsValidPath()
|
||||||
.SetValidator(rootFolderValidator)
|
.SetValidator(rootFolderValidator)
|
||||||
|
.SetValidator(recycleBinValidator)
|
||||||
.SetValidator(authorPathValidator)
|
.SetValidator(authorPathValidator)
|
||||||
.SetValidator(authorAncestorValidator);
|
.SetValidator(authorAncestorValidator);
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using NzbDrone.Common.Cache;
|
using NzbDrone.Common.Cache;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Authentication;
|
using NzbDrone.Core.Authentication;
|
||||||
using NzbDrone.Core.Configuration.Events;
|
using NzbDrone.Core.Configuration.Events;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Lifecycle;
|
using NzbDrone.Core.Lifecycle;
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
@@ -18,7 +20,7 @@ using NzbDrone.Core.Update;
|
|||||||
namespace NzbDrone.Core.Configuration
|
namespace NzbDrone.Core.Configuration
|
||||||
{
|
{
|
||||||
public interface IConfigFileProvider : IHandleAsync<ApplicationStartedEvent>,
|
public interface IConfigFileProvider : IHandleAsync<ApplicationStartedEvent>,
|
||||||
IExecute<ResetApiKeyCommand>
|
IExecute<ResetApiKeyCommand>
|
||||||
{
|
{
|
||||||
Dictionary<string, object> GetConfigDictionary();
|
Dictionary<string, object> GetConfigDictionary();
|
||||||
void SaveConfigDictionary(Dictionary<string, object> configValues);
|
void SaveConfigDictionary(Dictionary<string, object> configValues);
|
||||||
@@ -41,9 +43,20 @@ namespace NzbDrone.Core.Configuration
|
|||||||
string SslCertPassword { get; }
|
string SslCertPassword { get; }
|
||||||
string UrlBase { get; }
|
string UrlBase { get; }
|
||||||
string UiFolder { get; }
|
string UiFolder { get; }
|
||||||
|
string InstanceName { get; }
|
||||||
bool UpdateAutomatically { get; }
|
bool UpdateAutomatically { get; }
|
||||||
UpdateMechanism UpdateMechanism { get; }
|
UpdateMechanism UpdateMechanism { get; }
|
||||||
string UpdateScriptPath { get; }
|
string UpdateScriptPath { get; }
|
||||||
|
string SyslogServer { get; }
|
||||||
|
int SyslogPort { get; }
|
||||||
|
string SyslogLevel { get; }
|
||||||
|
string PostgresHost { get; }
|
||||||
|
int PostgresPort { get; }
|
||||||
|
string PostgresUser { get; }
|
||||||
|
string PostgresPassword { get; }
|
||||||
|
string PostgresMainDb { get; }
|
||||||
|
string PostgresLogDb { get; }
|
||||||
|
string PostgresCacheDb { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ConfigFileProvider : IConfigFileProvider
|
public class ConfigFileProvider : IConfigFileProvider
|
||||||
@@ -53,6 +66,7 @@ namespace NzbDrone.Core.Configuration
|
|||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
private readonly ICached<string> _cache;
|
private readonly ICached<string> _cache;
|
||||||
|
private readonly PostgresOptions _postgresOptions;
|
||||||
|
|
||||||
private readonly string _configFile;
|
private readonly string _configFile;
|
||||||
|
|
||||||
@@ -61,12 +75,14 @@ namespace NzbDrone.Core.Configuration
|
|||||||
public ConfigFileProvider(IAppFolderInfo appFolderInfo,
|
public ConfigFileProvider(IAppFolderInfo appFolderInfo,
|
||||||
ICacheManager cacheManager,
|
ICacheManager cacheManager,
|
||||||
IEventAggregator eventAggregator,
|
IEventAggregator eventAggregator,
|
||||||
IDiskProvider diskProvider)
|
IDiskProvider diskProvider,
|
||||||
|
IOptions<PostgresOptions> postgresOptions)
|
||||||
{
|
{
|
||||||
_cache = cacheManager.GetCache<string>(GetType());
|
_cache = cacheManager.GetCache<string>(GetType());
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
_configFile = appFolderInfo.GetConfigPath();
|
_configFile = appFolderInfo.GetConfigPath();
|
||||||
|
_postgresOptions = postgresOptions.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Dictionary<string, object> GetConfigDictionary()
|
public Dictionary<string, object> GetConfigDictionary()
|
||||||
@@ -180,6 +196,13 @@ namespace NzbDrone.Core.Configuration
|
|||||||
|
|
||||||
public string LogLevel => GetValue("LogLevel", "info");
|
public string LogLevel => GetValue("LogLevel", "info");
|
||||||
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
|
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
|
||||||
|
public string PostgresHost => _postgresOptions?.Host ?? GetValue("PostgresHost", string.Empty, persist: false);
|
||||||
|
public string PostgresUser => _postgresOptions?.User ?? GetValue("PostgresUser", string.Empty, persist: false);
|
||||||
|
public string PostgresPassword => _postgresOptions?.Password ?? GetValue("PostgresPassword", string.Empty, persist: false);
|
||||||
|
public string PostgresMainDb => _postgresOptions?.MainDb ?? GetValue("PostgresMainDb", "readarr-main", persist: false);
|
||||||
|
public string PostgresLogDb => _postgresOptions?.LogDb ?? GetValue("PostgresLogDb", "readarr-log", persist: false);
|
||||||
|
public string PostgresCacheDb => _postgresOptions?.CacheDb ?? GetValue("PostgresCacheDb", "readarr-cache", persist: false);
|
||||||
|
public int PostgresPort => (_postgresOptions?.Port ?? 0) != 0 ? _postgresOptions.Port : GetValueInt("PostgresPort", 5432, persist: false);
|
||||||
public bool LogSql => GetValueBoolean("LogSql", false, persist: false);
|
public bool LogSql => GetValueBoolean("LogSql", false, persist: false);
|
||||||
public int LogRotate => GetValueInt("LogRotate", 50, persist: false);
|
public int LogRotate => GetValueInt("LogRotate", 50, persist: false);
|
||||||
public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false);
|
public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false);
|
||||||
@@ -202,6 +225,7 @@ namespace NzbDrone.Core.Configuration
|
|||||||
}
|
}
|
||||||
|
|
||||||
public string UiFolder => BuildInfo.IsDebug ? Path.Combine("..", "UI") : "UI";
|
public string UiFolder => BuildInfo.IsDebug ? Path.Combine("..", "UI") : "UI";
|
||||||
|
public string InstanceName => GetValue("InstanceName", BuildInfo.AppName);
|
||||||
|
|
||||||
public bool UpdateAutomatically => GetValueBoolean("UpdateAutomatically", false, false);
|
public bool UpdateAutomatically => GetValueBoolean("UpdateAutomatically", false, false);
|
||||||
|
|
||||||
@@ -209,6 +233,12 @@ namespace NzbDrone.Core.Configuration
|
|||||||
|
|
||||||
public string UpdateScriptPath => GetValue("UpdateScriptPath", "", false);
|
public string UpdateScriptPath => GetValue("UpdateScriptPath", "", false);
|
||||||
|
|
||||||
|
public string SyslogServer => GetValue("SyslogServer", "", persist: false);
|
||||||
|
|
||||||
|
public int SyslogPort => GetValueInt("SyslogPort", 514, persist: false);
|
||||||
|
|
||||||
|
public string SyslogLevel => GetValue("SyslogLevel", LogLevel, false).ToLowerInvariant();
|
||||||
|
|
||||||
public int GetValueInt(string key, int defaultValue, bool persist = true)
|
public int GetValueInt(string key, int defaultValue, bool persist = true)
|
||||||
{
|
{
|
||||||
return Convert.ToInt32(GetValue(key, defaultValue, persist));
|
return Convert.ToInt32(GetValue(key, defaultValue, persist));
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ namespace NzbDrone.Core.Datastore
|
|||||||
_updateSql = GetUpdateSql(_properties);
|
_updateSql = GetUpdateSql(_properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual SqlBuilder Builder() => new SqlBuilder();
|
protected virtual SqlBuilder Builder() => new SqlBuilder(_database.DatabaseType);
|
||||||
|
|
||||||
protected virtual List<TModel> Query(SqlBuilder builder) => _database.Query<TModel>(builder).ToList();
|
protected virtual List<TModel> Query(SqlBuilder builder) => _database.Query<TModel>(builder).ToList();
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ namespace NzbDrone.Core.Datastore
|
|||||||
{
|
{
|
||||||
using (var conn = _database.OpenConnection())
|
using (var conn = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
return conn.ExecuteScalar<int>($"SELECT COUNT(*) FROM {_table}");
|
return conn.ExecuteScalar<int>($"SELECT COUNT(*) FROM \"{_table}\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,6 +176,11 @@ namespace NzbDrone.Core.Datastore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_database.DatabaseType == DatabaseType.PostgreSQL)
|
||||||
|
{
|
||||||
|
return $"INSERT INTO \"{_table}\" ({sbColumnList.ToString()}) VALUES ({sbParameterList.ToString()}) RETURNING \"Id\"";
|
||||||
|
}
|
||||||
|
|
||||||
return $"INSERT INTO {_table} ({sbColumnList.ToString()}) VALUES ({sbParameterList.ToString()}); SELECT last_insert_rowid() id";
|
return $"INSERT INTO {_table} ({sbColumnList.ToString()}) VALUES ({sbParameterList.ToString()}); SELECT last_insert_rowid() id";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +199,8 @@ namespace NzbDrone.Core.Datastore
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
var id = (int)multi.Read().First().id;
|
var multiRead = multi.Read();
|
||||||
|
var id = (int)(multiRead.First().id ?? multiRead.First().Id);
|
||||||
_keyProperty.SetValue(model, id);
|
_keyProperty.SetValue(model, id);
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
@@ -305,7 +311,7 @@ namespace NzbDrone.Core.Datastore
|
|||||||
{
|
{
|
||||||
using (var conn = _database.OpenConnection())
|
using (var conn = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
conn.Execute($"DELETE FROM [{_table}]");
|
conn.Execute($"DELETE FROM \"{_table}\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vacuum)
|
if (vacuum)
|
||||||
@@ -364,7 +370,7 @@ namespace NzbDrone.Core.Datastore
|
|||||||
private string GetUpdateSql(List<PropertyInfo> propertiesToUpdate)
|
private string GetUpdateSql(List<PropertyInfo> propertiesToUpdate)
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
sb.AppendFormat("UPDATE {0} SET ", _table);
|
sb.AppendFormat("UPDATE \"{0}\" SET ", _table);
|
||||||
|
|
||||||
for (var i = 0; i < propertiesToUpdate.Count; i++)
|
for (var i = 0; i < propertiesToUpdate.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -446,9 +452,10 @@ namespace NzbDrone.Core.Datastore
|
|||||||
pagingSpec.SortKey = $"{_table}.{_keyProperty.Name}";
|
pagingSpec.SortKey = $"{_table}.{_keyProperty.Name}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sortKey = TableMapping.Mapper.GetSortKey(pagingSpec.SortKey);
|
||||||
var sortDirection = pagingSpec.SortDirection == SortDirection.Descending ? "DESC" : "ASC";
|
var sortDirection = pagingSpec.SortDirection == SortDirection.Descending ? "DESC" : "ASC";
|
||||||
var pagingOffset = (pagingSpec.Page - 1) * pagingSpec.PageSize;
|
var pagingOffset = Math.Max(pagingSpec.Page - 1, 0) * pagingSpec.PageSize;
|
||||||
builder.OrderBy($"{pagingSpec.SortKey} {sortDirection} LIMIT {pagingSpec.PageSize} OFFSET {pagingOffset}");
|
builder.OrderBy($"\"{sortKey}\" {sortDirection} LIMIT {pagingSpec.PageSize} OFFSET {pagingOffset}");
|
||||||
|
|
||||||
return queryFunc(builder).ToList();
|
return queryFunc(builder).ToList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Datastore
|
namespace NzbDrone.Core.Datastore
|
||||||
@@ -10,10 +10,12 @@ namespace NzbDrone.Core.Datastore
|
|||||||
public class CacheDatabase : ICacheDatabase
|
public class CacheDatabase : ICacheDatabase
|
||||||
{
|
{
|
||||||
private readonly IDatabase _database;
|
private readonly IDatabase _database;
|
||||||
|
private readonly DatabaseType _databaseType;
|
||||||
|
|
||||||
public CacheDatabase(IDatabase database)
|
public CacheDatabase(IDatabase database)
|
||||||
{
|
{
|
||||||
_database = database;
|
_database = database;
|
||||||
|
_databaseType = _database == null ? DatabaseType.SQLite : _database.DatabaseType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDbConnection OpenConnection()
|
public IDbConnection OpenConnection()
|
||||||
@@ -25,6 +27,8 @@ namespace NzbDrone.Core.Datastore
|
|||||||
|
|
||||||
public int Migration => _database.Migration;
|
public int Migration => _database.Migration;
|
||||||
|
|
||||||
|
public DatabaseType DatabaseType => _databaseType;
|
||||||
|
|
||||||
public void Vacuum()
|
public void Vacuum()
|
||||||
{
|
{
|
||||||
_database.Vacuum();
|
_database.Vacuum();
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Data.SQLite;
|
using System.Data.SQLite;
|
||||||
|
using Npgsql;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Datastore
|
namespace NzbDrone.Core.Datastore
|
||||||
{
|
{
|
||||||
@@ -15,11 +17,20 @@ namespace NzbDrone.Core.Datastore
|
|||||||
|
|
||||||
public class ConnectionStringFactory : IConnectionStringFactory
|
public class ConnectionStringFactory : IConnectionStringFactory
|
||||||
{
|
{
|
||||||
public ConnectionStringFactory(IAppFolderInfo appFolderInfo)
|
private readonly IConfigFileProvider _configFileProvider;
|
||||||
|
|
||||||
|
public ConnectionStringFactory(IAppFolderInfo appFolderInfo, IConfigFileProvider configFileProvider)
|
||||||
{
|
{
|
||||||
MainDbConnectionString = GetConnectionString(appFolderInfo.GetDatabase());
|
_configFileProvider = configFileProvider;
|
||||||
LogDbConnectionString = GetConnectionString(appFolderInfo.GetLogDatabase());
|
|
||||||
CacheDbConnectionString = GetConnectionString(appFolderInfo.GetCacheDatabase());
|
MainDbConnectionString = _configFileProvider.PostgresHost.IsNotNullOrWhiteSpace() ? GetPostgresConnectionString(_configFileProvider.PostgresMainDb) :
|
||||||
|
GetConnectionString(appFolderInfo.GetDatabase());
|
||||||
|
|
||||||
|
LogDbConnectionString = _configFileProvider.PostgresHost.IsNotNullOrWhiteSpace() ? GetPostgresConnectionString(_configFileProvider.PostgresLogDb) :
|
||||||
|
GetConnectionString(appFolderInfo.GetLogDatabase());
|
||||||
|
|
||||||
|
CacheDbConnectionString = _configFileProvider.PostgresHost.IsNotNullOrWhiteSpace() ? GetPostgresConnectionString(_configFileProvider.PostgresCacheDb) :
|
||||||
|
GetConnectionString(appFolderInfo.GetCacheDatabase());
|
||||||
}
|
}
|
||||||
|
|
||||||
public string MainDbConnectionString { get; private set; }
|
public string MainDbConnectionString { get; private set; }
|
||||||
@@ -51,5 +62,19 @@ namespace NzbDrone.Core.Datastore
|
|||||||
|
|
||||||
return connectionBuilder.ConnectionString;
|
return connectionBuilder.ConnectionString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetPostgresConnectionString(string dbName)
|
||||||
|
{
|
||||||
|
var connectionBuilder = new NpgsqlConnectionStringBuilder();
|
||||||
|
|
||||||
|
connectionBuilder.Database = dbName;
|
||||||
|
connectionBuilder.Host = _configFileProvider.PostgresHost;
|
||||||
|
connectionBuilder.Username = _configFileProvider.PostgresUser;
|
||||||
|
connectionBuilder.Password = _configFileProvider.PostgresPassword;
|
||||||
|
connectionBuilder.Port = _configFileProvider.PostgresPort;
|
||||||
|
connectionBuilder.Enlist = false;
|
||||||
|
|
||||||
|
return connectionBuilder.ConnectionString;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
19
src/NzbDrone.Core/Datastore/Converters/TimeSpanConverter.cs
Normal file
19
src/NzbDrone.Core/Datastore/Converters/TimeSpanConverter.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Data;
|
||||||
|
using Dapper;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Converters
|
||||||
|
{
|
||||||
|
public class DapperTimeSpanConverter : SqlMapper.TypeHandler<TimeSpan>
|
||||||
|
{
|
||||||
|
public override void SetValue(IDbDataParameter parameter, TimeSpan value)
|
||||||
|
{
|
||||||
|
parameter.Value = value.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override TimeSpan Parse(object value)
|
||||||
|
{
|
||||||
|
return TimeSpan.Parse((string)value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using Dapper;
|
using Dapper;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Instrumentation;
|
using NzbDrone.Common.Instrumentation;
|
||||||
@@ -11,6 +12,7 @@ namespace NzbDrone.Core.Datastore
|
|||||||
IDbConnection OpenConnection();
|
IDbConnection OpenConnection();
|
||||||
Version Version { get; }
|
Version Version { get; }
|
||||||
int Migration { get; }
|
int Migration { get; }
|
||||||
|
DatabaseType DatabaseType { get; }
|
||||||
void Vacuum();
|
void Vacuum();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,13 +34,44 @@ namespace NzbDrone.Core.Datastore
|
|||||||
return _datamapperFactory();
|
return _datamapperFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DatabaseType DatabaseType
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
using (var db = _datamapperFactory())
|
||||||
|
{
|
||||||
|
if (db.ConnectionString.Contains(".db"))
|
||||||
|
{
|
||||||
|
return DatabaseType.SQLite;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return DatabaseType.PostgreSQL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Version Version
|
public Version Version
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
using (var db = _datamapperFactory())
|
using (var db = _datamapperFactory())
|
||||||
{
|
{
|
||||||
var version = db.QueryFirstOrDefault<string>("SELECT sqlite_version()");
|
string version;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
version = db.QueryFirstOrDefault<string>("SHOW server_version");
|
||||||
|
|
||||||
|
//Postgres can return extra info about operating system on version call, ignore this
|
||||||
|
version = Regex.Replace(version, @"\(.*?\)", "");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
version = db.QueryFirstOrDefault<string>("SELECT sqlite_version()");
|
||||||
|
}
|
||||||
|
|
||||||
return new Version(version);
|
return new Version(version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,7 +83,7 @@ namespace NzbDrone.Core.Datastore
|
|||||||
{
|
{
|
||||||
using (var db = _datamapperFactory())
|
using (var db = _datamapperFactory())
|
||||||
{
|
{
|
||||||
return db.QueryFirstOrDefault<int>("SELECT version from VersionInfo ORDER BY version DESC LIMIT 1");
|
return db.QueryFirstOrDefault<int>("SELECT \"Version\" from \"VersionInfo\" ORDER BY \"Version\" DESC LIMIT 1");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,4 +106,10 @@ namespace NzbDrone.Core.Datastore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum DatabaseType
|
||||||
|
{
|
||||||
|
SQLite,
|
||||||
|
PostgreSQL
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Data.Common;
|
||||||
using System.Data.SQLite;
|
using System.Data.SQLite;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using Npgsql;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Common.Exceptions;
|
using NzbDrone.Common.Exceptions;
|
||||||
@@ -92,10 +94,19 @@ namespace NzbDrone.Core.Datastore
|
|||||||
|
|
||||||
var db = new Database(migrationContext.MigrationType.ToString(), () =>
|
var db = new Database(migrationContext.MigrationType.ToString(), () =>
|
||||||
{
|
{
|
||||||
var conn = SQLiteFactory.Instance.CreateConnection();
|
DbConnection conn;
|
||||||
conn.ConnectionString = connectionString;
|
|
||||||
conn.Open();
|
|
||||||
|
|
||||||
|
if (connectionString.Contains(".db"))
|
||||||
|
{
|
||||||
|
conn = SQLiteFactory.Instance.CreateConnection();
|
||||||
|
conn.ConnectionString = connectionString;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
conn = new NpgsqlConnection(connectionString);
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.Open();
|
||||||
return conn;
|
return conn;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ namespace NzbDrone.Core.Datastore
|
|||||||
|
|
||||||
public static SqlBuilder Select(this SqlBuilder builder, params Type[] types)
|
public static SqlBuilder Select(this SqlBuilder builder, params Type[] types)
|
||||||
{
|
{
|
||||||
return builder.Select(types.Select(x => TableMapping.Mapper.TableNameMapping(x) + ".*").Join(", "));
|
return builder.Select(types.Select(x => $"\"{TableMapping.Mapper.TableNameMapping(x)}\".*").Join(", "));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SqlBuilder SelectDistinct(this SqlBuilder builder, params Type[] types)
|
public static SqlBuilder SelectDistinct(this SqlBuilder builder, params Type[] types)
|
||||||
{
|
{
|
||||||
return builder.Select("DISTINCT " + types.Select(x => TableMapping.Mapper.TableNameMapping(x) + ".*").Join(", "));
|
return builder.Select("DISTINCT " + types.Select(x => $"\"{TableMapping.Mapper.TableNameMapping(x)}\".*").Join(", "));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SqlBuilder SelectCount(this SqlBuilder builder)
|
public static SqlBuilder SelectCount(this SqlBuilder builder)
|
||||||
@@ -42,41 +42,48 @@ namespace NzbDrone.Core.Datastore
|
|||||||
|
|
||||||
public static SqlBuilder Where<TModel>(this SqlBuilder builder, Expression<Func<TModel, bool>> filter)
|
public static SqlBuilder Where<TModel>(this SqlBuilder builder, Expression<Func<TModel, bool>> filter)
|
||||||
{
|
{
|
||||||
var wb = new WhereBuilder(filter, true, builder.Sequence);
|
var wb = GetWhereBuilder(builder.DatabaseType, filter, true, builder.Sequence);
|
||||||
|
|
||||||
|
return builder.Where(wb.ToString(), wb.Parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SqlBuilder WherePostgres<TModel>(this SqlBuilder builder, Expression<Func<TModel, bool>> filter)
|
||||||
|
{
|
||||||
|
var wb = new WhereBuilderPostgres(filter, true, builder.Sequence);
|
||||||
|
|
||||||
return builder.Where(wb.ToString(), wb.Parameters);
|
return builder.Where(wb.ToString(), wb.Parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SqlBuilder OrWhere<TModel>(this SqlBuilder builder, Expression<Func<TModel, bool>> filter)
|
public static SqlBuilder OrWhere<TModel>(this SqlBuilder builder, Expression<Func<TModel, bool>> filter)
|
||||||
{
|
{
|
||||||
var wb = new WhereBuilder(filter, true, builder.Sequence);
|
var wb = GetWhereBuilder(builder.DatabaseType, filter, true, builder.Sequence);
|
||||||
|
|
||||||
return builder.OrWhere(wb.ToString(), wb.Parameters);
|
return builder.OrWhere(wb.ToString(), wb.Parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SqlBuilder Join<TLeft, TRight>(this SqlBuilder builder, Expression<Func<TLeft, TRight, bool>> filter)
|
public static SqlBuilder Join<TLeft, TRight>(this SqlBuilder builder, Expression<Func<TLeft, TRight, bool>> filter)
|
||||||
{
|
{
|
||||||
var wb = new WhereBuilder(filter, false, builder.Sequence);
|
var wb = GetWhereBuilder(builder.DatabaseType, filter, false, builder.Sequence);
|
||||||
|
|
||||||
var rightTable = TableMapping.Mapper.TableNameMapping(typeof(TRight));
|
var rightTable = TableMapping.Mapper.TableNameMapping(typeof(TRight));
|
||||||
|
|
||||||
return builder.Join($"{rightTable} ON {wb.ToString()}");
|
return builder.Join($"\"{rightTable}\" ON {wb.ToString()}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SqlBuilder LeftJoin<TLeft, TRight>(this SqlBuilder builder, Expression<Func<TLeft, TRight, bool>> filter)
|
public static SqlBuilder LeftJoin<TLeft, TRight>(this SqlBuilder builder, Expression<Func<TLeft, TRight, bool>> filter)
|
||||||
{
|
{
|
||||||
var wb = new WhereBuilder(filter, false, builder.Sequence);
|
var wb = GetWhereBuilder(builder.DatabaseType, filter, false, builder.Sequence);
|
||||||
|
|
||||||
var rightTable = TableMapping.Mapper.TableNameMapping(typeof(TRight));
|
var rightTable = TableMapping.Mapper.TableNameMapping(typeof(TRight));
|
||||||
|
|
||||||
return builder.LeftJoin($"{rightTable} ON {wb.ToString()}");
|
return builder.LeftJoin($"\"{rightTable}\" ON {wb.ToString()}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SqlBuilder GroupBy<TModel>(this SqlBuilder builder, Expression<Func<TModel, object>> property)
|
public static SqlBuilder GroupBy<TModel>(this SqlBuilder builder, Expression<Func<TModel, object>> property)
|
||||||
{
|
{
|
||||||
var table = TableMapping.Mapper.TableNameMapping(typeof(TModel));
|
var table = TableMapping.Mapper.TableNameMapping(typeof(TModel));
|
||||||
var propName = property.GetMemberName().Name;
|
var propName = property.GetMemberName().Name;
|
||||||
return builder.GroupBy($"{table}.{propName}");
|
return builder.GroupBy($"\"{table}\".\"{propName}\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SqlBuilder.Template AddSelectTemplate(this SqlBuilder builder, Type type)
|
public static SqlBuilder.Template AddSelectTemplate(this SqlBuilder builder, Type type)
|
||||||
@@ -138,6 +145,18 @@ namespace NzbDrone.Core.Datastore
|
|||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static WhereBuilder GetWhereBuilder(DatabaseType databaseType, Expression filter, bool requireConcrete, int seq)
|
||||||
|
{
|
||||||
|
if (databaseType == DatabaseType.PostgreSQL)
|
||||||
|
{
|
||||||
|
return new WhereBuilderPostgres(filter, requireConcrete, seq);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new WhereBuilderSqlite(filter, requireConcrete, seq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static Dictionary<string, object> ToDictionary(this DynamicParameters dynamicParams)
|
private static Dictionary<string, object> ToDictionary(this DynamicParameters dynamicParams)
|
||||||
{
|
{
|
||||||
var argsDictionary = new Dictionary<string, object>();
|
var argsDictionary = new Dictionary<string, object>();
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ namespace NzbDrone.Core.Datastore
|
|||||||
public class LogDatabase : ILogDatabase
|
public class LogDatabase : ILogDatabase
|
||||||
{
|
{
|
||||||
private readonly IDatabase _database;
|
private readonly IDatabase _database;
|
||||||
|
private readonly DatabaseType _databaseType;
|
||||||
|
|
||||||
public LogDatabase(IDatabase database)
|
public LogDatabase(IDatabase database)
|
||||||
{
|
{
|
||||||
_database = database;
|
_database = database;
|
||||||
|
_databaseType = _database == null ? DatabaseType.SQLite : _database.DatabaseType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDbConnection OpenConnection()
|
public IDbConnection OpenConnection()
|
||||||
@@ -25,6 +27,8 @@ namespace NzbDrone.Core.Datastore
|
|||||||
|
|
||||||
public int Migration => _database.Migration;
|
public int Migration => _database.Migration;
|
||||||
|
|
||||||
|
public DatabaseType DatabaseType => _databaseType;
|
||||||
|
|
||||||
public void Vacuum()
|
public void Vacuum()
|
||||||
{
|
{
|
||||||
_database.Vacuum();
|
_database.Vacuum();
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ namespace NzbDrone.Core.Datastore
|
|||||||
public class MainDatabase : IMainDatabase
|
public class MainDatabase : IMainDatabase
|
||||||
{
|
{
|
||||||
private readonly IDatabase _database;
|
private readonly IDatabase _database;
|
||||||
|
private readonly DatabaseType _databaseType;
|
||||||
|
|
||||||
public MainDatabase(IDatabase database)
|
public MainDatabase(IDatabase database)
|
||||||
{
|
{
|
||||||
_database = database;
|
_database = database;
|
||||||
|
_databaseType = _database == null ? DatabaseType.SQLite : _database.DatabaseType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDbConnection OpenConnection()
|
public IDbConnection OpenConnection()
|
||||||
@@ -25,6 +27,8 @@ namespace NzbDrone.Core.Datastore
|
|||||||
|
|
||||||
public int Migration => _database.Migration;
|
public int Migration => _database.Migration;
|
||||||
|
|
||||||
|
public DatabaseType DatabaseType => _databaseType;
|
||||||
|
|
||||||
public void Vacuum()
|
public void Vacuum()
|
||||||
{
|
{
|
||||||
_database.Vacuum();
|
_database.Vacuum();
|
||||||
|
|||||||
@@ -37,6 +37,22 @@ namespace NzbDrone.Core.Datastore.Migration
|
|||||||
.WithColumn("MetadataProfileId").AsInt32().WithDefaultValue(1)
|
.WithColumn("MetadataProfileId").AsInt32().WithDefaultValue(1)
|
||||||
.WithColumn("AuthorMetadataId").AsInt32().Unique();
|
.WithColumn("AuthorMetadataId").AsInt32().Unique();
|
||||||
|
|
||||||
|
Create.TableForModel("Books")
|
||||||
|
.WithColumn("AuthorMetadataId").AsInt32().WithDefaultValue(0)
|
||||||
|
.WithColumn("ForeignBookId").AsString().Indexed()
|
||||||
|
.WithColumn("TitleSlug").AsString().Unique()
|
||||||
|
.WithColumn("Title").AsString()
|
||||||
|
.WithColumn("ReleaseDate").AsDateTime().Nullable()
|
||||||
|
.WithColumn("Links").AsString().Nullable()
|
||||||
|
.WithColumn("Genres").AsString().Nullable()
|
||||||
|
.WithColumn("Ratings").AsString().Nullable()
|
||||||
|
.WithColumn("CleanTitle").AsString().Indexed()
|
||||||
|
.WithColumn("Monitored").AsBoolean()
|
||||||
|
.WithColumn("AnyEditionOk").AsBoolean()
|
||||||
|
.WithColumn("LastInfoSync").AsDateTime().Nullable()
|
||||||
|
.WithColumn("Added").AsDateTime().Nullable()
|
||||||
|
.WithColumn("AddOptions").AsString().Nullable();
|
||||||
|
|
||||||
Create.TableForModel("Series")
|
Create.TableForModel("Series")
|
||||||
.WithColumn("ForeignSeriesId").AsString().Unique()
|
.WithColumn("ForeignSeriesId").AsString().Unique()
|
||||||
.WithColumn("Title").AsString()
|
.WithColumn("Title").AsString()
|
||||||
@@ -68,22 +84,6 @@ namespace NzbDrone.Core.Datastore.Migration
|
|||||||
.WithColumn("Ratings").AsString().Nullable()
|
.WithColumn("Ratings").AsString().Nullable()
|
||||||
.WithColumn("Aliases").AsString().WithDefaultValue("[]");
|
.WithColumn("Aliases").AsString().WithDefaultValue("[]");
|
||||||
|
|
||||||
Create.TableForModel("Books")
|
|
||||||
.WithColumn("AuthorMetadataId").AsInt32().WithDefaultValue(0)
|
|
||||||
.WithColumn("ForeignBookId").AsString().Indexed()
|
|
||||||
.WithColumn("TitleSlug").AsString().Unique()
|
|
||||||
.WithColumn("Title").AsString()
|
|
||||||
.WithColumn("ReleaseDate").AsDateTime().Nullable()
|
|
||||||
.WithColumn("Links").AsString().Nullable()
|
|
||||||
.WithColumn("Genres").AsString().Nullable()
|
|
||||||
.WithColumn("Ratings").AsString().Nullable()
|
|
||||||
.WithColumn("CleanTitle").AsString().Indexed()
|
|
||||||
.WithColumn("Monitored").AsBoolean()
|
|
||||||
.WithColumn("AnyEditionOk").AsBoolean()
|
|
||||||
.WithColumn("LastInfoSync").AsDateTime().Nullable()
|
|
||||||
.WithColumn("Added").AsDateTime().Nullable()
|
|
||||||
.WithColumn("AddOptions").AsString().Nullable();
|
|
||||||
|
|
||||||
Create.TableForModel("Editions")
|
Create.TableForModel("Editions")
|
||||||
.WithColumn("BookId").AsInt32().WithDefaultValue(0)
|
.WithColumn("BookId").AsInt32().WithDefaultValue(0)
|
||||||
.WithColumn("ForeignEditionId").AsString().Unique()
|
.WithColumn("ForeignEditionId").AsString().Unique()
|
||||||
@@ -136,12 +136,12 @@ namespace NzbDrone.Core.Datastore.Migration
|
|||||||
.WithColumn("OnUpgrade").AsBoolean().Nullable()
|
.WithColumn("OnUpgrade").AsBoolean().Nullable()
|
||||||
.WithColumn("Tags").AsString().Nullable()
|
.WithColumn("Tags").AsString().Nullable()
|
||||||
.WithColumn("OnRename").AsBoolean().NotNullable()
|
.WithColumn("OnRename").AsBoolean().NotNullable()
|
||||||
.WithColumn("OnReleaseImport").AsBoolean().WithDefaultValue(0)
|
.WithColumn("OnReleaseImport").AsBoolean().WithDefaultValue(false)
|
||||||
.WithColumn("OnHealthIssue").AsBoolean().WithDefaultValue(0)
|
.WithColumn("OnHealthIssue").AsBoolean().WithDefaultValue(false)
|
||||||
.WithColumn("IncludeHealthWarnings").AsBoolean().WithDefaultValue(0)
|
.WithColumn("IncludeHealthWarnings").AsBoolean().WithDefaultValue(false)
|
||||||
.WithColumn("OnDownloadFailure").AsBoolean().WithDefaultValue(0)
|
.WithColumn("OnDownloadFailure").AsBoolean().WithDefaultValue(false)
|
||||||
.WithColumn("OnImportFailure").AsBoolean().WithDefaultValue(0)
|
.WithColumn("OnImportFailure").AsBoolean().WithDefaultValue(false)
|
||||||
.WithColumn("OnTrackRetag").AsBoolean().WithDefaultValue(0);
|
.WithColumn("OnTrackRetag").AsBoolean().WithDefaultValue(false);
|
||||||
|
|
||||||
Create.TableForModel("ScheduledTasks")
|
Create.TableForModel("ScheduledTasks")
|
||||||
.WithColumn("TypeName").AsString().Unique()
|
.WithColumn("TypeName").AsString().Unique()
|
||||||
@@ -327,8 +327,8 @@ namespace NzbDrone.Core.Datastore.Migration
|
|||||||
.WithColumn("Label").AsString().NotNullable()
|
.WithColumn("Label").AsString().NotNullable()
|
||||||
.WithColumn("Filters").AsString().NotNullable();
|
.WithColumn("Filters").AsString().NotNullable();
|
||||||
|
|
||||||
Create.Index().OnTable("Books").OnColumn("AuthorId");
|
IfDatabase("sqlite").Create.Index().OnTable("Books").OnColumn("AuthorId");
|
||||||
Create.Index().OnTable("Books").OnColumn("AuthorId").Ascending()
|
IfDatabase("sqlite").Create.Index().OnTable("Books").OnColumn("AuthorId").Ascending()
|
||||||
.OnColumn("ReleaseDate").Ascending();
|
.OnColumn("ReleaseDate").Ascending();
|
||||||
|
|
||||||
Delete.Index().OnTable("History").OnColumn("BookId");
|
Delete.Index().OnTable("History").OnColumn("BookId");
|
||||||
@@ -340,12 +340,15 @@ namespace NzbDrone.Core.Datastore.Migration
|
|||||||
.OnColumn("Date").Descending();
|
.OnColumn("Date").Descending();
|
||||||
|
|
||||||
Create.Index().OnTable("Authors").OnColumn("Monitored").Ascending();
|
Create.Index().OnTable("Authors").OnColumn("Monitored").Ascending();
|
||||||
|
|
||||||
Create.Index().OnTable("Books").OnColumn("AuthorMetadataId").Ascending();
|
Create.Index().OnTable("Books").OnColumn("AuthorMetadataId").Ascending();
|
||||||
|
Create.Index().OnTable("Books").OnColumn("AuthorMetadataId").Ascending()
|
||||||
|
.OnColumn("ReleaseDate").Ascending();
|
||||||
|
|
||||||
Insert.IntoTable("DelayProfiles").Row(new
|
Insert.IntoTable("DelayProfiles").Row(new
|
||||||
{
|
{
|
||||||
EnableUsenet = 1,
|
EnableUsenet = true,
|
||||||
EnableTorrent = 1,
|
EnableTorrent = true,
|
||||||
PreferredProtocol = 1,
|
PreferredProtocol = 1,
|
||||||
UsenetDelay = 0,
|
UsenetDelay = 0,
|
||||||
TorrentDelay = 0,
|
TorrentDelay = 0,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
|||||||
{
|
{
|
||||||
protected override void MainDbUpgrade()
|
protected override void MainDbUpgrade()
|
||||||
{
|
{
|
||||||
Alter.Table("ImportLists").AddColumn("ShouldSearch").AsInt32().WithDefaultValue(1);
|
Alter.Table("ImportLists").AddColumn("ShouldSearch").AsBoolean().WithDefaultValue(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
|||||||
{
|
{
|
||||||
protected override void MainDbUpgrade()
|
protected override void MainDbUpgrade()
|
||||||
{
|
{
|
||||||
Execute.Sql("DELETE FROM config WHERE Key IN ('folderchmod', 'chownuser')");
|
Execute.Sql("DELETE FROM \"Config\" WHERE \"Key\" IN ('folderchmod', 'chownuser')");
|
||||||
Execute.WithConnection(ConvertFileChmodToFolderChmod);
|
Execute.WithConnection(ConvertFileChmodToFolderChmod);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
|||||||
using (IDbCommand getFileChmodCmd = conn.CreateCommand())
|
using (IDbCommand getFileChmodCmd = conn.CreateCommand())
|
||||||
{
|
{
|
||||||
getFileChmodCmd.Transaction = tran;
|
getFileChmodCmd.Transaction = tran;
|
||||||
getFileChmodCmd.CommandText = @"SELECT Value FROM Config WHERE Key = 'filechmod'";
|
getFileChmodCmd.CommandText = @"SELECT ""Value"" FROM ""Config"" WHERE ""Key"" = 'filechmod'";
|
||||||
|
|
||||||
var fileChmod = getFileChmodCmd.ExecuteScalar() as string;
|
var fileChmod = getFileChmodCmd.ExecuteScalar() as string;
|
||||||
if (fileChmod != null)
|
if (fileChmod != null)
|
||||||
@@ -35,7 +35,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
|||||||
using (IDbCommand insertCmd = conn.CreateCommand())
|
using (IDbCommand insertCmd = conn.CreateCommand())
|
||||||
{
|
{
|
||||||
insertCmd.Transaction = tran;
|
insertCmd.Transaction = tran;
|
||||||
insertCmd.CommandText = "INSERT INTO Config (Key, Value) VALUES ('chmodfolder', ?)";
|
insertCmd.CommandText = "INSERT INTO \"Config\" (\"Key\", \"Value\") VALUES ('chmodfolder', ?)";
|
||||||
insertCmd.AddParameter(folderChmod);
|
insertCmd.AddParameter(folderChmod);
|
||||||
|
|
||||||
insertCmd.ExecuteNonQuery();
|
insertCmd.ExecuteNonQuery();
|
||||||
@@ -45,7 +45,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
|||||||
using (IDbCommand deleteCmd = conn.CreateCommand())
|
using (IDbCommand deleteCmd = conn.CreateCommand())
|
||||||
{
|
{
|
||||||
deleteCmd.Transaction = tran;
|
deleteCmd.Transaction = tran;
|
||||||
deleteCmd.CommandText = "DELETE FROM Config WHERE Key = 'filechmod'";
|
deleteCmd.CommandText = "DELETE FROM \"Config\" WHERE \"Key\" = 'filechmod'";
|
||||||
|
|
||||||
deleteCmd.ExecuteNonQuery();
|
deleteCmd.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
|||||||
{
|
{
|
||||||
protected override void MainDbUpgrade()
|
protected override void MainDbUpgrade()
|
||||||
{
|
{
|
||||||
Execute.Sql("UPDATE Notifications SET Implementation = Replace(Implementation, 'DiscordNotifier', 'Notifiarr'),ConfigContract = Replace(ConfigContract, 'DiscordNotifierSettings', 'NotifiarrSettings') WHERE Implementation = 'DiscordNotifier';");
|
Execute.Sql("UPDATE \"Notifications\" SET \"Implementation\" = Replace(\"Implementation\", 'DiscordNotifier', 'Notifiarr'),\"ConfigContract\" = Replace(\"ConfigContract\", 'DiscordNotifierSettings', 'NotifiarrSettings') WHERE \"Implementation\" = 'DiscordNotifier';");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ namespace NzbDrone.Core.Datastore.Migration
|
|||||||
|
|
||||||
private void MigrateAuthorSortName(IDbConnection conn, IDbTransaction tran)
|
private void MigrateAuthorSortName(IDbConnection conn, IDbTransaction tran)
|
||||||
{
|
{
|
||||||
var rows = conn.Query<AuthorName>("SELECT AuthorMetadata.Id, AuthorMetadata.Name FROM AuthorMetadata", transaction: tran);
|
var rows = conn.Query<AuthorName>("SELECT \"AuthorMetadata\".\"Id\", \"AuthorMetadata\".\"Name\" FROM \"AuthorMetadata\"", transaction: tran);
|
||||||
|
|
||||||
foreach (var row in rows)
|
foreach (var row in rows)
|
||||||
{
|
{
|
||||||
row.SortName = row.Name.ToLastFirst().ToLower();
|
row.SortName = row.Name.ToLastFirst().ToLower();
|
||||||
}
|
}
|
||||||
|
|
||||||
var sql = "UPDATE AuthorMetadata SET SortName = @SortName WHERE Id = @Id";
|
var sql = "UPDATE \"AuthorMetadata\" SET \"SortName\" = @SortName WHERE \"Id\" = @Id";
|
||||||
conn.Execute(sql, rows, transaction: tran);
|
conn.Execute(sql, rows, transaction: tran);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,13 +54,13 @@ namespace NzbDrone.Core.Datastore.Migration
|
|||||||
_connection = conn;
|
_connection = conn;
|
||||||
_transaction = tran;
|
_transaction = tran;
|
||||||
|
|
||||||
_profiles = _connection.Query<Profile10>(@"SELECT Id, Name, Cutoff, Items FROM QualityProfiles",
|
_profiles = _connection.Query<Profile10>(@"SELECT ""Id"", ""Name"", ""Cutoff"", ""Items"" FROM ""QualityProfiles""",
|
||||||
transaction: _transaction).ToList();
|
transaction: _transaction).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Commit()
|
public void Commit()
|
||||||
{
|
{
|
||||||
var sql = "UPDATE QualityProfiles SET Name = @Name, Cutoff = @Cutoff, Items = @Items WHERE Id = @Id";
|
var sql = "UPDATE \"QualityProfiles\" SET \"Name\" = @Name, \"Cutoff\" = @Cutoff, \"Items\" = @Items WHERE \"Id\" = @Id";
|
||||||
_connection.Execute(sql, _changedProfiles, transaction: _transaction);
|
_connection.Execute(sql, _changedProfiles, transaction: _transaction);
|
||||||
|
|
||||||
_changedProfiles.Clear();
|
_changedProfiles.Clear();
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
|||||||
{
|
{
|
||||||
protected override void MainDbUpgrade()
|
protected override void MainDbUpgrade()
|
||||||
{
|
{
|
||||||
Execute.Sql("UPDATE NamingConfig SET StandardBookFormat = StandardBookFormat || '{ (PartNumber)}'");
|
Execute.Sql("UPDATE \"NamingConfig\" SET \"StandardBookFormat\" = \"StandardBookFormat\" || '{ (PartNumber)}'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
|||||||
|
|
||||||
private void MigrateAuthorSortName(IDbConnection conn, IDbTransaction tran)
|
private void MigrateAuthorSortName(IDbConnection conn, IDbTransaction tran)
|
||||||
{
|
{
|
||||||
var rows = conn.Query<AuthorName>("SELECT AuthorMetadata.Id, AuthorMetadata.Name FROM AuthorMetadata", transaction: tran);
|
var rows = conn.Query<AuthorName>("SELECT \"AuthorMetadata\".\"Id\", \"AuthorMetadata\".\"Name\" FROM \"AuthorMetadata\"", transaction: tran);
|
||||||
|
|
||||||
foreach (var row in rows)
|
foreach (var row in rows)
|
||||||
{
|
{
|
||||||
@@ -30,7 +30,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
|||||||
row.SortNameLastFirst = row.Name.ToLastFirst().ToLower();
|
row.SortNameLastFirst = row.Name.ToLastFirst().ToLower();
|
||||||
}
|
}
|
||||||
|
|
||||||
var sql = "UPDATE AuthorMetadata SET NameLastFirst = @NameLastFirst, SortName = @SortName, SortNameLastFirst = @SortNameLastFirst WHERE Id = @Id";
|
var sql = "UPDATE \"AuthorMetadata\" SET \"NameLastFirst\" = @NameLastFirst, \"SortName\" = @SortName, \"SortNameLastFirst\" = @SortNameLastFirst WHERE \"Id\" = @Id";
|
||||||
conn.Execute(sql, rows, transaction: tran);
|
conn.Execute(sql, rows, transaction: tran);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ namespace NzbDrone.Core.Datastore.Migration
|
|||||||
{
|
{
|
||||||
protected override void MainDbUpgrade()
|
protected override void MainDbUpgrade()
|
||||||
{
|
{
|
||||||
Delete.Index().OnTable("Books").OnColumn("AuthorId");
|
IfDatabase("sqlite").Delete.Index().OnTable("Books").OnColumn("AuthorId");
|
||||||
Delete.Index().OnTable("Books").OnColumns("AuthorId", "ReleaseDate");
|
IfDatabase("sqlite").Delete.Index().OnTable("Books").OnColumns("AuthorId", "ReleaseDate");
|
||||||
|
|
||||||
Create.Index().OnTable("Editions").OnColumn("BookId");
|
Create.Index().OnTable("Editions").OnColumn("BookId");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
|||||||
{
|
{
|
||||||
protected override void MainDbUpgrade()
|
protected override void MainDbUpgrade()
|
||||||
{
|
{
|
||||||
Alter.Table("ImportLists").AddColumn("ShouldMonitorExisting").AsInt32().WithDefaultValue(0);
|
Alter.Table("ImportLists").AddColumn("ShouldMonitorExisting").AsBoolean().WithDefaultValue(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user