mirror of
https://github.com/Readarr/Readarr.git
synced 2026-03-07 13:40:02 -05:00
Compare commits
3 Commits
sonarr-pul
...
changelog-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0b7a78568 | ||
|
|
bc31f10770 | ||
|
|
98ca518178 |
@@ -15,7 +15,7 @@ variables:
|
||||
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
|
||||
sentryOrg: 'servarr'
|
||||
sentryUrl: 'https://sentry.servarr.com'
|
||||
dotnetVersion: '6.0.302'
|
||||
dotnetVersion: '6.0.201'
|
||||
innoVersion: '6.2.0'
|
||||
windowsImage: 'windows-2022'
|
||||
linuxImage: 'ubuntu-20.04'
|
||||
@@ -66,13 +66,16 @@ stages:
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
- bash: |
|
||||
SDK_PATH="${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}"
|
||||
BUNDLEDVERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
|
||||
|
||||
if ! grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
||||
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
|
||||
BUNDLEDVERSIONS=${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}/Microsoft.NETCoreSdk.BundledVersions.props
|
||||
echo $BUNDLEDVERSIONS
|
||||
grep osx-x64 $BUNDLEDVERSIONS
|
||||
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
||||
echo "BSD already enabled"
|
||||
else
|
||||
echo "Enabling BSD support"
|
||||
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64/' $BUNDLEDVERSIONS
|
||||
fi
|
||||
displayName: Extra Platform Support
|
||||
displayName: Enable FreeBSD Support
|
||||
- task: Cache@2
|
||||
inputs:
|
||||
key: 'nuget | "$(Agent.OS)" | $(Build.SourcesDirectory)/src/Directory.Packages.props'
|
||||
@@ -84,27 +87,29 @@ stages:
|
||||
NUGET_PACKAGES: $(nugetCacheFolder)
|
||||
- powershell: Get-ChildItem _output\net6.0*,_output\*.Update\* -Recurse | Where { $_.Fullname -notlike "*\publish\*" -and $_.attributes -notlike "*directory*" } | Remove-Item
|
||||
displayName: Clean up intermediate output
|
||||
- publish: $(outputFolder)
|
||||
artifact: '$(osName)Backend'
|
||||
- task: PublishPipelineArtifact@1
|
||||
inputs:
|
||||
path: $(outputFolder)
|
||||
artifact: '$(osName)Backend'
|
||||
artifactType: 'pipeline'
|
||||
parallel: true
|
||||
parallelCount: 100
|
||||
displayName: Publish Backend
|
||||
- publish: '$(testsFolder)/net6.0/win-x64/publish'
|
||||
artifact: win-x64-tests
|
||||
displayName: Publish win-x64 Test Package
|
||||
artifact: WindowsCoreTests
|
||||
displayName: Publish Windows Test Package
|
||||
- publish: '$(testsFolder)/net6.0/linux-x64/publish'
|
||||
artifact: linux-x64-tests
|
||||
displayName: Publish linux-x64 Test Package
|
||||
- publish: '$(testsFolder)/net6.0/linux-x86/publish'
|
||||
artifact: linux-x86-tests
|
||||
displayName: Publish linux-x86 Test Package
|
||||
artifact: LinuxCoreTests
|
||||
displayName: Publish Linux Test Package
|
||||
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
|
||||
artifact: linux-musl-x64-tests
|
||||
displayName: Publish linux-musl-x64 Test Package
|
||||
artifact: LinuxMuslCoreTests
|
||||
displayName: Publish Linux Musl Test Package
|
||||
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish'
|
||||
artifact: freebsd-x64-tests
|
||||
displayName: Publish freebsd-x64 Test Package
|
||||
artifact: FreebsdCoreTests
|
||||
displayName: Publish FreeBSD Test Package
|
||||
- publish: '$(testsFolder)/net6.0/osx-x64/publish'
|
||||
artifact: osx-x64-tests
|
||||
displayName: Publish osx-x64 Test Package
|
||||
artifact: MacCoreTests
|
||||
displayName: Publish MacOS Test Package
|
||||
|
||||
- stage: Build_Backend_Other
|
||||
displayName: Build Backend (Other OS)
|
||||
@@ -136,29 +141,25 @@ stages:
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
- bash: |
|
||||
SDK_PATH="${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}"
|
||||
BUNDLEDVERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
|
||||
|
||||
if ! grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
||||
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
|
||||
BUNDLEDVERSIONS=${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}/Microsoft.NETCoreSdk.BundledVersions.props
|
||||
echo $BUNDLEDVERSIONS
|
||||
grep osx-x64 $BUNDLEDVERSIONS
|
||||
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
||||
echo "BSD already enabled"
|
||||
else
|
||||
echo "Enabling BSD support"
|
||||
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64/' $BUNDLEDVERSIONS
|
||||
fi
|
||||
displayName: Extra Platform Support
|
||||
displayName: Enable FreeBSD Support
|
||||
- task: Cache@2
|
||||
inputs:
|
||||
key: 'nuget | "$(Agent.OS)" | $(Build.SourcesDirectory)/src/Directory.Packages.props'
|
||||
path: $(nugetCacheFolder)
|
||||
displayName: Cache NuGet packages
|
||||
- bash: ./build.sh --backend --enable-extra-platforms
|
||||
- bash: ./build.sh --backend --enable-bsd
|
||||
displayName: Build Readarr Backend
|
||||
env:
|
||||
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
|
||||
displayName: Frontend
|
||||
@@ -261,35 +262,35 @@ stages:
|
||||
artifactName: WindowsFrontend
|
||||
targetPath: _output
|
||||
displayName: Fetch Frontend
|
||||
- bash: ./build.sh --packages --enable-extra-platforms
|
||||
- bash: ./build.sh --packages --enable-bsd
|
||||
displayName: Create Packages
|
||||
- bash: |
|
||||
find . -name "Readarr" -exec chmod a+x {} \;
|
||||
find . -name "Readarr.Update" -exec chmod a+x {} \;
|
||||
displayName: Set executable bits
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create win-x64 zip
|
||||
displayName: Create Windows Core zip
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).windows-core-x64.zip'
|
||||
archiveType: 'zip'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/win-x64/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create win-x86 zip
|
||||
displayName: Create Windows x86 Core zip
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).windows-core-x86.zip'
|
||||
archiveType: 'zip'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/win-x86/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create osx-x64 app
|
||||
displayName: Create MacOS x64 Core app
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).osx-app-core-x64.zip'
|
||||
archiveType: 'zip'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/osx-x64-app/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create osx-x64 tar
|
||||
displayName: Create MacOS x64 Core tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).osx-core-x64.tar.gz'
|
||||
archiveType: 'tar'
|
||||
@@ -297,14 +298,14 @@ stages:
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/osx-x64/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create osx-arm64 app
|
||||
displayName: Create MacOS arm64 Core app
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).osx-app-core-arm64.zip'
|
||||
archiveType: 'zip'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/osx-arm64-app/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create osx-arm64 tar
|
||||
displayName: Create MacOS arm64 Core tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).osx-core-arm64.tar.gz'
|
||||
archiveType: 'tar'
|
||||
@@ -312,7 +313,7 @@ stages:
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/osx-arm64/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create linux-x64 tar
|
||||
displayName: Create Linux Core tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).linux-core-x64.tar.gz'
|
||||
archiveType: 'tar'
|
||||
@@ -320,7 +321,7 @@ stages:
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-x64/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create linux-musl-x64 tar
|
||||
displayName: Create Linux Musl Core tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).linux-musl-core-x64.tar.gz'
|
||||
archiveType: 'tar'
|
||||
@@ -328,15 +329,7 @@ stages:
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
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
|
||||
displayName: Create ARM32 Linux Core tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).linux-core-arm.tar.gz'
|
||||
archiveType: 'tar'
|
||||
@@ -344,7 +337,7 @@ stages:
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-arm/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create linux-musl-arm tar
|
||||
displayName: Create ARM32 Linux Musl Core tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).linux-musl-core-arm.tar.gz'
|
||||
archiveType: 'tar'
|
||||
@@ -352,7 +345,7 @@ stages:
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create linux-arm64 tar
|
||||
displayName: Create Linux arm64 Core tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).linux-core-arm64.tar.gz'
|
||||
archiveType: 'tar'
|
||||
@@ -360,7 +353,7 @@ stages:
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create linux-musl-arm64 tar
|
||||
displayName: Create ARM64 Linux Musl Core tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).linux-musl-core-arm64.tar.gz'
|
||||
archiveType: 'tar'
|
||||
@@ -368,7 +361,7 @@ stages:
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create freebsd-x64 tar
|
||||
displayName: Create FreeBSD Core Core tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).freebsd-core-x64.tar.gz'
|
||||
archiveType: 'tar'
|
||||
@@ -420,22 +413,22 @@ stages:
|
||||
matrix:
|
||||
MacCore:
|
||||
osName: 'Mac'
|
||||
testName: 'osx-x64'
|
||||
testName: 'MacCore'
|
||||
poolName: 'Azure Pipelines'
|
||||
imageName: ${{ variables.macImage }}
|
||||
WindowsCore:
|
||||
osName: 'Windows'
|
||||
testName: 'win-x64'
|
||||
testName: 'WindowsCore'
|
||||
poolName: 'Azure Pipelines'
|
||||
imageName: ${{ variables.windowsImage }}
|
||||
LinuxCore:
|
||||
osName: 'Linux'
|
||||
testName: 'linux-x64'
|
||||
testName: 'LinuxCore'
|
||||
poolName: 'Azure Pipelines'
|
||||
imageName: ${{ variables.linuxImage }}
|
||||
FreebsdCore:
|
||||
osName: 'Linux'
|
||||
testName: 'freebsd-x64'
|
||||
testName: 'FreebsdCore'
|
||||
poolName: 'FreeBSD'
|
||||
imageName:
|
||||
|
||||
@@ -454,7 +447,7 @@ stages:
|
||||
displayName: Download Test Artifact
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: '$(testName)-tests'
|
||||
artifactName: '$(testName)Tests'
|
||||
targetPath: $(testsFolder)
|
||||
- powershell: Set-Service SCardSvr -StartupType Manual
|
||||
displayName: Enable Windows Test Service
|
||||
@@ -482,12 +475,8 @@ stages:
|
||||
matrix:
|
||||
alpine:
|
||||
testName: 'Musl Net Core'
|
||||
artifactName: linux-musl-x64-tests
|
||||
artifactName: LinuxMuslCoreTests
|
||||
containerImage: ghcr.io/servarr/testimages:alpine
|
||||
linux-x86:
|
||||
testName: 'linux-x86'
|
||||
artifactName: linux-x86-tests
|
||||
containerImage: ghcr.io/servarr/testimages:linux-x86
|
||||
|
||||
pool:
|
||||
vmImage: ${{ variables.linuxImage }}
|
||||
@@ -498,15 +487,9 @@ stages:
|
||||
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Install .NET'
|
||||
displayName: 'Install .net core'
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
condition: and(succeeded(), ne(variables['testName'], 'linux-x86'))
|
||||
- bash: |
|
||||
SDKURL=$(curl -s https://api.github.com/repos/Servarr/dotnet-linux-x86/releases | jq -rc '.[].assets[].browser_download_url' | grep sdk-${DOTNETVERSION}.*gz$)
|
||||
curl -fsSL $SDKURL | tar xzf - -C /opt/dotnet
|
||||
displayName: 'Install .NET'
|
||||
condition: and(succeeded(), eq(variables['testName'], 'linux-x86'))
|
||||
- checkout: none
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Test Artifact
|
||||
@@ -529,57 +512,6 @@ stages:
|
||||
testResultsFiles: '**/TestResult.xml'
|
||||
testRunTitle: '$(testName) Unit Tests'
|
||||
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
|
||||
displayName: Integration
|
||||
@@ -591,17 +523,17 @@ stages:
|
||||
matrix:
|
||||
MacCore:
|
||||
osName: 'Mac'
|
||||
testName: 'osx-x64'
|
||||
testName: 'MacCore'
|
||||
imageName: ${{ variables.macImage }}
|
||||
pattern: 'Readarr.*.osx-core-x64.tar.gz'
|
||||
WindowsCore:
|
||||
osName: 'Windows'
|
||||
testName: 'win-x64'
|
||||
testName: 'WindowsCore'
|
||||
imageName: ${{ variables.windowsImage }}
|
||||
pattern: 'Readarr.*.windows-core-x64.zip'
|
||||
LinuxCore:
|
||||
osName: 'Linux'
|
||||
testName: 'linux-x64'
|
||||
testName: 'LinuxCore'
|
||||
imageName: ${{ variables.linuxImage }}
|
||||
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
||||
|
||||
@@ -618,7 +550,7 @@ stages:
|
||||
displayName: Download Test Artifact
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: '$(testName)-tests'
|
||||
artifactName: '$(testName)Tests'
|
||||
targetPath: $(testsFolder)
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Build Artifact
|
||||
@@ -648,66 +580,6 @@ stages:
|
||||
failTaskOnFailedTests: true
|
||||
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
|
||||
displayName: Integration Native FreeBSD
|
||||
workspace:
|
||||
@@ -723,7 +595,7 @@ stages:
|
||||
displayName: Download Test Artifact
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: 'freebsd-x64-tests'
|
||||
artifactName: 'FreebsdCoreTests'
|
||||
targetPath: $(testsFolder)
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Build Artifact
|
||||
@@ -757,15 +629,11 @@ stages:
|
||||
strategy:
|
||||
matrix:
|
||||
alpine:
|
||||
testName: 'linux-musl-x64'
|
||||
artifactName: linux-musl-x64-tests
|
||||
testName: 'Musl Net Core'
|
||||
artifactName: LinuxMuslCoreTests
|
||||
containerImage: ghcr.io/servarr/testimages:alpine
|
||||
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:
|
||||
vmImage: ${{ variables.linuxImage }}
|
||||
|
||||
@@ -775,15 +643,9 @@ stages:
|
||||
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Install .NET'
|
||||
displayName: 'Install .net core'
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
condition: and(succeeded(), ne(variables['testName'], 'linux-x86'))
|
||||
- bash: |
|
||||
SDKURL=$(curl -s https://api.github.com/repos/Servarr/dotnet-linux-x86/releases | jq -rc '.[].assets[].browser_download_url' | grep sdk-${DOTNETVERSION}.*gz$)
|
||||
curl -fsSL $SDKURL | tar xzf - -C /opt/dotnet
|
||||
displayName: 'Install .NET'
|
||||
condition: and(succeeded(), eq(variables['testName'], 'linux-x86'))
|
||||
- checkout: none
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Test Artifact
|
||||
@@ -829,17 +691,14 @@ stages:
|
||||
matrix:
|
||||
Linux:
|
||||
osName: 'Linux'
|
||||
artifactName: 'linux-x64'
|
||||
imageName: ${{ variables.linuxImage }}
|
||||
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
||||
Mac:
|
||||
osName: 'Mac'
|
||||
artifactName: 'osx-x64'
|
||||
imageName: ${{ variables.macImage }}
|
||||
pattern: 'Readarr.*.osx-core-x64.tar.gz'
|
||||
Windows:
|
||||
osName: 'Windows'
|
||||
artifactName: 'win-x64'
|
||||
imageName: ${{ variables.windowsImage }}
|
||||
pattern: 'Readarr.*.windows-core-x64.zip'
|
||||
|
||||
@@ -856,7 +715,7 @@ stages:
|
||||
displayName: Download Test Artifact
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: '$(artifactName)-tests'
|
||||
artifactName: '$(osName)CoreTests'
|
||||
targetPath: $(testsFolder)
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Build Artifact
|
||||
|
||||
45
build.sh
45
build.sh
@@ -27,22 +27,15 @@ UpdateVersionNumber()
|
||||
fi
|
||||
}
|
||||
|
||||
EnableExtraPlatformsInSDK()
|
||||
EnableBsdSupport()
|
||||
{
|
||||
SDK_PATH=$(dotnet --list-sdks | grep -P '6\.\d\.\d+' | head -1 | sed 's/\(6\.[0-9]*\.[0-9]*\).*\[\(.*\)\]/\2\/\1/g')
|
||||
BUNDLEDVERSIONS="${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
|
||||
}
|
||||
#todo enable sdk with
|
||||
#SDK_PATH=$(dotnet --list-sdks | grep -P '5\.\d\.\d+' | head -1 | sed 's/\(5\.[0-9]*\.[0-9]*\).*\[\(.*\)\]/\2\/\1/g')
|
||||
# BUNDLED_VERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
|
||||
|
||||
EnableExtraPlatforms()
|
||||
{
|
||||
if grep -qv freebsd-x64 src/Directory.Build.props; then
|
||||
sed -i'' -e "s^<RuntimeIdentifiers>\(.*\)</RuntimeIdentifiers>^<RuntimeIdentifiers>\1;freebsd-x64;linux-x86</RuntimeIdentifiers>^g" src/Directory.Build.props
|
||||
sed -i'' -e "s^<RuntimeIdentifiers>\(.*\)</RuntimeIdentifiers>^<RuntimeIdentifiers>\1;freebsd-x64</RuntimeIdentifiers>^g" src/Directory.Build.props
|
||||
sed -i'' -e "s^<ExcludedRuntimeFrameworkPairs>\(.*\)</ExcludedRuntimeFrameworkPairs>^<ExcludedRuntimeFrameworkPairs>\1;freebsd-x64:net472</ExcludedRuntimeFrameworkPairs>^g" src/Directory.Build.props
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -299,8 +292,7 @@ if [ $# -eq 0 ]; then
|
||||
PACKAGES=YES
|
||||
INSTALLER=NO
|
||||
LINT=YES
|
||||
ENABLE_EXTRA_PLATFORMS=NO
|
||||
ENABLE_EXTRA_PLATFORMS_IN_SDK=NO
|
||||
ENABLE_BSD=NO
|
||||
fi
|
||||
|
||||
while [[ $# -gt 0 ]]
|
||||
@@ -312,12 +304,8 @@ case $key in
|
||||
BACKEND=YES
|
||||
shift # past argument
|
||||
;;
|
||||
--enable-bsd|--enable-extra-platforms)
|
||||
ENABLE_EXTRA_PLATFORMS=YES
|
||||
shift # past argument
|
||||
;;
|
||||
--enable-extra-platforms-in-sdk)
|
||||
ENABLE_EXTRA_PLATFORMS_IN_SDK=YES
|
||||
--enable-bsd)
|
||||
ENABLE_BSD=YES
|
||||
shift # past argument
|
||||
;;
|
||||
-r|--runtime)
|
||||
@@ -361,17 +349,12 @@ esac
|
||||
done
|
||||
set -- "${POSITIONAL[@]}" # restore positional parameters
|
||||
|
||||
if [ "$ENABLE_EXTRA_PLATFORMS_IN_SDK" = "YES" ];
|
||||
then
|
||||
EnableExtraPlatformsInSDK
|
||||
fi
|
||||
|
||||
if [ "$BACKEND" = "YES" ];
|
||||
then
|
||||
UpdateVersionNumber
|
||||
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
|
||||
if [ "$ENABLE_BSD" = "YES" ];
|
||||
then
|
||||
EnableExtraPlatforms
|
||||
EnableBsdSupport
|
||||
fi
|
||||
Build
|
||||
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
|
||||
@@ -381,10 +364,9 @@ then
|
||||
PackageTests "net6.0" "linux-x64"
|
||||
PackageTests "net6.0" "linux-musl-x64"
|
||||
PackageTests "net6.0" "osx-x64"
|
||||
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
|
||||
if [ "$ENABLE_BSD" = "YES" ];
|
||||
then
|
||||
PackageTests "net6.0" "freebsd-x64"
|
||||
PackageTests "net6.0" "linux-x86"
|
||||
fi
|
||||
else
|
||||
PackageTests "$FRAMEWORK" "$RID"
|
||||
@@ -423,10 +405,9 @@ then
|
||||
Package "net6.0" "linux-musl-arm"
|
||||
Package "net6.0" "osx-x64"
|
||||
Package "net6.0" "osx-arm64"
|
||||
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
|
||||
if [ "$ENABLE_BSD" = "YES" ];
|
||||
then
|
||||
Package "net6.0" "freebsd-x64"
|
||||
Package "net6.0" "linux-x86"
|
||||
fi
|
||||
else
|
||||
Package "$FRAMEWORK" "$RID"
|
||||
|
||||
94
changelogs/CHANGELOG-v0.1.1.1320.md
Normal file
94
changelogs/CHANGELOG-v0.1.1.1320.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# New Beta Release
|
||||
|
||||
Readarr v0.1.1.1320 has been released on `develop`
|
||||
|
||||
- **Users who do not wish to be on the alpha `nightly` testing branch should take advantage of this parity and switch to `develop`**
|
||||
|
||||
A reminder about the `develop` and `nightly` branches
|
||||
|
||||
- **develop** - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first after nightly. It can be considered semi-stable, but is still beta.
|
||||
- **nightly** - Current Nightly/Unstable - (Alpha/Unstable) : This is the bleeding edge. It is released as soon as code is committed and passes all automated tests. This build may have not been used by us or other users yet. There is no guarantee that it will even run in some cases. This branch is only recommended for advanced users. Issues and self investigation are expected in this branch. Use this branch only if you know what you are doing and are willing to get your hands dirty to recover a failed update. This version is updated immediately.
|
||||
|
||||
# Announcements
|
||||
|
||||
- Automated API Documentation Updates recently implemented
|
||||
- [Wiki Contributions](https://wiki.servarr.com/readarr) and updates welcome and encouraged on the Wiki itself or via GitHub
|
||||
|
||||
# Additional Commentary
|
||||
|
||||
- [Lidarr v1 released](https://www.reddit.com/r/Lidarr/comments/ul0b2w/new_release_develop_v1012578/)
|
||||
- [Lidarr](https://lidarr.audio/donate), [Prowlarr](https://prowlarr.com/donate), [Radarr](https://radarr.video/donate), [Readarr](https://readarr.com/donate) now accept direct bitcoin donations
|
||||
- Radarr Postgres Database Support in `nightly` and `develop`
|
||||
- [Lidarr Postgres Database Support in development (Draft PR#2625)](https://github.com/Lidarr/Lidarr/pull/2625)
|
||||
|
||||
# Releases
|
||||
|
||||
## Native
|
||||
|
||||
- [GitHub Releases](https://github.com/Readarr/Readarr/releases)
|
||||
|
||||
- [Wiki Installation Instructions](https://wiki.servarr.com/readarr/installation)
|
||||
|
||||
## Docker
|
||||
|
||||
- [hotio/Readarr:testing](https://hotio.dev/containers/readarr)
|
||||
|
||||
- [lscr.io/linuxserver/Readarr:develop](https://docs.linuxserver.io/images/docker-readarr)
|
||||
|
||||
## NAS Packages
|
||||
|
||||
- Synology - Please ask the SynoCommunity to update the base package; however, you can update in-app normally
|
||||
|
||||
- QNAP - Please ask the QNAP to update the base package; however, you should be able to update in-app normally
|
||||
|
||||
------------
|
||||
|
||||
# Release Notes
|
||||
|
||||
## v0.1.1.1320 (changes since v0.1.0.1248)
|
||||
|
||||
- Fixed: Correct User-Agent api logging
|
||||
|
||||
- Fixed: UI hiding search results with duplicate GUIDs
|
||||
|
||||
- New: Add date picker for custom filter dates
|
||||
|
||||
- Fixed: Interactive Search Filter not filtering multiple qualities in the same filter row
|
||||
|
||||
- Fixed: Clarify Qbit Content Path Error
|
||||
|
||||
- Fixed: Properly handle 119 error code from Synology Download Station
|
||||
|
||||
- Fixed: API error when sending payload without optional parameters
|
||||
|
||||
- Fixed: Cleanup Temp files after backup creation
|
||||
|
||||
- Fixed: Loading old commands from database
|
||||
|
||||
- New: Update Cert Validation Help Text
|
||||
|
||||
- Fixed: Clarify Indexer Priority Helptext
|
||||
|
||||
- Fixed: Improve help text for download client Category
|
||||
|
||||
- Fixed: IPv4 instead of IP4
|
||||
|
||||
- New: Add Validations for Recycle Bin Folder
|
||||
|
||||
- New: .NET 6.0.3
|
||||
|
||||
- Fixed: Healthcheck warning message used incorrect variable
|
||||
|
||||
- Fixed: Assume SABnzbd develop version is 3.0.0 if not specified
|
||||
|
||||
- Fixed: Updater version number logging
|
||||
|
||||
- Fixed: Update from version in logs
|
||||
|
||||
- New: Add more information about Windows service to installer
|
||||
|
||||
- Fixed: Recycle bin log message
|
||||
|
||||
- Fixed: Make authentication cookie name unique to Readarr
|
||||
|
||||
- Other bug fixes and improvements, see GitHub history
|
||||
2
changelogs/templates/announcements.md
Normal file
2
changelogs/templates/announcements.md
Normal file
@@ -0,0 +1,2 @@
|
||||
- Automated API Documentation Updates recently implemented
|
||||
- [Wiki Contributions](https://wiki.servarr.com/readarr) and updates welcome and encouraged on the Wiki itself or via GitHub
|
||||
6
changelogs/templates/branch-develop.md
Normal file
6
changelogs/templates/branch-develop.md
Normal file
@@ -0,0 +1,6 @@
|
||||
- **Users who do not wish to be on the alpha `nightly` testing branch should take advantage of this parity and switch to `develop`**
|
||||
|
||||
A reminder about the `develop` and `nightly` branches
|
||||
|
||||
- **develop** - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first after nightly. It can be considered semi-stable, but is still beta.
|
||||
- **nightly** - Current Nightly/Unstable - (Alpha/Unstable) : This is the bleeding edge. It is released as soon as code is committed and passes all automated tests. This build may have not been used by us or other users yet. There is no guarantee that it will even run in some cases. This branch is only recommended for advanced users. Issues and self investigation are expected in this branch. Use this branch only if you know what you are doing and are willing to get your hands dirty to recover a failed update. This version is updated immediately.
|
||||
6
changelogs/templates/branch-master.md
Normal file
6
changelogs/templates/branch-master.md
Normal file
@@ -0,0 +1,6 @@
|
||||
- **Users who do not wish to be on the alpha `nightly` or beta `develop` testing branches should take advantage of this parity and switch to `master`
|
||||
|
||||
A reminder about the `develop` and `nightly` branches
|
||||
|
||||
- **develop** - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first after nightly. It can be considered semi-stable, but is still beta.**
|
||||
- **nightly** - Current Nightly/Unstable - (Alpha/Unstable) : This is the bleeding edge. It is released as soon as code is committed and passes all automated tests. This build may have not been used by us or other users yet. There is no guarantee that it will even run in some cases. This branch is only recommended for advanced users. Issues and self investigation are expected in this branch. Use this branch only if you know what you are doing and are willing to get your hands dirty to recover a failed update. This version is updated immediately.**
|
||||
4
changelogs/templates/commentary.md
Normal file
4
changelogs/templates/commentary.md
Normal file
@@ -0,0 +1,4 @@
|
||||
- [Lidarr v1 released](https://www.reddit.com/r/Lidarr/comments/ul0b2w/new_release_develop_v1012578/)
|
||||
- [Lidarr](https://lidarr.audio/donate), [Prowlarr](https://prowlarr.com/donate), [Radarr](https://radarr.video/donate), [Readarr](https://readarr.com/donate) now accept direct bitcoin donations
|
||||
- Radarr Postgres Database Support in `nightly` and `develop`
|
||||
- [Lidarr Postgres Database Support in development (Draft PR#2625)](https://github.com/Lidarr/Lidarr/pull/2625)
|
||||
@@ -8,17 +8,17 @@ function AuthorMonitorNewItemsOptionsPopoverContent() {
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
title={translate('AllBooks')}
|
||||
data={translate('DataNewAllBooks')}
|
||||
data="Monitor all new books"
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('NewBooks')}
|
||||
data={translate('DataNewBooks')}
|
||||
data="Monitor new books released after the newest existing book"
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('None')}
|
||||
data={translate('DataNewNone')}
|
||||
data="Don't monitor any new books"
|
||||
/>
|
||||
</DescriptionList>
|
||||
);
|
||||
|
||||
@@ -8,42 +8,42 @@ function AuthorMonitoringOptionsPopoverContent() {
|
||||
return (
|
||||
<>
|
||||
<Alert>
|
||||
{translate('MonitoringOptionsHelpText')}
|
||||
This is a one time adjustment to set which books are monitored
|
||||
</Alert>
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
title={translate('AllBooks')}
|
||||
data={translate('DataAllBooks')}
|
||||
data="Monitor all books"
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('FutureBooks')}
|
||||
data={translate('DataFutureBooks')}
|
||||
data="Monitor books that have not released yet"
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('MissingBooks')}
|
||||
data={translate('DataMissingBooks')}
|
||||
data="Monitor books that do not have files or have not released yet"
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('ExistingBooks')}
|
||||
data={translate('DataExistingBooks')}
|
||||
data="Monitor books that have files or have not released yet"
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('FirstBook')}
|
||||
data={translate('DataFirstBook')}
|
||||
data="Monitor the first book. All other books will be ignored"
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('LatestBook')}
|
||||
data={translate('DataLatestBook')}
|
||||
data="Monitor the latest book and future books"
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('None')}
|
||||
data={translate('DataNone')}
|
||||
data="No books will be monitored"
|
||||
/>
|
||||
</DescriptionList>
|
||||
</>
|
||||
|
||||
@@ -8,7 +8,7 @@ import AppRoutes from './AppRoutes';
|
||||
|
||||
function App({ store, history }) {
|
||||
return (
|
||||
<DocumentTitle title={window.Readarr.instanceName}>
|
||||
<DocumentTitle title="Readarr">
|
||||
<Provider store={store}>
|
||||
<ConnectedRouter history={history}>
|
||||
<PageConnector>
|
||||
|
||||
@@ -267,7 +267,7 @@ class AuthorEditorFooter extends Component {
|
||||
isDisabled={!selectedCount || isOrganizingAuthor || isRetaggingAuthor}
|
||||
onPress={onOrganizeAuthorPress}
|
||||
>
|
||||
{translate('RenameFiles')}
|
||||
Rename Files
|
||||
</SpinnerButton>
|
||||
|
||||
<SpinnerButton
|
||||
@@ -277,7 +277,7 @@ class AuthorEditorFooter extends Component {
|
||||
isDisabled={!selectedCount || isOrganizingAuthor || isRetaggingAuthor}
|
||||
onPress={onRetagAuthorPress}
|
||||
>
|
||||
{translate('WriteMetadataTags')}
|
||||
Write Metadata Tags
|
||||
</SpinnerButton>
|
||||
|
||||
<SpinnerButton
|
||||
@@ -286,7 +286,7 @@ class AuthorEditorFooter extends Component {
|
||||
isDisabled={!selectedCount || isOrganizingAuthor || isRetaggingAuthor}
|
||||
onPress={this.onTagsPress}
|
||||
>
|
||||
{translate('SetReadarrTags')}
|
||||
Set Readarr Tags
|
||||
</SpinnerButton>
|
||||
|
||||
<SpinnerButton
|
||||
@@ -296,7 +296,7 @@ class AuthorEditorFooter extends Component {
|
||||
isDisabled={!selectedCount || isDeleting}
|
||||
onPress={this.onDeleteSelectedPress}
|
||||
>
|
||||
{translate('Delete')}
|
||||
Delete
|
||||
</SpinnerButton>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,6 @@ import * as commandNames from 'Commands/commandNames';
|
||||
import { toggleBooksMonitored } from 'Store/Actions/bookActions';
|
||||
import { clearBookFiles, fetchBookFiles } from 'Store/Actions/bookFileActions';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import { clearEditions, fetchEditions } from 'Store/Actions/editionActions';
|
||||
import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions';
|
||||
import createAllAuthorSelector from 'Store/Selectors/createAllAuthorsSelector';
|
||||
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
||||
@@ -44,12 +43,11 @@ function createMapStateToProps() {
|
||||
(state, { titleSlug }) => titleSlug,
|
||||
selectBookFiles,
|
||||
(state) => state.books,
|
||||
(state) => state.editions,
|
||||
createAllAuthorSelector(),
|
||||
createCommandsSelector(),
|
||||
createUISettingsSelector(),
|
||||
createDimensionsSelector(),
|
||||
(titleSlug, bookFiles, books, editions, authors, commands, uiSettings, dimensions) => {
|
||||
(titleSlug, bookFiles, books, authors, commands, uiSettings, dimensions) => {
|
||||
const book = books.items.find((b) => b.titleSlug === titleSlug);
|
||||
const author = authors.find((a) => a.id === book.authorId);
|
||||
const sortedBooks = books.items.filter((b) => b.authorId === book.authorId);
|
||||
@@ -81,8 +79,8 @@ function createMapStateToProps() {
|
||||
isRefreshingCommand.body.bookId === book.id
|
||||
);
|
||||
|
||||
const isFetching = isBookFilesFetching || editions.isFetching;
|
||||
const isPopulated = isBookFilesPopulated && editions.isPopulated;
|
||||
const isFetching = isBookFilesFetching;
|
||||
const isPopulated = isBookFilesPopulated;
|
||||
|
||||
return {
|
||||
...book,
|
||||
@@ -106,8 +104,6 @@ const mapDispatchToProps = {
|
||||
executeCommand,
|
||||
fetchBookFiles,
|
||||
clearBookFiles,
|
||||
fetchEditions,
|
||||
clearEditions,
|
||||
clearReleases,
|
||||
cancelFetchReleases,
|
||||
toggleBooksMonitored
|
||||
@@ -125,8 +121,7 @@ class BookDetailsConnector extends Component {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.id !== this.props.id ||
|
||||
!_.isEqual(getMonitoredEditions(prevProps), getMonitoredEditions(this.props)) ||
|
||||
if (!_.isEqual(getMonitoredEditions(prevProps), getMonitoredEditions(this.props)) ||
|
||||
(prevProps.anyReleaseOk === false && this.props.anyReleaseOk === true)) {
|
||||
this.unpopulate();
|
||||
this.populate();
|
||||
@@ -145,14 +140,12 @@ class BookDetailsConnector extends Component {
|
||||
const bookId = this.props.id;
|
||||
|
||||
this.props.fetchBookFiles({ bookId });
|
||||
this.props.fetchEditions({ bookId });
|
||||
}
|
||||
|
||||
unpopulate = () => {
|
||||
this.props.cancelFetchReleases();
|
||||
this.props.clearReleases();
|
||||
this.props.clearBookFiles();
|
||||
this.props.clearEditions();
|
||||
}
|
||||
|
||||
//
|
||||
@@ -202,8 +195,6 @@ BookDetailsConnector.propTypes = {
|
||||
titleSlug: PropTypes.string.isRequired,
|
||||
fetchBookFiles: PropTypes.func.isRequired,
|
||||
clearBookFiles: PropTypes.func.isRequired,
|
||||
fetchEditions: PropTypes.func.isRequired,
|
||||
clearEditions: PropTypes.func.isRequired,
|
||||
clearReleases: PropTypes.func.isRequired,
|
||||
cancelFetchReleases: PropTypes.func.isRequired,
|
||||
toggleBooksMonitored: PropTypes.func.isRequired,
|
||||
|
||||
@@ -8,25 +8,15 @@ import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
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() {
|
||||
return createSelector(
|
||||
createBookSelector(),
|
||||
selectOverview,
|
||||
createUISettingsSelector(),
|
||||
createDimensionsSelector(),
|
||||
(book, overview, uiSettings, dimensions) => {
|
||||
(book, uiSettings, dimensions) => {
|
||||
|
||||
return {
|
||||
...book,
|
||||
overview,
|
||||
shortDateFormat: uiSettings.shortDateFormat,
|
||||
isSmallScreen: dimensions.isSmallScreen
|
||||
};
|
||||
|
||||
@@ -53,7 +53,7 @@ class EditBookModalContent extends Component {
|
||||
editions
|
||||
} = item;
|
||||
|
||||
const hasFile = statistics ? statistics.bookFileCount > 0 : false;
|
||||
const hasFile = statistics ? statistics.bookFileCount : 0;
|
||||
const errorMessage = getErrorMessage(error, 'Unable to load editions');
|
||||
|
||||
return (
|
||||
|
||||
@@ -4,7 +4,7 @@ import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { saveBook, setBookValue } from 'Store/Actions/bookActions';
|
||||
import { saveEditions } from 'Store/Actions/editionActions';
|
||||
import { clearEditions, fetchEditions } from 'Store/Actions/editionActions';
|
||||
import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
|
||||
import createBookSelector from 'Store/Selectors/createBookSelector';
|
||||
import selectSettings from 'Store/Selectors/selectSettings';
|
||||
@@ -26,14 +26,17 @@ function createMapStateToProps() {
|
||||
const {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error
|
||||
error,
|
||||
items
|
||||
} = editionState;
|
||||
|
||||
book.editions = items;
|
||||
|
||||
const bookSettings = _.pick(book, [
|
||||
'monitored',
|
||||
'anyEditionOk'
|
||||
'anyEditionOk',
|
||||
'editions'
|
||||
]);
|
||||
bookSettings.editions = editionState.items;
|
||||
|
||||
const settings = selectSettings(bookSettings, pendingChanges, saveError);
|
||||
|
||||
@@ -55,9 +58,10 @@ function createMapStateToProps() {
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
dispatchFetchEditions: fetchEditions,
|
||||
dispatchClearEditions: clearEditions,
|
||||
dispatchSetBookValue: setBookValue,
|
||||
dispatchSaveBook: saveBook,
|
||||
dispatchSaveEditions: saveEditions
|
||||
dispatchSaveBook: saveBook
|
||||
};
|
||||
|
||||
class EditBookModalContentConnector extends Component {
|
||||
@@ -65,12 +69,20 @@ class EditBookModalContentConnector extends Component {
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
this.props.dispatchFetchEditions({ bookId: this.props.bookId });
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevProps.isSaving && !this.props.isSaving && !this.props.saveError) {
|
||||
this.props.onModalClose();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.dispatchClearEditions();
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
@@ -82,9 +94,6 @@ class EditBookModalContentConnector extends Component {
|
||||
this.props.dispatchSaveBook({
|
||||
id: this.props.bookId
|
||||
});
|
||||
this.props.dispatchSaveEditions({
|
||||
id: this.props.bookId
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
@@ -105,9 +114,10 @@ EditBookModalContentConnector.propTypes = {
|
||||
bookId: PropTypes.number,
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
saveError: PropTypes.object,
|
||||
dispatchFetchEditions: PropTypes.func.isRequired,
|
||||
dispatchClearEditions: PropTypes.func.isRequired,
|
||||
dispatchSetBookValue: PropTypes.func.isRequired,
|
||||
dispatchSaveBook: PropTypes.func.isRequired,
|
||||
dispatchSaveEditions: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import createBookAuthorSelector from 'Store/Selectors/createBookAuthorSelector';
|
||||
import createBookQualityProfileSelector from 'Store/Selectors/createBookQualityProfileSelector';
|
||||
import createBookSelector from 'Store/Selectors/createBookSelector';
|
||||
import createExecutingCommandsSelector from 'Store/Selectors/createExecutingCommandsSelector';
|
||||
@@ -33,13 +32,11 @@ function selectShowSearchAction() {
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createBookSelector(),
|
||||
createBookAuthorSelector(),
|
||||
createBookQualityProfileSelector(),
|
||||
selectShowSearchAction(),
|
||||
createExecutingCommandsSelector(),
|
||||
(
|
||||
book,
|
||||
author,
|
||||
qualityProfile,
|
||||
showSearchAction,
|
||||
executingCommands
|
||||
@@ -57,7 +54,7 @@ function createMapStateToProps() {
|
||||
const isRefreshingBook = executingCommands.some((command) => {
|
||||
return (
|
||||
(command.name === commandNames.REFRESH_AUTHOR &&
|
||||
command.body.authorId === book.authorId) ||
|
||||
command.body.authorId === book.author.id) ||
|
||||
(command.name === commandNames.REFRESH_BOOK &&
|
||||
command.body.bookId === book.id)
|
||||
);
|
||||
@@ -66,7 +63,7 @@ function createMapStateToProps() {
|
||||
const isSearchingBook = executingCommands.some((command) => {
|
||||
return (
|
||||
(command.name === commandNames.AUTHOR_SEARCH &&
|
||||
command.body.authorId === book.authorId) ||
|
||||
command.body.authorId === book.author.id) ||
|
||||
(command.name === commandNames.BOOK_SEARCH &&
|
||||
command.body.bookIds.includes(book.id))
|
||||
);
|
||||
@@ -74,7 +71,6 @@ function createMapStateToProps() {
|
||||
|
||||
return {
|
||||
...book,
|
||||
author,
|
||||
qualityProfile,
|
||||
showSearchAction,
|
||||
isRefreshingBook,
|
||||
|
||||
@@ -12,7 +12,6 @@ import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import dimensions from 'Styles/Variables/dimensions';
|
||||
import fonts from 'Styles/Variables/fonts';
|
||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||
import stripHtml from 'Utilities/String/stripHtml';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import BookIndexOverviewInfo from './BookIndexOverviewInfo';
|
||||
@@ -43,26 +42,10 @@ class BookIndexOverview extends Component {
|
||||
|
||||
this.state = {
|
||||
isEditAuthorModalOpen: false,
|
||||
isDeleteAuthorModalOpen: false,
|
||||
overview: ''
|
||||
isDeleteAuthorModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -101,6 +84,7 @@ class BookIndexOverview extends Component {
|
||||
const {
|
||||
id,
|
||||
title,
|
||||
overview,
|
||||
monitored,
|
||||
titleSlug,
|
||||
nextAiring,
|
||||
@@ -134,7 +118,6 @@ class BookIndexOverview extends Component {
|
||||
} = statistics;
|
||||
|
||||
const {
|
||||
overview,
|
||||
isEditAuthorModalOpen,
|
||||
isDeleteAuthorModalOpen
|
||||
} = this.state;
|
||||
@@ -284,6 +267,7 @@ class BookIndexOverview extends Component {
|
||||
BookIndexOverview.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
overview: PropTypes.string.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
titleSlug: PropTypes.string.isRequired,
|
||||
nextAiring: PropTypes.string,
|
||||
|
||||
@@ -6,7 +6,6 @@ import SelectInput from 'Components/Form/SelectInput';
|
||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||
import PageContentFooter from 'Components/Page/PageContentFooter';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './BookshelfFooter.css';
|
||||
|
||||
const NO_CHANGE = 'noChange';
|
||||
@@ -115,7 +114,7 @@ class BookshelfFooter extends Component {
|
||||
|
||||
<div className={styles.inputContainer}>
|
||||
<div className={styles.label}>
|
||||
{translate('MonitorExistingBooks')}
|
||||
Monitor Existing Books
|
||||
</div>
|
||||
|
||||
<MonitorBooksSelectInput
|
||||
@@ -129,7 +128,7 @@ class BookshelfFooter extends Component {
|
||||
|
||||
<div className={styles.inputContainer}>
|
||||
<div className={styles.label}>
|
||||
{translate('MonitorNewBooks')}
|
||||
Monitor New Books
|
||||
</div>
|
||||
|
||||
<MonitorNewItemsSelectInput
|
||||
@@ -153,7 +152,7 @@ class BookshelfFooter extends Component {
|
||||
isDisabled={!selectedCount || noChanges}
|
||||
onPress={this.onUpdateSelectedPress}
|
||||
>
|
||||
{translate('UpdateSelected')}
|
||||
Update Selected
|
||||
</SpinnerButton>
|
||||
</div>
|
||||
</PageContentFooter>
|
||||
|
||||
@@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Link from 'Components/Link/Link';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
// import translate from 'Utilities/String/translate';
|
||||
import AutoCompleteInput from './AutoCompleteInput';
|
||||
import BookEditionSelectInputConnector from './BookEditionSelectInputConnector';
|
||||
@@ -217,7 +216,7 @@ function FormInputGroup(props) {
|
||||
<Link
|
||||
to={helpLink}
|
||||
>
|
||||
{translate('MoreInfo')}
|
||||
More Info
|
||||
</Link>
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ function PageContent(props) {
|
||||
|
||||
return (
|
||||
<ErrorBoundary errorComponent={PageContentError}>
|
||||
<DocumentTitle title={title ? `${title} - ${window.Readarr.instanceName}` : window.Readarr.instanceName}>
|
||||
<DocumentTitle title={title ? `${title} - Readarr` : 'Readarr'}>
|
||||
<div className={className}>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import Button from 'Components/Link/Button';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
@@ -10,8 +9,7 @@ import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import { scrollDirections } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import SelectEditionRowConnector from './SelectEditionRowConnector';
|
||||
import SelectEditionRow from './SelectEditionRow';
|
||||
import styles from './SelectEditionModalContent.css';
|
||||
|
||||
const columns = [
|
||||
@@ -35,30 +33,15 @@ class SelectEditionModalContent extends Component {
|
||||
render() {
|
||||
const {
|
||||
books,
|
||||
isPopulated,
|
||||
isFetching,
|
||||
error,
|
||||
onEditionSelect,
|
||||
onModalClose,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
if (!isPopulated && !error) {
|
||||
return (<LoadingIndicator />);
|
||||
}
|
||||
|
||||
if (!isFetching && error) {
|
||||
return (
|
||||
<div>
|
||||
{translate('LoadingEditionsFailed')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{translate('ManualImportSelectEdition')}
|
||||
Manual Import - Select Edition
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody
|
||||
@@ -77,7 +60,7 @@ class SelectEditionModalContent extends Component {
|
||||
{
|
||||
books.map((item) => {
|
||||
return (
|
||||
<SelectEditionRowConnector
|
||||
<SelectEditionRow
|
||||
key={item.book.id}
|
||||
matchedEditionId={item.matchedEditionId}
|
||||
columns={columns}
|
||||
@@ -93,7 +76,7 @@ class SelectEditionModalContent extends Component {
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={onModalClose}>
|
||||
{translate('Cancel')}
|
||||
Cancel
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
@@ -103,9 +86,6 @@ class SelectEditionModalContent extends Component {
|
||||
|
||||
SelectEditionModalContent.propTypes = {
|
||||
books: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isFetching: PropTypes.bool,
|
||||
isPopulated: PropTypes.bool,
|
||||
error: PropTypes.object,
|
||||
onEditionSelect: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -1,71 +1,27 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { clearEditions, fetchEditions } from 'Store/Actions/editionActions';
|
||||
import {
|
||||
saveInteractiveImportItem,
|
||||
updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions';
|
||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||
import SelectEditionModalContent from './SelectEditionModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.editions,
|
||||
(editions) => {
|
||||
const {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error
|
||||
} = editions;
|
||||
|
||||
return {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error
|
||||
};
|
||||
}
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchEditions,
|
||||
clearEditions,
|
||||
updateInteractiveImportItem,
|
||||
saveInteractiveImportItem
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
onEditionSelect = (bookId, foreignEditionId) => {
|
||||
console.log(`book: ${bookId} id: ${foreignEditionId} ${typeof foreignEditionId}`);
|
||||
const ids = this.props.importIdsByBook[bookId];
|
||||
|
||||
ids.forEach((id) => {
|
||||
@@ -99,8 +55,6 @@ class SelectEditionModalContentConnector extends Component {
|
||||
SelectEditionModalContentConnector.propTypes = {
|
||||
importIdsByBook: PropTypes.object.isRequired,
|
||||
books: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
fetchEditions: PropTypes.func.isRequired,
|
||||
clearEditions: PropTypes.func.isRequired,
|
||||
updateInteractiveImportItem: PropTypes.func.isRequired,
|
||||
saveInteractiveImportItem: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
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);
|
||||
@@ -126,7 +126,7 @@ class AddAuthorOptionsForm extends Component {
|
||||
|
||||
<FormGroup className={showMetadataProfile ? undefined : styles.hideMetadataProfile}>
|
||||
<FormLabel>
|
||||
{translate('MetadataProfile')}
|
||||
Metadata Profile
|
||||
|
||||
{
|
||||
includeNoneMetadataProfile &&
|
||||
|
||||
@@ -20,7 +20,6 @@ function HostSettings(props) {
|
||||
bindAddress,
|
||||
port,
|
||||
urlBase,
|
||||
instanceName,
|
||||
enableSsl,
|
||||
sslPort,
|
||||
sslCertPath,
|
||||
@@ -79,22 +78,6 @@ function HostSettings(props) {
|
||||
/>
|
||||
</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
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
|
||||
@@ -28,17 +28,17 @@ function ImportListMonitoringOptionsPopoverContent() {
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
title={translate('None')}
|
||||
data={translate('DataListMonitorNone')}
|
||||
data="Do not monitor authors or books"
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('SpecificBook')}
|
||||
data={translate('DataListMonitorSpecificBook')}
|
||||
data="Monitor authors but only monitor books explicitly included in the list"
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('AllAuthorBooks')}
|
||||
data={translate('DataListMonitorAll')}
|
||||
data="Monitor authors and all books for each author included on the import list"
|
||||
/>
|
||||
</DescriptionList>
|
||||
);
|
||||
@@ -89,7 +89,7 @@ function EditImportListModalContent(props) {
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{id ? translate('EditList') : translate('AddList')}
|
||||
{id ? 'Edit List' : 'Add List'}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
@@ -148,7 +148,7 @@ function EditImportListModalContent(props) {
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
{translate('Monitor')}
|
||||
Monitor
|
||||
|
||||
<Popover
|
||||
anchor={
|
||||
@@ -318,7 +318,7 @@ function EditImportListModalContent(props) {
|
||||
kind={kinds.DANGER}
|
||||
onPress={onDeleteImportListPress}
|
||||
>
|
||||
{translate('Delete')}
|
||||
Delete
|
||||
</Button>
|
||||
}
|
||||
|
||||
@@ -327,13 +327,13 @@ function EditImportListModalContent(props) {
|
||||
error={saveError}
|
||||
onPress={onTestPress}
|
||||
>
|
||||
{translate('Test')}
|
||||
Test
|
||||
</SpinnerErrorButton>
|
||||
|
||||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
{translate('Cancel')}
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<SpinnerErrorButton
|
||||
@@ -341,7 +341,7 @@ function EditImportListModalContent(props) {
|
||||
error={saveError}
|
||||
onPress={onSavePress}
|
||||
>
|
||||
{translate('Save')}
|
||||
Save
|
||||
</SpinnerErrorButton>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
|
||||
@@ -346,7 +346,7 @@ function EditRootFolderModalContent(props) {
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
{translate('ConvertToFormat')}
|
||||
Convert to format
|
||||
<Popover
|
||||
anchor={
|
||||
<Icon
|
||||
@@ -371,7 +371,7 @@ function EditRootFolderModalContent(props) {
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
{translate('CalibreOutputProfile')}
|
||||
Calibre Output Profile
|
||||
<Popover
|
||||
anchor={
|
||||
<Icon
|
||||
@@ -423,14 +423,14 @@ function EditRootFolderModalContent(props) {
|
||||
kind={kinds.DANGER}
|
||||
onPress={onDeleteRootFolderPress}
|
||||
>
|
||||
{translate('Delete')}
|
||||
Delete
|
||||
</Button>
|
||||
}
|
||||
|
||||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
{translate('Cancel')}
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<SpinnerErrorButton
|
||||
@@ -438,7 +438,7 @@ function EditRootFolderModalContent(props) {
|
||||
error={saveError}
|
||||
onPress={onSavePress}
|
||||
>
|
||||
{translate('Save')}
|
||||
Save
|
||||
</SpinnerErrorButton>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
|
||||
@@ -78,8 +78,12 @@ export const actionHandlers = handleThunks({
|
||||
} = payload;
|
||||
|
||||
const promise = createAjaxRequest({
|
||||
url: `/history/failed/${historyId}`,
|
||||
method: 'POST'
|
||||
url: '/history/failed',
|
||||
method: 'POST',
|
||||
data: {
|
||||
id: historyId
|
||||
},
|
||||
dataType: 'json'
|
||||
}).request;
|
||||
|
||||
promise.done(() => {
|
||||
|
||||
@@ -86,8 +86,12 @@ export const actionHandlers = handleThunks({
|
||||
} = payload;
|
||||
|
||||
const promise = createAjaxRequest({
|
||||
url: `/history/failed/${historyId}`,
|
||||
method: 'POST'
|
||||
url: '/history/failed',
|
||||
method: 'POST',
|
||||
data: {
|
||||
id: historyId
|
||||
},
|
||||
dataType: 'json'
|
||||
}).request;
|
||||
|
||||
promise.done(() => {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
import { batchActions } from 'redux-batched-actions';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import getProviderState from 'Utilities/State/getProviderState';
|
||||
import { updateItem } from './baseActions';
|
||||
import createFetchHandler from './Creators/createFetchHandler';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
import createClearReducer from './Creators/Reducers/createClearReducer';
|
||||
@@ -28,39 +25,18 @@ export const defaultState = {
|
||||
|
||||
export const FETCH_EDITIONS = 'editions/fetchEditions';
|
||||
export const CLEAR_EDITIONS = 'editions/clearEditions';
|
||||
export const SAVE_EDITIONS = 'editions/saveEditions';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
||||
export const fetchEditions = createThunk(FETCH_EDITIONS);
|
||||
export const clearEditions = createAction(CLEAR_EDITIONS);
|
||||
export const saveEditions = createThunk(SAVE_EDITIONS);
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
||||
export const actionHandlers = handleThunks({
|
||||
[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
|
||||
});
|
||||
})
|
||||
]));
|
||||
}
|
||||
[FETCH_EDITIONS]: createFetchHandler(section, '/edition')
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
@@ -246,8 +246,11 @@ export const actionHandlers = handleThunks({
|
||||
}));
|
||||
|
||||
const promise = createAjaxRequest({
|
||||
url: `/history/failed/${id}`,
|
||||
url: '/history/failed',
|
||||
method: 'POST',
|
||||
data: {
|
||||
id
|
||||
},
|
||||
dataType: 'json'
|
||||
}).request;
|
||||
|
||||
|
||||
@@ -177,8 +177,7 @@ export const defaultState = {
|
||||
{
|
||||
name: 'size',
|
||||
label: 'Size',
|
||||
type: filterBuilderTypes.NUMBER,
|
||||
valueType: filterBuilderValueTypes.BYTES
|
||||
type: filterBuilderTypes.NUMBER
|
||||
},
|
||||
{
|
||||
name: 'seeders',
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
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 hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
||||
import createBooksClientSideCollectionSelector from './createBooksClientSideCollectionSelector';
|
||||
import createClientSideCollectionSelector from './createClientSideCollectionSelector';
|
||||
|
||||
function createUnoptimizedSelector(uiSection) {
|
||||
return createSelector(
|
||||
createBooksClientSideCollectionSelector(uiSection),
|
||||
createClientSideCollectionSelector('books', uiSection),
|
||||
(books) => {
|
||||
const items = books.items.map((s) => {
|
||||
const {
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import { createSelector } from 'reselect';
|
||||
import createBookAuthorSelector from './createBookAuthorSelector';
|
||||
import createBookSelector from './createBookSelector';
|
||||
|
||||
function createBookQualityProfileSelector() {
|
||||
return createSelector(
|
||||
(state) => state.settings.qualityProfiles.items,
|
||||
createBookAuthorSelector(),
|
||||
(qualityProfiles, author) => {
|
||||
if (!author) {
|
||||
createBookSelector(),
|
||||
(qualityProfiles, book) => {
|
||||
if (!book) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return qualityProfiles.find((profile) => profile.id === author.qualityProfileId);
|
||||
return qualityProfiles.find((profile) => {
|
||||
return profile.id === book.author.qualityProfileId;
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
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,8 +1,123 @@
|
||||
import _ from 'lodash';
|
||||
import { createSelector } from 'reselect';
|
||||
import filterCollection from 'Utilities/Array/filterCollection';
|
||||
import sortCollection from 'Utilities/Array/sortCollection';
|
||||
import createCustomFiltersSelector from './createCustomFiltersSelector';
|
||||
import { filterTypePredicates, filterTypes, sortDirections } from 'Helpers/Props';
|
||||
import findSelectedFilters from 'Utilities/Filter/findSelectedFilters';
|
||||
|
||||
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)) {
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
return createSelector(
|
||||
@@ -12,8 +127,8 @@ function createClientSideCollectionSelector(section, uiSection) {
|
||||
(sectionState, uiSectionState = {}, customFilters) => {
|
||||
const state = Object.assign({}, sectionState, uiSectionState, { customFilters });
|
||||
|
||||
const filtered = filterCollection(state.items, state);
|
||||
const sorted = sortCollection(filtered, state);
|
||||
const filtered = filter(state.items, state);
|
||||
const sorted = sort(filtered, state);
|
||||
|
||||
return {
|
||||
...sectionState,
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
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,8 +24,6 @@ class About extends Component {
|
||||
isDocker,
|
||||
runtimeVersion,
|
||||
migrationVersion,
|
||||
databaseVersion,
|
||||
databaseType,
|
||||
appData,
|
||||
startupPath,
|
||||
mode,
|
||||
@@ -79,11 +77,6 @@ class About extends Component {
|
||||
data={migrationVersion}
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('Database')}
|
||||
data={`${titleCase(databaseType)} ${databaseVersion}`}
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('AppDataDirectory')}
|
||||
data={appData}
|
||||
@@ -125,8 +118,6 @@ About.propTypes = {
|
||||
runtimeVersion: PropTypes.string.isRequired,
|
||||
isDocker: PropTypes.bool.isRequired,
|
||||
migrationVersion: PropTypes.number.isRequired,
|
||||
databaseType: PropTypes.string.isRequired,
|
||||
databaseVersion: PropTypes.string.isRequired,
|
||||
appData: PropTypes.string.isRequired,
|
||||
startupPath: PropTypes.string.isRequired,
|
||||
mode: PropTypes.string.isRequired,
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
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;
|
||||
@@ -1,42 +0,0 @@
|
||||
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,7 +33,7 @@
|
||||
"@fortawesome/free-regular-svg-icons": "5.15.3",
|
||||
"@fortawesome/free-solid-svg-icons": "5.15.3",
|
||||
"@fortawesome/react-fontawesome": "0.1.14",
|
||||
"@microsoft/signalr": "6.0.7",
|
||||
"@microsoft/signalr": "6.0.3",
|
||||
"@sentry/browser": "6.18.2",
|
||||
"@sentry/integrations": "6.18.2",
|
||||
"ansi-colors": "4.1.1",
|
||||
|
||||
110
scripts/readarr_changelog_post.sh
Normal file
110
scripts/readarr_changelog_post.sh
Normal file
@@ -0,0 +1,110 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Generate a Markdown change log of pull requests from commits between two tags
|
||||
scriptDir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
|
||||
ghRepo="Readarr"
|
||||
#branch="develop"
|
||||
#read -r -p "What Repo?: " ghRepo
|
||||
#read -r -p "What Org?: [Default:$ghRepo]" ghOrg
|
||||
read -r -p "What Branch? [master|develop|nightly]:" branch
|
||||
ghOrg=${ghOrg:-$ghRepo}
|
||||
ghRepoUrl=https://github.com/$ghOrg/$ghRepo
|
||||
|
||||
case "${branch}" in
|
||||
master)
|
||||
hotioBranch='release'
|
||||
lsioBranch='latest'
|
||||
branchType='Stable'
|
||||
;;
|
||||
develop)
|
||||
hotioBranch='testing'
|
||||
lsioBranch='develop'
|
||||
branchType='Beta'
|
||||
;;
|
||||
nightly)
|
||||
hotioBranch='nightly'
|
||||
lsioBranch='nightly'
|
||||
branchType='Alpha'
|
||||
;;
|
||||
esac
|
||||
baseDir=$(dirname "$scriptDir")
|
||||
changelogDir="$baseDir/changelogs/"
|
||||
templateDir="$changelogDir/templates/"
|
||||
# Get a list of all tags in reverse order
|
||||
# Assumes the tags are in version format like v1.2.3
|
||||
## gitTags=$(git ls-remote -t --exit-code --refs --sort='-v:refname' "$ghRepoUrl" | sed -E 's/^[[:xdigit:]]+[[:space:]]+refs\/tags\/(.+)/\1/g')
|
||||
|
||||
# Make the tags an array
|
||||
|
||||
# shellcheck disable=SC2206
|
||||
## tags=($gitTags)
|
||||
|
||||
# Prompt for Tags due to bad Tags on GH (Sonarr v2)
|
||||
# Commented out automation
|
||||
##latestTag=${tags[0]}
|
||||
##previousTag=${tags[1]}
|
||||
read -r -p "Enter Latest Tag:" latestTag
|
||||
read -r -p "Enter Previous Tag:" previousTag
|
||||
|
||||
# Get a log of commits that occurred between two tags
|
||||
# See Pretty format placeholders at https://git-scm.com/docs/pretty-formats
|
||||
# -i -E --grep="(Fixed:|New:)"'
|
||||
commits=$(git log --pretty=format:" - %s%n" -i -E --grep="(Fixed:|New:)" "$previousTag".."$latestTag")
|
||||
# Store our changelog in a variable to be saved to a file at the end
|
||||
markdown="# New ${branchType^} Release"
|
||||
markdown+='\n\n'
|
||||
markdown+="$ghRepo $latestTag has been released on \`$branch\`"
|
||||
markdown+='\n\n'
|
||||
branchmsg=$(cat "$templateDir"/branch-$branch.md)
|
||||
if [ -n "$branchmsg" ]; then
|
||||
{
|
||||
markdown+=$branchmsg
|
||||
markdown+='\n\n'
|
||||
}
|
||||
fi
|
||||
markdown+="# Announcements"
|
||||
markdown+='\n\n'
|
||||
markdown+=$(cat "$templateDir"/announcements.md)
|
||||
markdown+='\n\n'
|
||||
markdown+="# Additional Commentary"
|
||||
markdown+='\n\n'
|
||||
markdown+=$(cat "$templateDir"/commentary.md)
|
||||
markdown+='\n\n'
|
||||
markdown+="# Releases"
|
||||
markdown+='\n\n'
|
||||
markdown+="## Native"
|
||||
markdown+="\n\n"
|
||||
markdown+="- [GitHub Releases]($ghRepoUrl/releases)"
|
||||
markdown+="\n\n"
|
||||
markdown+="- [Wiki Installation Instructions](https://wiki.servarr.com/${ghRepo,,}/installation)"
|
||||
markdown+="\n\n"
|
||||
markdown+="## Docker"
|
||||
markdown+="\n\n"
|
||||
markdown+="- [hotio/$ghRepo:$hotioBranch](https://hotio.dev/containers/${ghRepo,,})"
|
||||
markdown+="\n\n"
|
||||
markdown+="- [lscr.io/linuxserver/$ghRepo:$lsioBranch](https://docs.linuxserver.io/images/docker-${ghRepo,,})"
|
||||
markdown+="\n\n"
|
||||
markdown+="## NAS Packages"
|
||||
markdown+="\n\n"
|
||||
markdown+="- Synology - Please ask the SynoCommunity to update the base package; however, you can update in-app normally"
|
||||
markdown+="\n\n"
|
||||
markdown+="- QNAP - Please ask the QNAP to update the base package; however, you should be able to update in-app normally"
|
||||
markdown+="\n\n"
|
||||
markdown+="------------"
|
||||
markdown+="\n\n"
|
||||
markdown+="# Release Notes"
|
||||
markdown+="\n\n"
|
||||
markdown+="## $latestTag (changes since $previousTag)"
|
||||
markdown+="\n\n"
|
||||
markdown+="$commits"
|
||||
markdown+="\n\n"
|
||||
markdown+=" - Other bug fixes and improvements, see GitHub history"
|
||||
# Loop over each commit and look for merged pull requests
|
||||
#for COMMIT in $COMMITS; do
|
||||
|
||||
#done
|
||||
|
||||
# Save our markdown to a file
|
||||
mkdir -p "$changelogDir"
|
||||
echo -e "$markdown" >"$changelogDir/CHANGELOG-$latestTag.md"
|
||||
exit 0
|
||||
@@ -4,62 +4,60 @@
|
||||
<PackageVersion Include="AutoFixture" Version="4.17.0" />
|
||||
<PackageVersion Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" PrivateAssets="all" />
|
||||
<PackageVersion Include="Dapper" Version="2.0.123" />
|
||||
<PackageVersion Include="DryIoc.dll" Version="5.2.0" />
|
||||
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.0.2" />
|
||||
<PackageVersion Include="DryIoc.dll" Version="4.8.7" />
|
||||
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="5.1.0" />
|
||||
<PackageVersion Include="Equ" Version="2.3.0" />
|
||||
<PackageVersion Include="FluentAssertions" Version="5.10.3" />
|
||||
<PackageVersion Include="Servarr.FluentMigrator.Runner" Version="3.3.2.9" />
|
||||
<PackageVersion Include="Servarr.FluentMigrator.Runner.SQLite" Version="3.3.2.9" />
|
||||
<PackageVersion Include="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" />
|
||||
<PackageVersion Include="FluentMigrator.Runner.SQLite" Version="4.0.0-alpha.289" />
|
||||
<PackageVersion Include="FluentMigrator.Runner" Version="4.0.0-alpha.289" />
|
||||
<PackageVersion Include="FluentValidation" Version="8.6.2" />
|
||||
<PackageVersion Include="Ical.Net" Version="4.2.0" />
|
||||
<PackageVersion Include="ImpromptuInterface" Version="7.0.1" />
|
||||
<PackageVersion Include="LazyCache" Version="2.4.0" />
|
||||
<PackageVersion Include="Mailkit" Version="3.3.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.7" />
|
||||
<PackageVersion Include="Mailkit" Version="3.1.1" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.3" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" 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.NET.Test.Sdk" Version="17.2.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageVersion Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr22" />
|
||||
<PackageVersion Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr18" />
|
||||
<PackageVersion Include="MonoTorrent" Version="2.0.4" />
|
||||
<PackageVersion Include="Moq" Version="4.17.2" />
|
||||
<PackageVersion Include="MonoTorrent" Version="2.0.6" />
|
||||
<PackageVersion Include="NBuilder" Version="6.1.0" />
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageVersion Include="NLog.Extensions.Logging" Version="1.7.4" />
|
||||
<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="NUnit" Version="3.13.3" />
|
||||
<PackageVersion Include="NUnit" Version="3.13.2" />
|
||||
<PackageVersion Include="NunitXml.TestLogger" Version="3.0.117" />
|
||||
<PackageVersion Include="PdfSharpCore" Version="1.3.32" />
|
||||
<PackageVersion Include="PdfSharpCore" Version="1.3.13" />
|
||||
<PackageVersion Include="RestSharp.Serializers.SystemTextJson" Version="106.15.0" />
|
||||
<PackageVersion Include="RestSharp" Version="106.15.0" />
|
||||
<PackageVersion Include="Selenium.Support" Version="3.141.0" />
|
||||
<PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" />
|
||||
<PackageVersion Include="Sentry" Version="3.20.1" />
|
||||
<PackageVersion Include="Sentry" Version="3.14.1" />
|
||||
<PackageVersion Include="SharpZipLib" Version="1.3.3" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.3" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||
<PackageVersion Include="System.Buffers" Version="4.5.1" />
|
||||
<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.IO.Abstractions.TestingHelpers" Version="17.0.24" />
|
||||
<PackageVersion Include="System.IO.Abstractions" Version="17.0.24" />
|
||||
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="16.1.20" />
|
||||
<PackageVersion Include="System.IO.Abstractions" Version="16.1.20" />
|
||||
<PackageVersion Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
|
||||
<PackageVersion Include="System.Memory" Version="4.5.5" />
|
||||
<PackageVersion Include="System.Memory" Version="4.5.4" />
|
||||
<PackageVersion Include="System.Reflection.TypeExtensions" Version="4.7.0" />
|
||||
<PackageVersion Include="System.Resources.Extensions" Version="6.0.0" />
|
||||
<PackageVersion Include="System.Runtime.Loader" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Security.Principal.Windows" Version="5.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.Json" Version="6.0.5" />
|
||||
<PackageVersion Include="System.Text.Json" Version="6.0.2" />
|
||||
<PackageVersion Include="System.ValueTuple" Version="4.5.0" />
|
||||
<PackageVersion Include="TagLibSharp-Lidarr" Version="2.2.0.19" />
|
||||
<PackageVersion Include="Unity" Version="5.11.10" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,33 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<clear />
|
||||
<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="Mono.Posix.NETStandard" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/Mono.Posix.NETStandard/nuget/v3/index.json" />
|
||||
<add key="SQLite" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/SQLite/nuget/v3/index.json" />
|
||||
<add key="coverlet-nightly" value="https://pkgs.dev.azure.com/Servarr/coverlet/_packaging/coverlet-nightly/nuget/v3/index.json" />
|
||||
<add key="FluentMigrator" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/FluentMigrator/nuget/v3/index.json" />
|
||||
</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>
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace NzbDrone.Automation.Test
|
||||
|
||||
driver.Manage().Window.Size = new System.Drawing.Size(1920, 1080);
|
||||
|
||||
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), null);
|
||||
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger());
|
||||
_runner.KillAll();
|
||||
_runner.Start();
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Disk;
|
||||
@@ -11,12 +10,6 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
public abstract class DiskProviderFixtureBase<TSubject> : TestBase<TSubject>
|
||||
where TSubject : class, IDiskProvider
|
||||
{
|
||||
[SetUp]
|
||||
public void BaseSetup()
|
||||
{
|
||||
Mocker.SetConstant<IFileSystem>(new FileSystem());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void writealltext_should_truncate_existing()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Disk;
|
||||
@@ -11,12 +10,6 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
public abstract class FreeSpaceFixtureBase<TSubject> : TestBase<TSubject>
|
||||
where TSubject : class, IDiskProvider
|
||||
{
|
||||
[SetUp]
|
||||
public void BaseSetup()
|
||||
{
|
||||
Mocker.SetConstant<IFileSystem>(new FileSystem());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_get_free_space_for_folder()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using FluentAssertions;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
|
||||
@@ -60,7 +60,6 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
[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("/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
|
||||
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")]
|
||||
@@ -71,11 +70,6 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
[TestCase(@"tracker"":""https://xxx.yyy/tracker.php/9pr04sg601233210imaveql2tyu8xyui/announce""}")]
|
||||
[TestCase(@"tracker"":""https://xxx.yyy/announce/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)
|
||||
{
|
||||
var cleansedMessage = CleanseLogMessage.Cleanse(message);
|
||||
@@ -86,7 +80,6 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
|
||||
//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(@"https://www.goodreads.com/series/311911?key=1234530f422f4aacb6b301233210aaaa&_nc=1&format=xml")]
|
||||
public void should_cleanGoodRead_message(string message)
|
||||
{
|
||||
var cleansedMessage = CleanseLogMessage.Cleanse(message);
|
||||
|
||||
@@ -4,13 +4,11 @@ using DryIoc.Microsoft.DependencyInjection;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Composition.Extensions;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Datastore.Extensions;
|
||||
using NzbDrone.Core.Lifecycle;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
@@ -31,8 +29,7 @@ namespace NzbDrone.Common.Test
|
||||
.AddDummyDatabase()
|
||||
.AddStartupContext(new StartupContext("first", "second"));
|
||||
|
||||
container.RegisterInstance(new Mock<IHostLifetime>().Object);
|
||||
container.RegisterInstance(new Mock<IOptions<PostgresOptions>>().Object);
|
||||
container.RegisterInstance<IHostLifetime>(new Mock<IHostLifetime>().Object);
|
||||
|
||||
var serviceProvider = container.GetServiceProvider();
|
||||
serviceProvider.GetRequiredService<IAppFolderFactory>().Register();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -11,14 +11,13 @@ namespace NzbDrone.Common.Instrumentation
|
||||
private static readonly Regex[] CleansingRules = new[]
|
||||
{
|
||||
// Url
|
||||
new Regex(@"(?<=\?|&|: )((?:api|auth|pass)?key|(?:access[-_]?)?token|auth|user|uid|api|[a-z_]*apikey|account|passwd)=(?<secret>[^&=""]+?)(?=[ ""&=]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=\?|&|: )(apikey|(?:access[-_]?)?token|passkey|auth|authkey|user|uid|api|[a-z_]*apikey|account|passwd)=(?<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/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(@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
|
||||
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
|
||||
new Regex(@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
|
||||
@@ -47,11 +46,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
new Regex(@"(?<=\?|&)(authkey|torrent_pass)=(?<secret>[^&=]+?)(?=""|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// Good Reads
|
||||
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)
|
||||
new Regex(@"(?<=""(token|tokensecret)"":\s)""(?<secret>[^""]+?)""", 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);
|
||||
|
||||
@@ -127,18 +127,7 @@ namespace NzbDrone.Common.Processes
|
||||
try
|
||||
{
|
||||
_logger.Trace("Setting environment variable '{0}' to '{1}'", environmentVariable.Key, environmentVariable.Value);
|
||||
|
||||
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);
|
||||
}
|
||||
startInfo.EnvironmentVariables.Add(environmentVariable.Key.ToString(), environmentVariable.Value.ToString());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||
public void SingleOrDefault_should_return_null_on_empty_db()
|
||||
{
|
||||
Mocker.Resolve<IDatabase>()
|
||||
.OpenConnection().Query<Author>("SELECT * FROM \"Authors\"")
|
||||
.OpenConnection().Query<Author>("SELECT * FROM Authors")
|
||||
.SingleOrDefault(c => c.CleanName == "SomeTitle")
|
||||
.Should()
|
||||
.BeNull();
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||
public void should_lazy_load_author_for_trackfile()
|
||||
{
|
||||
var db = Mocker.Resolve<IDatabase>();
|
||||
var tracks = db.Query<BookFile>(new SqlBuilder(db.DatabaseType)).ToList();
|
||||
var tracks = db.Query<BookFile>(new SqlBuilder()).ToList();
|
||||
|
||||
Assert.IsNotEmpty(tracks);
|
||||
foreach (var track in tracks)
|
||||
@@ -94,7 +94,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||
public void should_lazy_load_trackfile_if_not_joined()
|
||||
{
|
||||
var db = Mocker.Resolve<IDatabase>();
|
||||
var tracks = db.Query<Book>(new SqlBuilder(db.DatabaseType)).ToList();
|
||||
var tracks = db.Query<Book>(new SqlBuilder()).ToList();
|
||||
|
||||
foreach (var track in tracks)
|
||||
{
|
||||
@@ -109,7 +109,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||
{
|
||||
var db = Mocker.Resolve<IDatabase>();
|
||||
var files = MediaFileRepository.Query(db,
|
||||
new SqlBuilder(db.DatabaseType)
|
||||
new SqlBuilder()
|
||||
.Join<BookFile, Edition>((t, a) => t.EditionId == a.Id)
|
||||
.Join<Edition, Book>((e, b) => e.BookId == b.Id)
|
||||
.Join<Book, Author>((book, author) => book.AuthorMetadataId == author.AuthorMetadataId)
|
||||
|
||||
@@ -11,9 +11,9 @@ using NzbDrone.Core.Test.Framework;
|
||||
namespace NzbDrone.Core.Test.Datastore
|
||||
{
|
||||
[TestFixture]
|
||||
public class WhereBuilderSqliteFixture : CoreTest
|
||||
public class WhereBuilderFixture : CoreTest
|
||||
{
|
||||
private WhereBuilderSqlite _subject;
|
||||
private WhereBuilder _subject;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void MapTables()
|
||||
@@ -22,14 +22,14 @@ namespace NzbDrone.Core.Test.Datastore
|
||||
Mocker.Resolve<DbFactory>();
|
||||
}
|
||||
|
||||
private WhereBuilderSqlite Where(Expression<Func<Author, bool>> filter)
|
||||
private WhereBuilder Where(Expression<Func<Author, bool>> filter)
|
||||
{
|
||||
return new WhereBuilderSqlite(filter, true, 0);
|
||||
return new WhereBuilder(filter, true, 0);
|
||||
}
|
||||
|
||||
private WhereBuilderSqlite WhereMetadata(Expression<Func<AuthorMetadata, bool>> filter)
|
||||
private WhereBuilder WhereMetadata(Expression<Func<AuthorMetadata, bool>> filter)
|
||||
{
|
||||
return new WhereBuilderSqlite(filter, true, 0);
|
||||
return new WhereBuilder(filter, true, 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -76,7 +76,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||
public void where_throws_without_concrete_condition_if_requiresConcreteCondition()
|
||||
{
|
||||
Expression<Func<Author, Author, bool>> filter = (x, y) => x.Id == y.Id;
|
||||
_subject = new WhereBuilderSqlite(filter, true, 0);
|
||||
_subject = new WhereBuilder(filter, true, 0);
|
||||
Assert.Throws<InvalidOperationException>(() => _subject.ToString());
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||
public void where_allows_abstract_condition_if_not_requiresConcreteCondition()
|
||||
{
|
||||
Expression<Func<Author, Author, bool>> filter = (x, y) => x.Id == y.Id;
|
||||
_subject = new WhereBuilderSqlite(filter, false, 0);
|
||||
_subject = new WhereBuilder(filter, false, 0);
|
||||
_subject.ToString().Should().Be($"(\"Authors\".\"Id\" = \"Authors\".\"Id\")");
|
||||
}
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
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))");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,12 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SQLite;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Npgsql;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
using NzbDrone.Test.Common.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Test.Framework
|
||||
{
|
||||
@@ -53,7 +47,6 @@ namespace NzbDrone.Core.Test.Framework
|
||||
public abstract class DbTest : CoreTest
|
||||
{
|
||||
private ITestDatabase _db;
|
||||
private DatabaseType _databaseType;
|
||||
|
||||
protected virtual MigrationType MigrationType => MigrationType.Main;
|
||||
|
||||
@@ -72,7 +65,8 @@ namespace NzbDrone.Core.Test.Framework
|
||||
|
||||
protected virtual ITestDatabase WithTestDb(MigrationContext migrationContext)
|
||||
{
|
||||
var database = CreateDatabase(migrationContext);
|
||||
var factory = Mocker.Resolve<DbFactory>();
|
||||
var database = factory.Create(migrationContext);
|
||||
Mocker.SetConstant(database);
|
||||
|
||||
switch (MigrationType)
|
||||
@@ -104,65 +98,6 @@ namespace NzbDrone.Core.Test.Framework
|
||||
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()
|
||||
{
|
||||
Mocker.SetConstant<ILoggerProvider>(NullLoggerProvider.Instance);
|
||||
@@ -173,13 +108,6 @@ namespace NzbDrone.Core.Test.Framework
|
||||
WithTempAsAppPath();
|
||||
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<IMigrationController>(Mocker.Resolve<MigrationController>());
|
||||
|
||||
@@ -199,19 +127,12 @@ namespace NzbDrone.Core.Test.Framework
|
||||
// Make sure there are no lingering connections. (When this happens it means we haven't disposed something properly)
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
|
||||
SQLiteConnection.ClearAllPools();
|
||||
NpgsqlConnection.ClearAllPools();
|
||||
|
||||
if (TestFolderInfo != null)
|
||||
{
|
||||
DeleteTempFolder(TestFolderInfo.AppDataFolder);
|
||||
}
|
||||
|
||||
if (_databaseType == DatabaseType.PostgreSQL)
|
||||
{
|
||||
DropPostgresDb();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
using System.IO.Abstractions;
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Test.Common.AutoMoq;
|
||||
using Unity.Resolution;
|
||||
|
||||
namespace NzbDrone.Core.Test.Framework
|
||||
{
|
||||
@@ -15,9 +14,12 @@ namespace NzbDrone.Core.Test.Framework
|
||||
[SetUp]
|
||||
public void FileSystemTestSetup()
|
||||
{
|
||||
FileSystem = (MockFileSystem)Mocker.Resolve<IFileSystem>(FileSystemType.Mock);
|
||||
FileSystem = new MockFileSystem();
|
||||
|
||||
DiskProvider = Mocker.Resolve<IDiskProvider>(FileSystemType.Mock);
|
||||
DiskProvider = Mocker.Resolve<IDiskProvider>("ActualDiskProvider", new ResolverOverride[]
|
||||
{
|
||||
new ParameterOverride("fileSystem", FileSystem)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ namespace NzbDrone.Core.Test.Framework
|
||||
where T : ModelBase, new();
|
||||
IDirectDataMapper GetDirectDataMapper();
|
||||
IDbConnection OpenConnection();
|
||||
DatabaseType DatabaseType { get; }
|
||||
}
|
||||
|
||||
public class TestDatabase : ITestDatabase
|
||||
@@ -31,8 +30,6 @@ namespace NzbDrone.Core.Test.Framework
|
||||
private readonly IDatabase _dbConnection;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
|
||||
public DatabaseType DatabaseType => _dbConnection.DatabaseType;
|
||||
|
||||
public TestDatabase(IDatabase dbConnection)
|
||||
{
|
||||
_eventAggregator = new Mock<IEventAggregator>().Object;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
@@ -135,13 +133,6 @@ 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);
|
||||
|
||||
covers.Single().Url.Should().Be("/MediaCover/12/banner" + extension);
|
||||
|
||||
@@ -13,7 +13,6 @@ using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
using NzbDrone.Test.Common.AutoMoq;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.AudioTagServiceFixture
|
||||
{
|
||||
@@ -56,7 +55,9 @@ namespace NzbDrone.Core.Test.MediaFiles.AudioTagServiceFixture
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_diskProvider = Mocker.Resolve<IDiskProvider>(FileSystemType.Actual);
|
||||
_diskProvider = Mocker.Resolve<IDiskProvider>("ActualDiskProvider");
|
||||
|
||||
Mocker.SetConstant<IDiskProvider>(_diskProvider);
|
||||
|
||||
Mocker.GetMock<IConfigService>()
|
||||
.Setup(x => x.WriteAudioTags)
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads
|
||||
}
|
||||
|
||||
[TestCase("Harry Potter and the sorcerer's stone", 3)]
|
||||
[TestCase("B0192CTMYG", 61209488)]
|
||||
[TestCase("B0192CTMYG", 42844155)]
|
||||
[TestCase("9780439554930", 48517161)]
|
||||
public void successful_book_search(string title, int expected)
|
||||
{
|
||||
|
||||
@@ -3,10 +3,8 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using FluentAssertions.Equivalency;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Books;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.MusicTests.BookRepositoryTests
|
||||
@@ -23,13 +21,6 @@ namespace NzbDrone.Core.Test.MusicTests.BookRepositoryTests
|
||||
[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
|
||||
{
|
||||
Name = "Alien Ant Farm",
|
||||
@@ -152,7 +143,7 @@ namespace NzbDrone.Core.Test.MusicTests.BookRepositoryTests
|
||||
GivenMultipleBooks();
|
||||
|
||||
var result = _bookRepo.GetNextBooks(new[] { _author.AuthorMetadataId });
|
||||
result.Should().BeEquivalentTo(_books.Take(1), BookComparerOptions);
|
||||
result.Should().BeEquivalentTo(_books.Take(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -161,11 +152,7 @@ namespace NzbDrone.Core.Test.MusicTests.BookRepositoryTests
|
||||
GivenMultipleBooks();
|
||||
|
||||
var result = _bookRepo.GetLastBooks(new[] { _author.AuthorMetadataId });
|
||||
result.Should().BeEquivalentTo(_books.Skip(2).Take(1), BookComparerOptions);
|
||||
result.Should().BeEquivalentTo(_books.Skip(2).Take(1));
|
||||
}
|
||||
|
||||
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,11 +3,9 @@ using System.Collections.Generic;
|
||||
using System.Data.SQLite;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Npgsql;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Books;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Profiles.Metadata;
|
||||
using NzbDrone.Core.Profiles.Qualities;
|
||||
using NzbDrone.Core.Qualities;
|
||||
@@ -147,14 +145,7 @@ namespace NzbDrone.Core.Test.MusicTests.AuthorRepositoryTests
|
||||
_authorRepo.Insert(author1);
|
||||
|
||||
Action insertDupe = () => _authorRepo.Insert(author2);
|
||||
if (Db.DatabaseType == DatabaseType.PostgreSQL)
|
||||
{
|
||||
insertDupe.Should().Throw<PostgresException>();
|
||||
}
|
||||
else
|
||||
{
|
||||
insertDupe.Should().Throw<SQLiteException>();
|
||||
}
|
||||
insertDupe.Should().Throw<SQLiteException>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ namespace NzbDrone.Core.Test.MusicTests
|
||||
private void GivenBooksForRefresh(List<Book> books)
|
||||
{
|
||||
Mocker.GetMock<IBookService>(MockBehavior.Strict)
|
||||
.Setup(s => s.GetBooksForRefresh(It.IsAny<int>(), It.IsAny<List<string>>()))
|
||||
.Setup(s => s.GetBooksForRefresh(It.IsAny<int>(), It.IsAny<IEnumerable<string>>()))
|
||||
.Returns(books);
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ namespace NzbDrone.Core.Test.MusicTests
|
||||
|
||||
Mocker.GetMock<IBookService>(MockBehavior.Strict)
|
||||
.InSequence(seq)
|
||||
.Setup(x => x.GetBooksForRefresh(It.IsAny<int>(), It.IsAny<List<string>>()))
|
||||
.Setup(x => x.GetBooksForRefresh(It.IsAny<int>(), It.IsAny<IEnumerable<string>>()))
|
||||
.Returns(new List<Book>());
|
||||
|
||||
// Update called twice for a move/merge
|
||||
@@ -298,7 +298,7 @@ namespace NzbDrone.Core.Test.MusicTests
|
||||
|
||||
Mocker.GetMock<IBookService>(MockBehavior.Strict)
|
||||
.InSequence(seq)
|
||||
.Setup(x => x.GetBooksForRefresh(clash.AuthorMetadataId, It.IsAny<List<string>>()))
|
||||
.Setup(x => x.GetBooksForRefresh(clash.AuthorMetadataId, It.IsAny<IEnumerable<string>>()))
|
||||
.Returns(_books);
|
||||
|
||||
// Update called twice for a move/merge
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace NzbDrone.Core.Test.UpdateTests
|
||||
[Test]
|
||||
public void finds_update_when_version_lower()
|
||||
{
|
||||
NotBsd();
|
||||
UseRealHttp();
|
||||
Subject.GetLatestUpdate("nightly", new Version(0, 1)).Should().NotBeNull();
|
||||
}
|
||||
@@ -42,6 +43,8 @@ namespace NzbDrone.Core.Test.UpdateTests
|
||||
[Test]
|
||||
public void should_get_recent_updates()
|
||||
{
|
||||
NotBsd();
|
||||
|
||||
const string branch = "nightly";
|
||||
UseRealHttp();
|
||||
var recent = Subject.GetRecentUpdates(branch, new Version(0, 1));
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace NzbDrone.Core.AuthorStats
|
||||
|
||||
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;
|
||||
|
||||
@@ -45,14 +45,14 @@ namespace NzbDrone.Core.AuthorStats
|
||||
}
|
||||
}
|
||||
|
||||
private SqlBuilder Builder() => new SqlBuilder(_database.DatabaseType)
|
||||
.Select(@"""Authors"".""Id"" AS ""AuthorId"",
|
||||
""Books"".""Id"" AS ""BookId"",
|
||||
SUM(COALESCE(""BookFiles"".""Size"", 0)) AS ""SizeOnDisk"",
|
||||
1 AS ""TotalBookCount"",
|
||||
CASE WHEN MIN(""BookFiles"".""Id"") IS NULL THEN 0 ELSE 1 END AS ""AvailableBookCount"",
|
||||
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 MIN(""BookFiles"".""Id"") IS NULL THEN 0 ELSE COUNT(""BookFiles"".""Id"") END AS ""BookFileCount""")
|
||||
private SqlBuilder Builder() => new SqlBuilder()
|
||||
.Select(@"Authors.Id AS AuthorId,
|
||||
Books.Id AS BookId,
|
||||
SUM(COALESCE(BookFiles.Size, 0)) AS SizeOnDisk,
|
||||
1 AS TotalBookCount,
|
||||
CASE WHEN 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 BookFiles.Id IS NULL THEN 0 ELSE COUNT(BookFiles.Id) END AS BookFileCount")
|
||||
.Join<Edition, Book>((e, b) => e.BookId == b.Id)
|
||||
.Join<Book, Author>((book, author) => book.AuthorMetadataId == author.AuthorMetadataId)
|
||||
.LeftJoin<Edition, BookFile>((t, f) => t.Id == f.EditionId)
|
||||
|
||||
@@ -15,14 +15,12 @@ namespace NzbDrone.Core.AuthorStats
|
||||
}
|
||||
|
||||
public class AuthorStatisticsService : IAuthorStatisticsService,
|
||||
IHandle<AuthorAddedEvent>,
|
||||
IHandle<AuthorUpdatedEvent>,
|
||||
IHandle<AuthorDeletedEvent>,
|
||||
IHandle<BookAddedEvent>,
|
||||
IHandle<BookDeletedEvent>,
|
||||
IHandle<BookImportedEvent>,
|
||||
IHandle<BookEditedEvent>,
|
||||
IHandle<BookUpdatedEvent>,
|
||||
IHandle<BookFileDeletedEvent>
|
||||
{
|
||||
private readonly IAuthorStatisticsRepository _authorStatisticsRepository;
|
||||
@@ -70,13 +68,6 @@ namespace NzbDrone.Core.AuthorStats
|
||||
return authorStatistics;
|
||||
}
|
||||
|
||||
[EventHandleOrder(EventHandleOrder.First)]
|
||||
public void Handle(AuthorAddedEvent message)
|
||||
{
|
||||
_cache.Remove("AllAuthors");
|
||||
_cache.Remove(message.Author.Id.ToString());
|
||||
}
|
||||
|
||||
[EventHandleOrder(EventHandleOrder.First)]
|
||||
public void Handle(AuthorUpdatedEvent message)
|
||||
{
|
||||
@@ -119,13 +110,6 @@ namespace NzbDrone.Core.AuthorStats
|
||||
_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)]
|
||||
public void Handle(BookFileDeletedEvent message)
|
||||
{
|
||||
|
||||
@@ -183,12 +183,9 @@ namespace NzbDrone.Core.Backup
|
||||
|
||||
private void BackupDatabase()
|
||||
{
|
||||
if (_maindDb.DatabaseType == DatabaseType.SQLite)
|
||||
{
|
||||
_logger.ProgressDebug("Backing up database");
|
||||
_logger.ProgressDebug("Backing up database");
|
||||
|
||||
_makeDatabaseBackup.BackupDatabase(_maindDb, _backupTempFolder);
|
||||
}
|
||||
_makeDatabaseBackup.BackupDatabase(_maindDb, _backupTempFolder);
|
||||
}
|
||||
|
||||
private void BackupConfigFile()
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Blocklisting
|
||||
return Query(b => b.AuthorId == authorId);
|
||||
}
|
||||
|
||||
protected override SqlBuilder PagedBuilder() => new SqlBuilder(_database.DatabaseType)
|
||||
protected override SqlBuilder PagedBuilder() => new SqlBuilder()
|
||||
.Join<Blocklist, Author>((b, m) => b.AuthorId == m.Id)
|
||||
.Join<Author, AuthorMetadata>((l, r) => l.AuthorMetadataId == r.Id);
|
||||
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(_database.DatabaseType)
|
||||
protected override SqlBuilder Builder() => new SqlBuilder()
|
||||
.Join<Author, AuthorMetadata>((a, m) => a.AuthorMetadataId == m.Id);
|
||||
|
||||
protected override List<Author> Query(SqlBuilder builder) => Query(_database, builder).ToList();
|
||||
@@ -60,7 +60,7 @@ namespace NzbDrone.Core.Books
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace NzbDrone.Core.Books
|
||||
List<Book> GetLastBooks(IEnumerable<int> authorMetadataIds);
|
||||
List<Book> GetNextBooks(IEnumerable<int> authorMetadataIds);
|
||||
List<Book> GetBooksByAuthorMetadataId(int authorMetadataId);
|
||||
List<Book> GetBooksForRefresh(int authorMetadataId, List<string> foreignIds);
|
||||
List<Book> GetBooksForRefresh(int authorMetadataId, IEnumerable<string> foreignIds);
|
||||
List<Book> GetBooksByFileIds(IEnumerable<int> fileIds);
|
||||
Book FindByTitle(int authorMetadataId, string title);
|
||||
Book FindById(string foreignBookId);
|
||||
@@ -44,35 +44,17 @@ namespace NzbDrone.Core.Books
|
||||
public List<Book> GetLastBooks(IEnumerable<int> authorMetadataIds)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
var inner = Builder()
|
||||
.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);
|
||||
return Query(Builder().Where<Book>(x => authorMetadataIds.Contains(x.AuthorMetadataId) && x.ReleaseDate < now)
|
||||
.GroupBy<Book>(x => x.AuthorMetadataId)
|
||||
.Having("Books.ReleaseDate = MAX(Books.ReleaseDate)"));
|
||||
}
|
||||
|
||||
public List<Book> GetNextBooks(IEnumerable<int> authorMetadataIds)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
var inner = Builder()
|
||||
.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);
|
||||
return Query(Builder().Where<Book>(x => authorMetadataIds.Contains(x.AuthorMetadataId) && x.ReleaseDate > now)
|
||||
.GroupBy<Book>(x => x.AuthorMetadataId)
|
||||
.Having("Books.ReleaseDate = MIN(Books.ReleaseDate)"));
|
||||
}
|
||||
|
||||
public List<Book> GetBooksByAuthorMetadataId(int authorMetadataId)
|
||||
@@ -80,14 +62,14 @@ namespace NzbDrone.Core.Books
|
||||
return Query(s => s.AuthorMetadataId == authorMetadataId);
|
||||
}
|
||||
|
||||
public List<Book> GetBooksForRefresh(int authorMetadataId, List<string> foreignIds)
|
||||
public List<Book> GetBooksForRefresh(int authorMetadataId, IEnumerable<string> foreignIds)
|
||||
{
|
||||
return Query(a => a.AuthorMetadataId == authorMetadataId || foreignIds.Contains(a.ForeignBookId));
|
||||
}
|
||||
|
||||
public List<Book> GetBooksByFileIds(IEnumerable<int> fileIds)
|
||||
{
|
||||
return Query(new SqlBuilder(_database.DatabaseType)
|
||||
return Query(new SqlBuilder()
|
||||
.Join<Book, Edition>((b, e) => b.Id == e.BookId)
|
||||
.Join<Edition, BookFile>((l, r) => l.Id == r.EditionId)
|
||||
.Where<BookFile>(f => fileIds.Contains(f.Id)))
|
||||
@@ -143,7 +125,7 @@ namespace NzbDrone.Core.Books
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +136,7 @@ namespace NzbDrone.Core.Books
|
||||
{
|
||||
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**/) AS \"Inner\"";
|
||||
var countTemplate = $"SELECT COUNT(*) FROM (SELECT /**select**/ FROM {TableMapping.Mapper.TableNameMapping(typeof(Book))} /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/)";
|
||||
pagingSpec.TotalRecords = GetPagedRecordCount(BooksWhereCutoffUnmetBuilder(qualitiesBelowCutoff).Select(typeof(Book)), pagingSpec, countTemplate);
|
||||
|
||||
return pagingSpec;
|
||||
|
||||
@@ -11,11 +11,11 @@ namespace NzbDrone.Core.Books
|
||||
{
|
||||
List<Edition> GetAllMonitoredEditions();
|
||||
Edition FindByForeignEditionId(string foreignEditionId);
|
||||
List<Edition> FindByBook(IEnumerable<int> ids);
|
||||
List<Edition> FindByBook(int id);
|
||||
List<Edition> FindByAuthor(int id);
|
||||
List<Edition> FindByAuthorMetadataId(int id, bool onlyMonitored);
|
||||
Edition FindByTitle(int authorMetadataId, string title);
|
||||
List<Edition> GetEditionsForRefresh(int bookId, List<string> foreignEditionIds);
|
||||
List<Edition> GetEditionsForRefresh(int bookId, IEnumerable<string> foreignEditionIds);
|
||||
List<Edition> SetMonitored(Edition edition);
|
||||
}
|
||||
|
||||
@@ -38,19 +38,19 @@ namespace NzbDrone.Core.Books
|
||||
return edition;
|
||||
}
|
||||
|
||||
public List<Edition> GetEditionsForRefresh(int bookId, List<string> foreignEditionIds)
|
||||
public List<Edition> GetEditionsForRefresh(int bookId, IEnumerable<string> foreignEditionIds)
|
||||
{
|
||||
return Query(r => r.BookId == bookId || foreignEditionIds.Contains(r.ForeignEditionId));
|
||||
}
|
||||
|
||||
public List<Edition> FindByBook(IEnumerable<int> ids)
|
||||
public List<Edition> FindByBook(int id)
|
||||
{
|
||||
// populate the books and author metadata also
|
||||
// this hopefully speeds up the track matching a lot
|
||||
var builder = new SqlBuilder(_database.DatabaseType)
|
||||
var builder = new SqlBuilder()
|
||||
.LeftJoin<Edition, Book>((e, b) => e.BookId == b.Id)
|
||||
.LeftJoin<Book, AuthorMetadata>((b, a) => b.AuthorMetadataId == a.Id)
|
||||
.Where<Edition>(r => ids.Contains(r.BookId));
|
||||
.Where<Edition>(r => r.BookId == id);
|
||||
|
||||
return _database.QueryJoined<Edition, Book, AuthorMetadata>(builder, (edition, book, metadata) =>
|
||||
{
|
||||
@@ -96,7 +96,7 @@ namespace NzbDrone.Core.Books
|
||||
|
||||
public List<Edition> SetMonitored(Edition edition)
|
||||
{
|
||||
var allEditions = FindByBook(new[] { edition.BookId });
|
||||
var allEditions = FindByBook(edition.BookId);
|
||||
allEditions.ForEach(r => r.Monitored = r.Id == edition.Id);
|
||||
Ensure.That(allEditions.Count(x => x.Monitored) == 1).IsTrue();
|
||||
UpdateMany(allEditions);
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace NzbDrone.Core.Books
|
||||
public interface ISeriesRepository : IBasicRepository<Series>
|
||||
{
|
||||
Series FindById(string foreignSeriesId);
|
||||
List<Series> FindById(List<string> foreignSeriesId);
|
||||
List<Series> FindById(IEnumerable<string> foreignSeriesId);
|
||||
List<Series> GetByAuthorMetadataId(int authorMetadataId);
|
||||
List<Series> GetByAuthorId(int authorId);
|
||||
}
|
||||
@@ -25,7 +25,7 @@ namespace NzbDrone.Core.Books
|
||||
return Query(x => x.ForeignSeriesId == foreignSeriesId).SingleOrDefault();
|
||||
}
|
||||
|
||||
public List<Series> FindById(List<string> foreignSeriesId)
|
||||
public List<Series> FindById(IEnumerable<string> foreignSeriesId)
|
||||
{
|
||||
return Query(x => foreignSeriesId.Contains(x.ForeignSeriesId));
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace NzbDrone.Core.Books
|
||||
List<Book> GetNextBooksByAuthorMetadataId(IEnumerable<int> authorMetadataIds);
|
||||
List<Book> GetLastBooksByAuthorMetadataId(IEnumerable<int> authorMetadataIds);
|
||||
List<Book> GetBooksByAuthorMetadataId(int authorMetadataId);
|
||||
List<Book> GetBooksForRefresh(int authorMetadataId, List<string> foreignIds);
|
||||
List<Book> GetBooksForRefresh(int authorMetadataId, IEnumerable<string> foreignIds);
|
||||
List<Book> GetBooksByFileIds(IEnumerable<int> fileIds);
|
||||
Book AddBook(Book newBook, bool doRefresh = true);
|
||||
Book FindById(string foreignId);
|
||||
@@ -206,7 +206,7 @@ namespace NzbDrone.Core.Books
|
||||
return _bookRepository.GetBooksByAuthorMetadataId(authorMetadataId).ToList();
|
||||
}
|
||||
|
||||
public List<Book> GetBooksForRefresh(int authorMetadataId, List<string> foreignIds)
|
||||
public List<Book> GetBooksForRefresh(int authorMetadataId, IEnumerable<string> foreignIds)
|
||||
{
|
||||
return _bookRepository.GetBooksForRefresh(authorMetadataId, foreignIds);
|
||||
}
|
||||
|
||||
@@ -16,9 +16,8 @@ namespace NzbDrone.Core.Books
|
||||
void InsertMany(List<Edition> editions);
|
||||
void UpdateMany(List<Edition> editions);
|
||||
void DeleteMany(List<Edition> editions);
|
||||
List<Edition> GetEditionsForRefresh(int bookId, List<string> foreignEditionIds);
|
||||
List<Edition> GetEditionsForRefresh(int bookId, IEnumerable<string> foreignEditionIds);
|
||||
List<Edition> GetEditionsByBook(int bookId);
|
||||
List<Edition> GetEditionsByBook(IEnumerable<int> bookIds);
|
||||
List<Edition> GetEditionsByAuthor(int authorId);
|
||||
Edition FindByTitle(int authorMetadataId, string title);
|
||||
Edition FindByTitleInexact(int authorMetadataId, string title);
|
||||
@@ -73,19 +72,14 @@ namespace NzbDrone.Core.Books
|
||||
}
|
||||
}
|
||||
|
||||
public List<Edition> GetEditionsForRefresh(int bookId, List<string> foreignEditionIds)
|
||||
public List<Edition> GetEditionsForRefresh(int bookId, IEnumerable<string> foreignEditionIds)
|
||||
{
|
||||
return _editionRepository.GetEditionsForRefresh(bookId, foreignEditionIds);
|
||||
}
|
||||
|
||||
public List<Edition> GetEditionsByBook(int bookId)
|
||||
{
|
||||
return _editionRepository.FindByBook(new[] { bookId });
|
||||
}
|
||||
|
||||
public List<Edition> GetEditionsByBook(IEnumerable<int> bookIds)
|
||||
{
|
||||
return _editionRepository.FindByBook(bookIds);
|
||||
return _editionRepository.FindByBook(bookId);
|
||||
}
|
||||
|
||||
public List<Edition> GetEditionsByAuthor(int authorId)
|
||||
|
||||
@@ -238,7 +238,7 @@ namespace NzbDrone.Core.Books
|
||||
protected override List<Book> GetLocalChildren(Author entity, List<Book> remoteChildren)
|
||||
{
|
||||
return _bookService.GetBooksForRefresh(entity.AuthorMetadataId,
|
||||
remoteChildren.Select(x => x.ForeignBookId).ToList());
|
||||
remoteChildren.Select(x => x.ForeignBookId));
|
||||
}
|
||||
|
||||
protected override Tuple<Book, List<Book>> GetMatchingExistingChildren(List<Book> existingChildren, Book remote)
|
||||
|
||||
@@ -14,7 +14,6 @@ using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.MetadataSource;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
|
||||
namespace NzbDrone.Core.Books
|
||||
{
|
||||
@@ -31,7 +30,6 @@ namespace NzbDrone.Core.Books
|
||||
{
|
||||
private readonly IBookService _bookService;
|
||||
private readonly IAuthorService _authorService;
|
||||
private readonly IRootFolderService _rootFolderService;
|
||||
private readonly IAddAuthorService _addAuthorService;
|
||||
private readonly IEditionService _editionService;
|
||||
private readonly IProvideAuthorInfo _authorInfo;
|
||||
@@ -46,7 +44,6 @@ namespace NzbDrone.Core.Books
|
||||
|
||||
public RefreshBookService(IBookService bookService,
|
||||
IAuthorService authorService,
|
||||
IRootFolderService rootFolderService,
|
||||
IAddAuthorService addAuthorService,
|
||||
IEditionService editionService,
|
||||
IAuthorMetadataService authorMetadataService,
|
||||
@@ -63,7 +60,6 @@ namespace NzbDrone.Core.Books
|
||||
{
|
||||
_bookService = bookService;
|
||||
_authorService = authorService;
|
||||
_rootFolderService = rootFolderService;
|
||||
_addAuthorService = addAuthorService;
|
||||
_editionService = editionService;
|
||||
_authorInfo = authorInfo;
|
||||
@@ -146,7 +142,7 @@ namespace NzbDrone.Core.Books
|
||||
Metadata = remote.AuthorMetadata.Value,
|
||||
MetadataProfileId = oldAuthor.MetadataProfileId,
|
||||
QualityProfileId = oldAuthor.QualityProfileId,
|
||||
RootFolderPath = _rootFolderService.GetBestRootFolderPath(oldAuthor.Path),
|
||||
RootFolderPath = oldAuthor.RootFolderPath,
|
||||
Monitored = oldAuthor.Monitored,
|
||||
Tags = oldAuthor.Tags
|
||||
};
|
||||
@@ -250,7 +246,7 @@ namespace NzbDrone.Core.Books
|
||||
|
||||
protected override List<Edition> GetLocalChildren(Book entity, List<Edition> remoteChildren)
|
||||
{
|
||||
return _editionService.GetEditionsForRefresh(entity.Id, remoteChildren.Select(x => x.ForeignEditionId).ToList());
|
||||
return _editionService.GetEditionsForRefresh(entity.Id, remoteChildren.Select(x => x.ForeignEditionId));
|
||||
}
|
||||
|
||||
protected override Tuple<Edition, List<Edition>> GetMatchingExistingChildren(List<Edition> existingChildren, Edition remote)
|
||||
|
||||
@@ -132,7 +132,7 @@ namespace NzbDrone.Core.Books
|
||||
var updated = false;
|
||||
|
||||
var existingByAuthor = _seriesService.GetByAuthorMetadataId(authorMetadataId);
|
||||
var existingBySeries = _seriesService.FindById(remoteSeries.Select(x => x.ForeignSeriesId).ToList());
|
||||
var existingBySeries = _seriesService.FindById(remoteSeries.Select(x => x.ForeignSeriesId));
|
||||
var existing = existingByAuthor.Concat(existingBySeries).GroupBy(x => x.ForeignSeriesId).Select(x => x.First()).ToList();
|
||||
|
||||
var books = _bookService.GetBooksByAuthorMetadataId(authorMetadataId);
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace NzbDrone.Core.Books
|
||||
public interface ISeriesService
|
||||
{
|
||||
Series FindById(string foreignSeriesId);
|
||||
List<Series> FindById(List<string> foreignSeriesId);
|
||||
List<Series> FindById(IEnumerable<string> foreignSeriesId);
|
||||
List<Series> GetByAuthorMetadataId(int authorMetadataId);
|
||||
List<Series> GetByAuthorId(int authorId);
|
||||
void Delete(int seriesId);
|
||||
@@ -27,7 +27,7 @@ namespace NzbDrone.Core.Books
|
||||
return _seriesRepository.FindById(foreignSeriesId);
|
||||
}
|
||||
|
||||
public List<Series> FindById(List<string> foreignSeriesId)
|
||||
public List<Series> FindById(IEnumerable<string> foreignSeriesId)
|
||||
{
|
||||
return _seriesRepository.FindById(foreignSeriesId);
|
||||
}
|
||||
|
||||
@@ -4,14 +4,12 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Authentication;
|
||||
using NzbDrone.Core.Configuration.Events;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Lifecycle;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
@@ -20,7 +18,7 @@ using NzbDrone.Core.Update;
|
||||
namespace NzbDrone.Core.Configuration
|
||||
{
|
||||
public interface IConfigFileProvider : IHandleAsync<ApplicationStartedEvent>,
|
||||
IExecute<ResetApiKeyCommand>
|
||||
IExecute<ResetApiKeyCommand>
|
||||
{
|
||||
Dictionary<string, object> GetConfigDictionary();
|
||||
void SaveConfigDictionary(Dictionary<string, object> configValues);
|
||||
@@ -43,20 +41,9 @@ namespace NzbDrone.Core.Configuration
|
||||
string SslCertPassword { get; }
|
||||
string UrlBase { get; }
|
||||
string UiFolder { get; }
|
||||
string InstanceName { get; }
|
||||
bool UpdateAutomatically { get; }
|
||||
UpdateMechanism UpdateMechanism { 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
|
||||
@@ -66,7 +53,6 @@ namespace NzbDrone.Core.Configuration
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly ICached<string> _cache;
|
||||
private readonly PostgresOptions _postgresOptions;
|
||||
|
||||
private readonly string _configFile;
|
||||
|
||||
@@ -75,14 +61,12 @@ namespace NzbDrone.Core.Configuration
|
||||
public ConfigFileProvider(IAppFolderInfo appFolderInfo,
|
||||
ICacheManager cacheManager,
|
||||
IEventAggregator eventAggregator,
|
||||
IDiskProvider diskProvider,
|
||||
IOptions<PostgresOptions> postgresOptions)
|
||||
IDiskProvider diskProvider)
|
||||
{
|
||||
_cache = cacheManager.GetCache<string>(GetType());
|
||||
_eventAggregator = eventAggregator;
|
||||
_diskProvider = diskProvider;
|
||||
_configFile = appFolderInfo.GetConfigPath();
|
||||
_postgresOptions = postgresOptions.Value;
|
||||
}
|
||||
|
||||
public Dictionary<string, object> GetConfigDictionary()
|
||||
@@ -196,13 +180,6 @@ namespace NzbDrone.Core.Configuration
|
||||
|
||||
public string LogLevel => GetValue("LogLevel", "info");
|
||||
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 int LogRotate => GetValueInt("LogRotate", 50, persist: false);
|
||||
public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false);
|
||||
@@ -225,7 +202,6 @@ namespace NzbDrone.Core.Configuration
|
||||
}
|
||||
|
||||
public string UiFolder => BuildInfo.IsDebug ? Path.Combine("..", "UI") : "UI";
|
||||
public string InstanceName => GetValue("InstanceName", BuildInfo.AppName);
|
||||
|
||||
public bool UpdateAutomatically => GetValueBoolean("UpdateAutomatically", false, false);
|
||||
|
||||
@@ -233,12 +209,6 @@ namespace NzbDrone.Core.Configuration
|
||||
|
||||
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)
|
||||
{
|
||||
return Convert.ToInt32(GetValue(key, defaultValue, persist));
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace NzbDrone.Core.Datastore
|
||||
_updateSql = GetUpdateSql(_properties);
|
||||
}
|
||||
|
||||
protected virtual SqlBuilder Builder() => new SqlBuilder(_database.DatabaseType);
|
||||
protected virtual SqlBuilder Builder() => new SqlBuilder();
|
||||
|
||||
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())
|
||||
{
|
||||
return conn.ExecuteScalar<int>($"SELECT COUNT(*) FROM \"{_table}\"");
|
||||
return conn.ExecuteScalar<int>($"SELECT COUNT(*) FROM {_table}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,11 +176,6 @@ 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";
|
||||
}
|
||||
|
||||
@@ -199,8 +194,7 @@ namespace NzbDrone.Core.Datastore
|
||||
throw;
|
||||
}
|
||||
|
||||
var multiRead = multi.Read();
|
||||
var id = (int)(multiRead.First().id ?? multiRead.First().Id);
|
||||
var id = (int)multi.Read().First().id;
|
||||
_keyProperty.SetValue(model, id);
|
||||
|
||||
return model;
|
||||
@@ -311,7 +305,7 @@ namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
using (var conn = _database.OpenConnection())
|
||||
{
|
||||
conn.Execute($"DELETE FROM \"{_table}\"");
|
||||
conn.Execute($"DELETE FROM [{_table}]");
|
||||
}
|
||||
|
||||
if (vacuum)
|
||||
@@ -370,7 +364,7 @@ namespace NzbDrone.Core.Datastore
|
||||
private string GetUpdateSql(List<PropertyInfo> propertiesToUpdate)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendFormat("UPDATE \"{0}\" SET ", _table);
|
||||
sb.AppendFormat("UPDATE {0} SET ", _table);
|
||||
|
||||
for (var i = 0; i < propertiesToUpdate.Count; i++)
|
||||
{
|
||||
@@ -452,10 +446,9 @@ namespace NzbDrone.Core.Datastore
|
||||
pagingSpec.SortKey = $"{_table}.{_keyProperty.Name}";
|
||||
}
|
||||
|
||||
var sortKey = TableMapping.Mapper.GetSortKey(pagingSpec.SortKey);
|
||||
var sortDirection = pagingSpec.SortDirection == SortDirection.Descending ? "DESC" : "ASC";
|
||||
var pagingOffset = Math.Max(pagingSpec.Page - 1, 0) * pagingSpec.PageSize;
|
||||
builder.OrderBy($"\"{sortKey}\" {sortDirection} LIMIT {pagingSpec.PageSize} OFFSET {pagingOffset}");
|
||||
var pagingOffset = (pagingSpec.Page - 1) * pagingSpec.PageSize;
|
||||
builder.OrderBy($"{pagingSpec.SortKey} {sortDirection} LIMIT {pagingSpec.PageSize} OFFSET {pagingOffset}");
|
||||
|
||||
return queryFunc(builder).ToList();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Data;
|
||||
|
||||
namespace NzbDrone.Core.Datastore
|
||||
@@ -10,12 +10,10 @@ namespace NzbDrone.Core.Datastore
|
||||
public class CacheDatabase : ICacheDatabase
|
||||
{
|
||||
private readonly IDatabase _database;
|
||||
private readonly DatabaseType _databaseType;
|
||||
|
||||
public CacheDatabase(IDatabase database)
|
||||
{
|
||||
_database = database;
|
||||
_databaseType = _database == null ? DatabaseType.SQLite : _database.DatabaseType;
|
||||
}
|
||||
|
||||
public IDbConnection OpenConnection()
|
||||
@@ -27,8 +25,6 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
public int Migration => _database.Migration;
|
||||
|
||||
public DatabaseType DatabaseType => _databaseType;
|
||||
|
||||
public void Vacuum()
|
||||
{
|
||||
_database.Vacuum();
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using System;
|
||||
using System.Data.SQLite;
|
||||
using Npgsql;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
@@ -17,20 +15,11 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
public class ConnectionStringFactory : IConnectionStringFactory
|
||||
{
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
|
||||
public ConnectionStringFactory(IAppFolderInfo appFolderInfo, IConfigFileProvider configFileProvider)
|
||||
public ConnectionStringFactory(IAppFolderInfo appFolderInfo)
|
||||
{
|
||||
_configFileProvider = configFileProvider;
|
||||
|
||||
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());
|
||||
MainDbConnectionString = GetConnectionString(appFolderInfo.GetDatabase());
|
||||
LogDbConnectionString = GetConnectionString(appFolderInfo.GetLogDatabase());
|
||||
CacheDbConnectionString = GetConnectionString(appFolderInfo.GetCacheDatabase());
|
||||
}
|
||||
|
||||
public string MainDbConnectionString { get; private set; }
|
||||
@@ -62,19 +51,5 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Text.RegularExpressions;
|
||||
using Dapper;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
@@ -12,7 +11,6 @@ namespace NzbDrone.Core.Datastore
|
||||
IDbConnection OpenConnection();
|
||||
Version Version { get; }
|
||||
int Migration { get; }
|
||||
DatabaseType DatabaseType { get; }
|
||||
void Vacuum();
|
||||
}
|
||||
|
||||
@@ -34,44 +32,13 @@ namespace NzbDrone.Core.Datastore
|
||||
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
|
||||
{
|
||||
get
|
||||
{
|
||||
using (var db = _datamapperFactory())
|
||||
{
|
||||
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()");
|
||||
}
|
||||
|
||||
var version = db.QueryFirstOrDefault<string>("SELECT sqlite_version()");
|
||||
return new Version(version);
|
||||
}
|
||||
}
|
||||
@@ -83,7 +50,7 @@ namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,10 +73,4 @@ namespace NzbDrone.Core.Datastore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum DatabaseType
|
||||
{
|
||||
SQLite,
|
||||
PostgreSQL
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using System.Data.SQLite;
|
||||
using NLog;
|
||||
using Npgsql;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Exceptions;
|
||||
@@ -94,19 +92,10 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
var db = new Database(migrationContext.MigrationType.ToString(), () =>
|
||||
{
|
||||
DbConnection conn;
|
||||
|
||||
if (connectionString.Contains(".db"))
|
||||
{
|
||||
conn = SQLiteFactory.Instance.CreateConnection();
|
||||
conn.ConnectionString = connectionString;
|
||||
}
|
||||
else
|
||||
{
|
||||
conn = new NpgsqlConnection(connectionString);
|
||||
}
|
||||
|
||||
var conn = SQLiteFactory.Instance.CreateConnection();
|
||||
conn.ConnectionString = connectionString;
|
||||
conn.Open();
|
||||
|
||||
return conn;
|
||||
});
|
||||
|
||||
|
||||
@@ -20,12 +20,12 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
@@ -42,48 +42,41 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
public static SqlBuilder Where<TModel>(this SqlBuilder builder, Expression<Func<TModel, bool>> filter)
|
||||
{
|
||||
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);
|
||||
var wb = new WhereBuilder(filter, true, builder.Sequence);
|
||||
|
||||
return builder.Where(wb.ToString(), wb.Parameters);
|
||||
}
|
||||
|
||||
public static SqlBuilder OrWhere<TModel>(this SqlBuilder builder, Expression<Func<TModel, bool>> filter)
|
||||
{
|
||||
var wb = GetWhereBuilder(builder.DatabaseType, filter, true, builder.Sequence);
|
||||
var wb = new WhereBuilder(filter, true, builder.Sequence);
|
||||
|
||||
return builder.OrWhere(wb.ToString(), wb.Parameters);
|
||||
}
|
||||
|
||||
public static SqlBuilder Join<TLeft, TRight>(this SqlBuilder builder, Expression<Func<TLeft, TRight, bool>> filter)
|
||||
{
|
||||
var wb = GetWhereBuilder(builder.DatabaseType, filter, false, builder.Sequence);
|
||||
var wb = new WhereBuilder(filter, false, builder.Sequence);
|
||||
|
||||
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)
|
||||
{
|
||||
var wb = GetWhereBuilder(builder.DatabaseType, filter, false, builder.Sequence);
|
||||
var wb = new WhereBuilder(filter, false, builder.Sequence);
|
||||
|
||||
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)
|
||||
{
|
||||
var table = TableMapping.Mapper.TableNameMapping(typeof(TModel));
|
||||
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)
|
||||
@@ -145,18 +138,6 @@ namespace NzbDrone.Core.Datastore
|
||||
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)
|
||||
{
|
||||
var argsDictionary = new Dictionary<string, object>();
|
||||
|
||||
@@ -10,12 +10,10 @@ namespace NzbDrone.Core.Datastore
|
||||
public class LogDatabase : ILogDatabase
|
||||
{
|
||||
private readonly IDatabase _database;
|
||||
private readonly DatabaseType _databaseType;
|
||||
|
||||
public LogDatabase(IDatabase database)
|
||||
{
|
||||
_database = database;
|
||||
_databaseType = _database == null ? DatabaseType.SQLite : _database.DatabaseType;
|
||||
}
|
||||
|
||||
public IDbConnection OpenConnection()
|
||||
@@ -27,8 +25,6 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
public int Migration => _database.Migration;
|
||||
|
||||
public DatabaseType DatabaseType => _databaseType;
|
||||
|
||||
public void Vacuum()
|
||||
{
|
||||
_database.Vacuum();
|
||||
|
||||
@@ -10,12 +10,10 @@ namespace NzbDrone.Core.Datastore
|
||||
public class MainDatabase : IMainDatabase
|
||||
{
|
||||
private readonly IDatabase _database;
|
||||
private readonly DatabaseType _databaseType;
|
||||
|
||||
public MainDatabase(IDatabase database)
|
||||
{
|
||||
_database = database;
|
||||
_databaseType = _database == null ? DatabaseType.SQLite : _database.DatabaseType;
|
||||
}
|
||||
|
||||
public IDbConnection OpenConnection()
|
||||
@@ -27,8 +25,6 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
public int Migration => _database.Migration;
|
||||
|
||||
public DatabaseType DatabaseType => _databaseType;
|
||||
|
||||
public void Vacuum()
|
||||
{
|
||||
_database.Vacuum();
|
||||
|
||||
@@ -37,22 +37,6 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
.WithColumn("MetadataProfileId").AsInt32().WithDefaultValue(1)
|
||||
.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")
|
||||
.WithColumn("ForeignSeriesId").AsString().Unique()
|
||||
.WithColumn("Title").AsString()
|
||||
@@ -84,6 +68,22 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
.WithColumn("Ratings").AsString().Nullable()
|
||||
.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")
|
||||
.WithColumn("BookId").AsInt32().WithDefaultValue(0)
|
||||
.WithColumn("ForeignEditionId").AsString().Unique()
|
||||
@@ -136,12 +136,12 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
.WithColumn("OnUpgrade").AsBoolean().Nullable()
|
||||
.WithColumn("Tags").AsString().Nullable()
|
||||
.WithColumn("OnRename").AsBoolean().NotNullable()
|
||||
.WithColumn("OnReleaseImport").AsBoolean().WithDefaultValue(false)
|
||||
.WithColumn("OnHealthIssue").AsBoolean().WithDefaultValue(false)
|
||||
.WithColumn("IncludeHealthWarnings").AsBoolean().WithDefaultValue(false)
|
||||
.WithColumn("OnDownloadFailure").AsBoolean().WithDefaultValue(false)
|
||||
.WithColumn("OnImportFailure").AsBoolean().WithDefaultValue(false)
|
||||
.WithColumn("OnTrackRetag").AsBoolean().WithDefaultValue(false);
|
||||
.WithColumn("OnReleaseImport").AsBoolean().WithDefaultValue(0)
|
||||
.WithColumn("OnHealthIssue").AsBoolean().WithDefaultValue(0)
|
||||
.WithColumn("IncludeHealthWarnings").AsBoolean().WithDefaultValue(0)
|
||||
.WithColumn("OnDownloadFailure").AsBoolean().WithDefaultValue(0)
|
||||
.WithColumn("OnImportFailure").AsBoolean().WithDefaultValue(0)
|
||||
.WithColumn("OnTrackRetag").AsBoolean().WithDefaultValue(0);
|
||||
|
||||
Create.TableForModel("ScheduledTasks")
|
||||
.WithColumn("TypeName").AsString().Unique()
|
||||
@@ -327,8 +327,8 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
.WithColumn("Label").AsString().NotNullable()
|
||||
.WithColumn("Filters").AsString().NotNullable();
|
||||
|
||||
IfDatabase("sqlite").Create.Index().OnTable("Books").OnColumn("AuthorId");
|
||||
IfDatabase("sqlite").Create.Index().OnTable("Books").OnColumn("AuthorId").Ascending()
|
||||
Create.Index().OnTable("Books").OnColumn("AuthorId");
|
||||
Create.Index().OnTable("Books").OnColumn("AuthorId").Ascending()
|
||||
.OnColumn("ReleaseDate").Ascending();
|
||||
|
||||
Delete.Index().OnTable("History").OnColumn("BookId");
|
||||
@@ -340,15 +340,12 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
.OnColumn("Date").Descending();
|
||||
|
||||
Create.Index().OnTable("Authors").OnColumn("Monitored").Ascending();
|
||||
|
||||
Create.Index().OnTable("Books").OnColumn("AuthorMetadataId").Ascending();
|
||||
Create.Index().OnTable("Books").OnColumn("AuthorMetadataId").Ascending()
|
||||
.OnColumn("ReleaseDate").Ascending();
|
||||
|
||||
Insert.IntoTable("DelayProfiles").Row(new
|
||||
{
|
||||
EnableUsenet = true,
|
||||
EnableTorrent = true,
|
||||
EnableUsenet = 1,
|
||||
EnableTorrent = 1,
|
||||
PreferredProtocol = 1,
|
||||
UsenetDelay = 0,
|
||||
TorrentDelay = 0,
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Alter.Table("ImportLists").AddColumn("ShouldSearch").AsBoolean().WithDefaultValue(true);
|
||||
Alter.Table("ImportLists").AddColumn("ShouldSearch").AsInt32().WithDefaultValue(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
using (IDbCommand getFileChmodCmd = conn.CreateCommand())
|
||||
{
|
||||
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;
|
||||
if (fileChmod != null)
|
||||
@@ -35,7 +35,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
using (IDbCommand insertCmd = conn.CreateCommand())
|
||||
{
|
||||
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.ExecuteNonQuery();
|
||||
@@ -45,7 +45,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
using (IDbCommand deleteCmd = conn.CreateCommand())
|
||||
{
|
||||
deleteCmd.Transaction = tran;
|
||||
deleteCmd.CommandText = "DELETE FROM \"Config\" WHERE \"Key\" = 'filechmod'";
|
||||
deleteCmd.CommandText = "DELETE FROM Config WHERE Key = 'filechmod'";
|
||||
|
||||
deleteCmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -54,13 +54,13 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
_connection = conn;
|
||||
_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();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
_changedProfiles.Clear();
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
@@ -30,7 +30,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
IfDatabase("sqlite").Delete.Index().OnTable("Books").OnColumn("AuthorId");
|
||||
IfDatabase("sqlite").Delete.Index().OnTable("Books").OnColumns("AuthorId", "ReleaseDate");
|
||||
Delete.Index().OnTable("Books").OnColumn("AuthorId");
|
||||
Delete.Index().OnTable("Books").OnColumns("AuthorId", "ReleaseDate");
|
||||
|
||||
Create.Index().OnTable("Editions").OnColumn("BookId");
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Alter.Table("ImportLists").AddColumn("ShouldMonitorExisting").AsBoolean().WithDefaultValue(false);
|
||||
Alter.Table("ImportLists").AddColumn("ShouldMonitorExisting").AsInt32().WithDefaultValue(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,9 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
Create.Index().OnTable("DownloadHistory").OnColumn("AuthorId");
|
||||
Create.Index().OnTable("DownloadHistory").OnColumn("DownloadId");
|
||||
|
||||
IfDatabase("sqlite").Execute.WithConnection(InitialImportedDownloadHistory);
|
||||
Execute.WithConnection(InitialImportedDownloadHistory);
|
||||
|
||||
Execute.Sql("DELETE From History where EventType = 8;");
|
||||
}
|
||||
|
||||
private static readonly Dictionary<int, int> EventTypeMap = new Dictionary<int, int>()
|
||||
@@ -54,7 +56,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.Transaction = tran;
|
||||
cmd.CommandText = "SELECT \"AuthorId\", \"DownloadId\", \"EventType\", \"SourceTitle\", \"Date\", \"Data\" FROM \"History\" WHERE \"DownloadId\" IS NOT NULL AND \"EventType\" IN (1, 8, 4, 10, 7) GROUP BY \"EventType\", \"DownloadId\"";
|
||||
cmd.CommandText = "SELECT AuthorId, DownloadId, EventType, SourceTitle, Date, Data FROM History WHERE DownloadId IS NOT NULL AND EventType IN (1, 8, 4, 10, 7) GROUP BY EventType, DownloadId";
|
||||
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
@@ -85,15 +87,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
using (var updateCmd = conn.CreateCommand())
|
||||
{
|
||||
updateCmd.Transaction = tran;
|
||||
if (conn.GetType().FullName == "Npgsql.NpgsqlConnection")
|
||||
{
|
||||
updateCmd.CommandText = @"INSERT INTO ""DownloadHistory"" (""EventType"", ""AuthorId"", ""DownloadId"", ""SourceTitle"", ""Date"", ""Protocol"", ""Data"") VALUES ($1, $2, $3, $4, $5, $6, $7)";
|
||||
}
|
||||
else
|
||||
{
|
||||
updateCmd.CommandText = @"INSERT INTO ""DownloadHistory"" (""EventType"", ""AuthorId"", ""DownloadId"", ""SourceTitle"", ""Date"", ""Protocol"", ""Data"") VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
}
|
||||
|
||||
updateCmd.CommandText = @"INSERT INTO DownloadHistory (EventType, AuthorId, DownloadId, SourceTitle, Date, Protocol, Data) VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
updateCmd.AddParameter(downloadHistoryEventType);
|
||||
updateCmd.AddParameter(seriesId);
|
||||
updateCmd.AddParameter(downloadId);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user