Compare commits

...

24 commits

Author SHA1 Message Date
Robin Dadswell
d8f79c0189 Skip proxy tests on MacOsX 2025-11-19 19:58:33 +00:00
Robin Dadswell
3023eabad0 bump to 3.1.1 2025-11-14 23:22:09 +00:00
Robin Dadswell
563ffee615 chore: updated build images 2025-11-14 23:21:45 +00:00
Mark McDowall
350860e524 Add private IPv6 networks
(cherry picked from commit 52972e7efcce800560cbbaa64f5f76aaef6cbe77)
2025-11-09 10:21:01 +00:00
bakerboy448
65802559cb
Bump to 3.1.0 2025-10-30 05:53:04 -05:00
bakerboy448
9c9ebbca3f skip spotify and metadata tests temporarily 2025-10-29 19:48:49 -05:00
Mark McDowall
6bcead8bf6 Set known networks to RFC 1918 ranges during startup
(cherry picked from commit d10107739b9ed6a50165e5dd1dfae15c7e8aea56)
2025-10-29 19:48:49 -05:00
Polgonite
1992127e91 Fixed: qBittorrent /login API success check 2025-10-29 18:00:08 -05:00
Servarr
3e2858439f Automated API Docs update 2025-10-20 12:58:21 -05:00
bakerboy448
505df12def
Bump to 3.0.1 2025-10-18 23:21:52 -05:00
Mark McDowall
96e5a4df2f Change authentication to Forms if set to Basic
(cherry picked from commit dfb6fdfbeb7ce85b287b41fed80f2511727353e5)
2025-10-14 21:51:14 -05:00
Mark McDowall
7cdb4e4b52 New: Remove Basic Auth
(cherry picked from commit 0f9e063e2146812f6e963363eee70a524612f354)
2025-10-14 21:51:14 -05:00
Bogdan
5d141358da Pin System.Drawing.Common to 8.0.20 2025-10-14 21:51:14 -05:00
bakerboy448
d5984d7386 New: Support removed for linux-x86 2025-10-14 21:51:14 -05:00
bakerboy448
87d46be67a Bump to 3.0.0 2025-10-14 21:51:14 -05:00
Bogdan
8cd45394f3 Switch to FluentMigrator.Runner.Core to avoid extranous platform runners
(cherry picked from commit 0662b2f49efb23f977f75c2f583ae96939512fd1)
2025-10-14 21:51:14 -05:00
Bogdan
5e683145e3 Bump System.Data.SQLite to official 2.0.2
Bump sqlite3 to 3.50.4

(cherry picked from commit 5617ab200fe3bad5eb376d51e0cb9e520b7769fe)
2025-10-14 21:51:14 -05:00
Bogdan
61c23de168 Bump FluentMigrator to official 6.2.0
(cherry picked from commit b7ee3afa36a0bc7b4bd9e391082ad6751c5063cf)
2025-10-14 21:51:14 -05:00
Bogdan
826b8b5933 Parameter binding for API requests
(cherry picked from commit 074b293d62b56bf02d82ac12ad726777b5d1e478)
2025-10-14 21:51:14 -05:00
Bogdan
1a4c1b6db5 New: Migrate appdata folder for .NET 8 on OSX
(cherry picked from commit d62547236a5e506ca9b11bf369683691ed8d253b)
2025-10-14 21:51:14 -05:00
Bogdan
96328b8d95 New: Bump to .NET 8
(cherry picked from commit d6cee5094cdd3ad8342eda56632f5a66316e390c)
2025-10-14 21:51:14 -05:00
bakerboy448
80ea0bd0a8 Bump to 2.15 2025-10-13 19:46:26 -05:00
bakerboy448
985ecd31e8 Fixed: Audio File Detected actually use file path
Fixes #2162
2025-10-13 19:46:26 -05:00
Weblate
0501e61565 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Marcello Cuoghi <marcello.cuoghi@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/it/
Translation: Servarr/Lidarr
2025-10-05 11:05:12 -05:00
99 changed files with 449 additions and 422 deletions

View file

@ -2,7 +2,7 @@
// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet // README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
{ {
"name": "Lidarr", "name": "Lidarr",
"image": "mcr.microsoft.com/devcontainers/dotnet:1-6.0", "image": "mcr.microsoft.com/devcontainers/dotnet:1-8.0",
"features": { "features": {
"ghcr.io/devcontainers/features/node:1": { "ghcr.io/devcontainers/features/node:1": {
"nodeGypDependencies": true, "nodeGypDependencies": true,

2
.vscode/launch.json vendored
View file

@ -10,7 +10,7 @@
"request": "launch", "request": "launch",
"preLaunchTask": "build dotnet", "preLaunchTask": "build dotnet",
// If you have changed target frameworks, make sure to update the program path. // If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/_output/net6.0/Lidarr", "program": "${workspaceFolder}/_output/net8.0/Lidarr",
"args": [], "args": [],
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console

View file

@ -9,13 +9,13 @@ Setup guides, [FAQ](/lidarr/faq), the more information we have on the [wiki](htt
# Development # Development
Lidarr is written in C# (backend) and JS (frontend). The backend is built on the .NET6 (and _soon_ .NET8) framework, while the frontend utilizes Reactjs. Lidarr is written in C# (backend) and JS (frontend). The backend is built on the .NET 8 framework, while the frontend utilizes Reactjs.
## Tools required ## Tools required
- Visual Studio 2022 or higher is recommended (<https://www.visualstudio.com/vs/>). The community version is free and works (<https://www.visualstudio.com/downloads/>). - Visual Studio 2022 or higher is recommended (<https://www.visualstudio.com/vs/>). The community version is free and works (<https://www.visualstudio.com/downloads/>).
> VS 2022 V17.0 or higher is recommended as it includes the .NET6 SDK > VS 2022 V17.0 or higher is recommended as it includes the .NET 8 SDK
{.is-info} {.is-info}
- HTML/Javascript editor of choice (VS Code/Sublime Text/Webstorm/Atom/etc) - HTML/Javascript editor of choice (VS Code/Sublime Text/Webstorm/Atom/etc)
@ -24,7 +24,7 @@ Lidarr is written in C# (backend) and JS (frontend). The backend is built on the
- **20** (any minor or patch version within this) - **20** (any minor or patch version within this)
{.grid-list} {.grid-list}
> The Application will **NOT** run on older versions such as `18.x`, `16.x` or any version below 20.0! Due to a dependency issue, it will also not run on `21.x` and is untested on other verisons. > The Application will **NOT** run on older versions such as `18.x`, `16.x` or any version below 20.0! Due to a dependency issue, it will also not run on `21.x` and is untested on other versions.
{.is-warning} {.is-warning}
- [Yarn](https://yarnpkg.com/getting-started/install) is required to build the frontend - [Yarn](https://yarnpkg.com/getting-started/install) is required to build the frontend
@ -60,7 +60,7 @@ The backend solution is most easily built and ran in Visual Studio or Rider, how
#### Visual Studio #### Visual Studio
> Ensure startup project is set to `Lidarr.Console` and framework to `net6.0` > Ensure startup project is set to `Lidarr.Console` and framework to `net8.0`
{.is-info} {.is-info}
1. First `Build` the solution in Visual Studio, this will ensure all projects are correctly built and dependencies restored 1. First `Build` the solution in Visual Studio, this will ensure all projects are correctly built and dependencies restored

View file

@ -9,18 +9,18 @@ variables:
testsFolder: './_tests' testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '2.14.5' majorVersion: '3.1.1'
minorVersion: $[counter('minorVersion', 1076)] minorVersion: $[counter('minorVersion', 1076)]
lidarrVersion: '$(majorVersion).$(minorVersion)' lidarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(lidarrVersion)' buildName: '$(Build.SourceBranchName).$(lidarrVersion)'
sentryOrg: 'servarr' sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com' sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.427' dotnetVersion: '8.0.405'
nodeVersion: '20.X' nodeVersion: '20.X'
innoVersion: '6.2.0' innoVersion: '6.2.0'
windowsImage: 'windows-2022' windowsImage: 'windows-2025'
linuxImage: 'ubuntu-22.04' linuxImage: 'ubuntu-24.04'
macImage: 'macOS-13' macImage: 'macOS-15'
trigger: trigger:
branches: branches:
@ -106,7 +106,7 @@ stages:
echo "Extra platforms already enabled" echo "Extra platforms already enabled"
else else
echo "Enabling extra platform support" echo "Enabling extra platform support"
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64/' "$BUNDLEDVERSIONS"
fi fi
displayName: Enable Extra Platform Support displayName: Enable Extra Platform Support
- bash: ./build.sh --backend --enable-extra-platforms - bash: ./build.sh --backend --enable-extra-platforms
@ -122,27 +122,23 @@ stages:
artifact: '$(osName)Backend' artifact: '$(osName)Backend'
displayName: Publish Backend displayName: Publish Backend
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/win-x64/publish' - publish: '$(testsFolder)/net8.0/win-x64/publish'
artifact: win-x64-tests artifact: win-x64-tests
displayName: Publish win-x64 Test Package displayName: Publish win-x64 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/linux-x64/publish' - publish: '$(testsFolder)/net8.0/linux-x64/publish'
artifact: linux-x64-tests artifact: linux-x64-tests
displayName: Publish linux-x64 Test Package displayName: Publish linux-x64 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/linux-x86/publish' - publish: '$(testsFolder)/net8.0/linux-musl-x64/publish'
artifact: linux-x86-tests
displayName: Publish linux-x86 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
artifact: linux-musl-x64-tests artifact: linux-musl-x64-tests
displayName: Publish linux-musl-x64 Test Package displayName: Publish linux-musl-x64 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish' - publish: '$(testsFolder)/net8.0/freebsd-x64/publish'
artifact: freebsd-x64-tests artifact: freebsd-x64-tests
displayName: Publish freebsd-x64 Test Package displayName: Publish freebsd-x64 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/osx-x64/publish' - publish: '$(testsFolder)/net8.0/osx-x64/publish'
artifact: osx-x64-tests artifact: osx-x64-tests
displayName: Publish osx-x64 Test Package displayName: Publish osx-x64 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
@ -260,21 +256,21 @@ stages:
archiveFile: '$(Build.ArtifactStagingDirectory)/Lidarr.$(buildName).windows-core-x64.zip' archiveFile: '$(Build.ArtifactStagingDirectory)/Lidarr.$(buildName).windows-core-x64.zip'
archiveType: 'zip' archiveType: 'zip'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/win-x64/net6.0 rootFolderOrFile: $(artifactsFolder)/win-x64/net8.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create win-x86 zip displayName: Create win-x86 zip
inputs: inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Lidarr.$(buildName).windows-core-x86.zip' archiveFile: '$(Build.ArtifactStagingDirectory)/Lidarr.$(buildName).windows-core-x86.zip'
archiveType: 'zip' archiveType: 'zip'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/win-x86/net6.0 rootFolderOrFile: $(artifactsFolder)/win-x86/net8.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create osx-x64 app displayName: Create osx-x64 app
inputs: inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Lidarr.$(buildName).osx-app-core-x64.zip' archiveFile: '$(Build.ArtifactStagingDirectory)/Lidarr.$(buildName).osx-app-core-x64.zip'
archiveType: 'zip' archiveType: 'zip'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/osx-x64-app/net6.0 rootFolderOrFile: $(artifactsFolder)/osx-x64-app/net8.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create osx-x64 tar displayName: Create osx-x64 tar
inputs: inputs:
@ -282,14 +278,14 @@ stages:
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/osx-x64/net6.0 rootFolderOrFile: $(artifactsFolder)/osx-x64/net8.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create osx-arm64 app displayName: Create osx-arm64 app
inputs: inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Lidarr.$(buildName).osx-app-core-arm64.zip' archiveFile: '$(Build.ArtifactStagingDirectory)/Lidarr.$(buildName).osx-app-core-arm64.zip'
archiveType: 'zip' archiveType: 'zip'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/osx-arm64-app/net6.0 rootFolderOrFile: $(artifactsFolder)/osx-arm64-app/net8.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create osx-arm64 tar displayName: Create osx-arm64 tar
inputs: inputs:
@ -297,7 +293,7 @@ stages:
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/osx-arm64/net6.0 rootFolderOrFile: $(artifactsFolder)/osx-arm64/net8.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create linux-x64 tar displayName: Create linux-x64 tar
inputs: inputs:
@ -305,7 +301,7 @@ stages:
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-x64/net6.0 rootFolderOrFile: $(artifactsFolder)/linux-x64/net8.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create linux-musl-x64 tar displayName: Create linux-musl-x64 tar
inputs: inputs:
@ -313,15 +309,7 @@ stages:
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net6.0 rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net8.0
- task: ArchiveFiles@2
displayName: Create linux-x86 tar
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Lidarr.$(buildName).linux-core-x86.tar.gz'
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-x86/net6.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create linux-arm tar displayName: Create linux-arm tar
inputs: inputs:
@ -329,7 +317,7 @@ stages:
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-arm/net6.0 rootFolderOrFile: $(artifactsFolder)/linux-arm/net8.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create linux-musl-arm tar displayName: Create linux-musl-arm tar
inputs: inputs:
@ -337,7 +325,7 @@ stages:
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm/net6.0 rootFolderOrFile: $(artifactsFolder)/linux-musl-arm/net8.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create linux-arm64 tar displayName: Create linux-arm64 tar
inputs: inputs:
@ -345,7 +333,7 @@ stages:
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net6.0 rootFolderOrFile: $(artifactsFolder)/linux-arm64/net8.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create linux-musl-arm64 tar displayName: Create linux-musl-arm64 tar
inputs: inputs:
@ -353,7 +341,7 @@ stages:
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net6.0 rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net8.0
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create freebsd-x64 tar displayName: Create freebsd-x64 tar
inputs: inputs:
@ -361,7 +349,7 @@ stages:
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/freebsd-x64/net6.0 rootFolderOrFile: $(artifactsFolder)/freebsd-x64/net8.0
- publish: $(Build.ArtifactStagingDirectory) - publish: $(Build.ArtifactStagingDirectory)
artifact: 'Packages' artifact: 'Packages'
displayName: Publish Packages displayName: Publish Packages
@ -493,10 +481,6 @@ stages:
testName: 'Musl Net Core' testName: 'Musl Net Core'
artifactName: linux-musl-x64-tests artifactName: linux-musl-x64-tests
containerImage: ghcr.io/servarr/testimages:alpine containerImage: ghcr.io/servarr/testimages:alpine
linux-x86:
testName: 'linux-x86'
artifactName: linux-x86-tests
containerImage: ghcr.io/servarr/testimages:linux-x86
pool: pool:
vmImage: ${{ variables.linuxImage }} vmImage: ${{ variables.linuxImage }}
@ -510,12 +494,6 @@ stages:
displayName: 'Install .NET' displayName: 'Install .NET'
inputs: inputs:
version: $(dotnetVersion) version: $(dotnetVersion)
condition: and(succeeded(), ne(variables['testName'], 'linux-x86'))
- bash: |
SDKURL=$(curl -s https://api.github.com/repos/Servarr/dotnet-linux-x86/releases | jq -rc '.[].assets[].browser_download_url' | grep sdk-${DOTNETVERSION}.*gz$)
curl -fsSL $SDKURL | tar xzf - -C /opt/dotnet
displayName: 'Install .NET'
condition: and(succeeded(), eq(variables['testName'], 'linux-x86'))
- checkout: none - checkout: none
- task: DownloadPipelineArtifact@2 - task: DownloadPipelineArtifact@2
displayName: Download Test Artifact displayName: Download Test Artifact
@ -922,11 +900,6 @@ stages:
artifactName: linux-musl-x64-tests artifactName: linux-musl-x64-tests
containerImage: ghcr.io/servarr/testimages:alpine containerImage: ghcr.io/servarr/testimages:alpine
pattern: 'Lidarr.*.linux-musl-core-x64.tar.gz' pattern: 'Lidarr.*.linux-musl-core-x64.tar.gz'
linux-x86:
testName: 'linux-x86'
artifactName: linux-x86-tests
containerImage: ghcr.io/servarr/testimages:linux-x86
pattern: 'Lidarr.*.linux-core-x86.tar.gz'
pool: pool:
vmImage: ${{ variables.linuxImage }} vmImage: ${{ variables.linuxImage }}
@ -939,12 +912,6 @@ stages:
displayName: 'Install .NET' displayName: 'Install .NET'
inputs: inputs:
version: $(dotnetVersion) version: $(dotnetVersion)
condition: and(succeeded(), ne(variables['testName'], 'linux-x86'))
- bash: |
SDKURL=$(curl -s https://api.github.com/repos/Servarr/dotnet-linux-x86/releases | jq -rc '.[].assets[].browser_download_url' | grep sdk-${DOTNETVERSION}.*gz$)
curl -fsSL $SDKURL | tar xzf - -C /opt/dotnet
displayName: 'Install .NET'
condition: and(succeeded(), eq(variables['testName'], 'linux-x86'))
- checkout: none - checkout: none
- task: DownloadPipelineArtifact@2 - task: DownloadPipelineArtifact@2
displayName: Download Test Artifact displayName: Download Test Artifact
@ -1223,13 +1190,13 @@ stages:
sonar.cs.cobertura.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.cobertura.xml sonar.cs.cobertura.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.cobertura.xml
sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml
- bash: | - bash: |
./build.sh --backend -f net6.0 -r win-x64 ./build.sh --backend -f net8.0 -r win-x64
TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage TEST_DIR=_tests/net8.0/win-x64/publish/ ./test.sh Windows Unit Coverage
displayName: Coverage Unit Tests displayName: Coverage Unit Tests
- task: SonarCloudAnalyze@3 - task: SonarCloudAnalyze@3
condition: eq(variables['System.PullRequest.IsFork'], 'False') condition: eq(variables['System.PullRequest.IsFork'], 'False')
displayName: Publish SonarCloud Results displayName: Publish SonarCloud Results
- task: reportgenerator@5.3.11 - task: reportgenerator@5
displayName: Generate Coverage Report displayName: Generate Coverage Report
inputs: inputs:
reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.cobertura.xml' reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.cobertura.xml'
@ -1267,4 +1234,3 @@ stages:
DISCORDCHANNELID: $(discordChannelId) DISCORDCHANNELID: $(discordChannelId)
DISCORDWEBHOOKKEY: $(discordWebhookKey) DISCORDWEBHOOKKEY: $(discordWebhookKey)
DISCORDTHREADID: $(discordThreadId) DISCORDTHREADID: $(discordThreadId)

View file

@ -33,14 +33,14 @@ EnableExtraPlatformsInSDK()
echo "Extra platforms already enabled" echo "Extra platforms already enabled"
else else
echo "Enabling extra platform support" echo "Enabling extra platform support"
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64/' "$BUNDLEDVERSIONS"
fi fi
} }
EnableExtraPlatforms() EnableExtraPlatforms()
{ {
if grep -qv freebsd-x64 src/Directory.Build.props; then if grep -qv freebsd-x64 src/Directory.Build.props; then
sed -i'' -e "s^<RuntimeIdentifiers>\(.*\)</RuntimeIdentifiers>^<RuntimeIdentifiers>\1;freebsd-x64;linux-x86</RuntimeIdentifiers>^g" src/Directory.Build.props sed -i'' -e "s^<RuntimeIdentifiers>\(.*\)</RuntimeIdentifiers>^<RuntimeIdentifiers>\1;freebsd-x64</RuntimeIdentifiers>^g" src/Directory.Build.props
fi fi
} }
@ -79,9 +79,9 @@ Build()
if [[ -z "$RID" || -z "$FRAMEWORK" ]]; if [[ -z "$RID" || -z "$FRAMEWORK" ]];
then then
dotnet msbuild -restore $slnFile -p:Configuration=Release -p:Platform=$platform -t:PublishAllRids dotnet msbuild -restore $slnFile -p:SelfContained=true -p:Configuration=Release -p:Platform=$platform -t:PublishAllRids
else else
dotnet msbuild -restore $slnFile -p:Configuration=Release -p:Platform=$platform -p:RuntimeIdentifiers=$RID -t:PublishAllRids dotnet msbuild -restore $slnFile -p:SelfContained=true -p:Configuration=Release -p:Platform=$platform -p:RuntimeIdentifiers=$RID -t:PublishAllRids
fi fi
ProgressEnd 'Build' ProgressEnd 'Build'
@ -137,7 +137,7 @@ PackageLinux()
echo "Adding Lidarr.Mono to UpdatePackage" echo "Adding Lidarr.Mono to UpdatePackage"
cp $folder/Lidarr.Mono.* $folder/Lidarr.Update cp $folder/Lidarr.Mono.* $folder/Lidarr.Update
if [ "$framework" = "net6.0" ]; then if [ "$framework" = "net8.0" ]; then
cp $folder/Mono.Posix.NETStandard.* $folder/Lidarr.Update cp $folder/Mono.Posix.NETStandard.* $folder/Lidarr.Update
cp $folder/libMonoPosixHelper.* $folder/Lidarr.Update cp $folder/libMonoPosixHelper.* $folder/Lidarr.Update
fi fi
@ -165,7 +165,7 @@ PackageMacOS()
echo "Adding Lidarr.Mono to UpdatePackage" echo "Adding Lidarr.Mono to UpdatePackage"
cp $folder/Lidarr.Mono.* $folder/Lidarr.Update cp $folder/Lidarr.Mono.* $folder/Lidarr.Update
if [ "$framework" = "net6.0" ]; then if [ "$framework" = "net8.0" ]; then
cp $folder/Mono.Posix.NETStandard.* $folder/Lidarr.Update cp $folder/Mono.Posix.NETStandard.* $folder/Lidarr.Update
cp $folder/libMonoPosixHelper.* $folder/Lidarr.Update cp $folder/libMonoPosixHelper.* $folder/Lidarr.Update
fi fi
@ -377,15 +377,14 @@ then
Build Build
if [[ -z "$RID" || -z "$FRAMEWORK" ]]; if [[ -z "$RID" || -z "$FRAMEWORK" ]];
then then
PackageTests "net6.0" "win-x64" PackageTests "net8.0" "win-x64"
PackageTests "net6.0" "win-x86" PackageTests "net8.0" "win-x86"
PackageTests "net6.0" "linux-x64" PackageTests "net8.0" "linux-x64"
PackageTests "net6.0" "linux-musl-x64" PackageTests "net8.0" "linux-musl-x64"
PackageTests "net6.0" "osx-x64" PackageTests "net8.0" "osx-x64"
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ]; if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
then then
PackageTests "net6.0" "freebsd-x64" PackageTests "net8.0" "freebsd-x64"
PackageTests "net6.0" "linux-x86"
fi fi
else else
PackageTests "$FRAMEWORK" "$RID" PackageTests "$FRAMEWORK" "$RID"
@ -413,20 +412,19 @@ then
if [[ -z "$RID" || -z "$FRAMEWORK" ]]; if [[ -z "$RID" || -z "$FRAMEWORK" ]];
then then
Package "net6.0" "win-x64" Package "net8.0" "win-x64"
Package "net6.0" "win-x86" Package "net8.0" "win-x86"
Package "net6.0" "linux-x64" Package "net8.0" "linux-x64"
Package "net6.0" "linux-musl-x64" Package "net8.0" "linux-musl-x64"
Package "net6.0" "linux-arm64" Package "net8.0" "linux-arm64"
Package "net6.0" "linux-musl-arm64" Package "net8.0" "linux-musl-arm64"
Package "net6.0" "linux-arm" Package "net8.0" "linux-arm"
Package "net6.0" "linux-musl-arm" Package "net8.0" "linux-musl-arm"
Package "net6.0" "osx-x64" Package "net8.0" "osx-x64"
Package "net6.0" "osx-arm64" Package "net8.0" "osx-arm64"
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ]; if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
then then
Package "net6.0" "freebsd-x64" Package "net8.0" "freebsd-x64"
Package "net6.0" "linux-x86"
fi fi
else else
Package "$FRAMEWORK" "$RID" Package "$FRAMEWORK" "$RID"
@ -436,7 +434,7 @@ fi
if [ "$INSTALLER" = "YES" ]; if [ "$INSTALLER" = "YES" ];
then then
InstallInno InstallInno
BuildInstaller "net6.0" "win-x64" BuildInstaller "net8.0" "win-x64"
BuildInstaller "net6.0" "win-x86" BuildInstaller "net8.0" "win-x86"
RemoveInno RemoveInno
fi fi

View file

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
set -e set -e
FRAMEWORK="net6.0" FRAMEWORK="net8.0"
PLATFORM=$1 PLATFORM=$1
ARCHITECTURE="${2:-x64}" ARCHITECTURE="${2:-x64}"
@ -38,7 +38,7 @@ dotnet clean $slnFile -c Release
dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p:RuntimeIdentifiers=$RUNTIME -t:PublishAllRids dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p:RuntimeIdentifiers=$RUNTIME -t:PublishAllRids
dotnet new tool-manifest dotnet new tool-manifest
dotnet tool install --version 6.6.2 Swashbuckle.AspNetCore.Cli dotnet tool install --version 9.0.6 Swashbuckle.AspNetCore.Cli
dotnet tool run swagger tofile --output ./src/Lidarr.Api.V1/openapi.json "$outputFolder/$FRAMEWORK/$RUNTIME/$application" v1 & dotnet tool run swagger tofile --output ./src/Lidarr.Api.V1/openapi.json "$outputFolder/$FRAMEWORK/$RUNTIME/$application" v1 &

View file

@ -30,7 +30,9 @@ export const authenticationMethodOptions = [
key: 'basic', key: 'basic',
get value() { get value() {
return translate('AuthBasic'); return translate('AuthBasic');
} },
isDisabled: true,
isHidden: true
}, },
{ {
key: 'forms', key: 'forms',

5
global.json Normal file
View file

@ -0,0 +1,5 @@
{
"sdk": {
"version": "8.0.405"
}
}

View file

@ -31,7 +31,7 @@
"@fortawesome/free-solid-svg-icons": "6.7.1", "@fortawesome/free-solid-svg-icons": "6.7.1",
"@fortawesome/react-fontawesome": "0.2.2", "@fortawesome/react-fontawesome": "0.2.2",
"@juggle/resize-observer": "3.4.0", "@juggle/resize-observer": "3.4.0",
"@microsoft/signalr": "6.0.25", "@microsoft/signalr": "8.0.17",
"@sentry/browser": "7.119.1", "@sentry/browser": "7.119.1",
"@sentry/integrations": "7.119.1", "@sentry/integrations": "7.119.1",
"@types/node": "20.16.11", "@types/node": "20.16.11",

View file

@ -99,13 +99,6 @@
<RootNamespace Condition="'$(LidarrProject)'=='true'">$(MSBuildProjectName.Replace('Lidarr','NzbDrone'))</RootNamespace> <RootNamespace Condition="'$(LidarrProject)'=='true'">$(MSBuildProjectName.Replace('Lidarr','NzbDrone'))</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup Condition="'$(TestProject)'!='true'">
<!-- Annotates .NET assemblies with repository information including SHA -->
<!-- Sentry uses this to link directly to GitHub at the exact version/file/line -->
<!-- This is built-in on .NET 8 and can be removed once the project is updated -->
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>
<!-- Sentry specific configuration: Only in Release mode --> <!-- Sentry specific configuration: Only in Release mode -->
<PropertyGroup Condition="'$(Configuration)' == 'Release'"> <PropertyGroup Condition="'$(Configuration)' == 'Release'">
<!-- https://docs.sentry.io/platforms/dotnet/configuration/msbuild/ --> <!-- https://docs.sentry.io/platforms/dotnet/configuration/msbuild/ -->

View file

@ -142,7 +142,7 @@ public List<AlbumResource> GetAlbums([FromQuery]int? artistId,
} }
[RestPostById] [RestPostById]
public ActionResult<AlbumResource> AddAlbum(AlbumResource albumResource) public ActionResult<AlbumResource> AddAlbum([FromBody] AlbumResource albumResource)
{ {
var album = _addAlbumService.AddAlbum(albumResource.ToModel()); var album = _addAlbumService.AddAlbum(albumResource.ToModel());
@ -150,7 +150,7 @@ public ActionResult<AlbumResource> AddAlbum(AlbumResource albumResource)
} }
[RestPutById] [RestPutById]
public ActionResult<AlbumResource> UpdateAlbum(AlbumResource albumResource) public ActionResult<AlbumResource> UpdateAlbum([FromBody] AlbumResource albumResource)
{ {
var album = _albumService.GetAlbum(albumResource.Id); var album = _albumService.GetAlbum(albumResource.Id);
@ -171,7 +171,7 @@ public void DeleteAlbum(int id, bool deleteFiles = false, bool addImportListExcl
} }
[HttpPut("monitor")] [HttpPut("monitor")]
public IActionResult SetAlbumsMonitored([FromBody]AlbumsMonitoredResource resource) public IActionResult SetAlbumsMonitored([FromBody] AlbumsMonitoredResource resource)
{ {
_albumService.SetMonitored(resource.AlbumIds, resource.Monitored); _albumService.SetMonitored(resource.AlbumIds, resource.Monitored);

View file

@ -157,7 +157,7 @@ public List<ArtistResource> AllArtists(Guid? mbId)
[RestPostById] [RestPostById]
[Consumes("application/json")] [Consumes("application/json")]
[Produces("application/json")] [Produces("application/json")]
public ActionResult<ArtistResource> AddArtist(ArtistResource artistResource) public ActionResult<ArtistResource> AddArtist([FromBody] ArtistResource artistResource)
{ {
var artist = _addArtistService.AddArtist(artistResource.ToModel()); var artist = _addArtistService.AddArtist(artistResource.ToModel());
@ -167,7 +167,7 @@ public ActionResult<ArtistResource> AddArtist(ArtistResource artistResource)
[RestPutById] [RestPutById]
[Consumes("application/json")] [Consumes("application/json")]
[Produces("application/json")] [Produces("application/json")]
public ActionResult<ArtistResource> UpdateArtist(ArtistResource artistResource, bool moveFiles = false) public ActionResult<ArtistResource> UpdateArtist([FromBody] ArtistResource artistResource, bool moveFiles = false)
{ {
var artist = _artistService.GetArtist(artistResource.Id); var artist = _artistService.GetArtist(artistResource.Id);

View file

@ -51,7 +51,7 @@ public override AutoTaggingResource GetResourceById(int id)
[RestPostById] [RestPostById]
[Consumes("application/json")] [Consumes("application/json")]
public ActionResult<AutoTaggingResource> Create(AutoTaggingResource autoTagResource) public ActionResult<AutoTaggingResource> Create([FromBody] AutoTaggingResource autoTagResource)
{ {
var model = autoTagResource.ToModel(_specifications); var model = autoTagResource.ToModel(_specifications);
@ -62,7 +62,7 @@ public ActionResult<AutoTaggingResource> Create(AutoTaggingResource autoTagResou
[RestPutById] [RestPutById]
[Consumes("application/json")] [Consumes("application/json")]
public ActionResult<AutoTaggingResource> Update(AutoTaggingResource resource) public ActionResult<AutoTaggingResource> Update([FromBody] AutoTaggingResource resource)
{ {
var model = resource.ToModel(_specifications); var model = resource.ToModel(_specifications);

View file

@ -51,7 +51,7 @@ public override CommandResource GetResourceById(int id)
[RestPostById] [RestPostById]
[Consumes("application/json")] [Consumes("application/json")]
[Produces("application/json")] [Produces("application/json")]
public ActionResult<CommandResource> StartCommand(CommandResource commandResource) public ActionResult<CommandResource> StartCommand([FromBody] CommandResource commandResource)
{ {
var commandType = var commandType =
_knownTypes.GetImplementations(typeof(Command)) _knownTypes.GetImplementations(typeof(Command))

View file

@ -34,7 +34,7 @@ public TResource GetConfig()
[RestPutById] [RestPutById]
[Consumes("application/json")] [Consumes("application/json")]
public virtual ActionResult<TResource> SaveConfig(TResource resource) public virtual ActionResult<TResource> SaveConfig([FromBody] TResource resource)
{ {
var dictionary = resource.GetType() var dictionary = resource.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public) .GetProperties(BindingFlags.Instance | BindingFlags.Public)

View file

@ -40,10 +40,14 @@ public HostConfigController(IConfigFileProvider configFileProvider,
SharedValidator.RuleFor(c => c.UrlBase).ValidUrlBase(); SharedValidator.RuleFor(c => c.UrlBase).ValidUrlBase();
SharedValidator.RuleFor(c => c.InstanceName).ContainsLidarr().When(c => c.InstanceName.IsNotNullOrWhiteSpace()); SharedValidator.RuleFor(c => c.InstanceName).ContainsLidarr().When(c => c.InstanceName.IsNotNullOrWhiteSpace());
SharedValidator.RuleFor(c => c.Username).NotEmpty().When(c => c.AuthenticationMethod == AuthenticationType.Basic || SharedValidator.RuleFor(c => c.Username).NotEmpty().When(c => c.AuthenticationMethod == AuthenticationType.Forms);
c.AuthenticationMethod == AuthenticationType.Forms); SharedValidator.RuleFor(c => c.Password).NotEmpty().When(c => c.AuthenticationMethod == AuthenticationType.Forms);
SharedValidator.RuleFor(c => c.Password).NotEmpty().When(c => c.AuthenticationMethod == AuthenticationType.Basic ||
c.AuthenticationMethod == AuthenticationType.Forms); SharedValidator.RuleFor(c => c.AuthenticationMethod)
#pragma warning disable CS0618 // Type or member is obsolete
.NotEqual(AuthenticationType.Basic)
#pragma warning restore CS0618 // Type or member is obsolete
.WithMessage("'Basic' is no longer supported, switch to 'Forms' instead.");
SharedValidator.RuleFor(c => c.PasswordConfirmation) SharedValidator.RuleFor(c => c.PasswordConfirmation)
.Must((resource, p) => IsMatchingPassword(resource)).WithMessage("Must match Password"); .Must((resource, p) => IsMatchingPassword(resource)).WithMessage("Must match Password");
@ -107,7 +111,7 @@ public HostConfigResource GetHostConfig()
} }
[RestPutById] [RestPutById]
public ActionResult<HostConfigResource> SaveHostConfig(HostConfigResource resource) public ActionResult<HostConfigResource> SaveHostConfig([FromBody] HostConfigResource resource)
{ {
var dictionary = resource.GetType() var dictionary = resource.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public) .GetProperties(BindingFlags.Instance | BindingFlags.Public)

View file

@ -61,7 +61,7 @@ public NamingConfigResource GetNamingConfig()
} }
[RestPutById] [RestPutById]
public ActionResult<NamingConfigResource> UpdateNamingConfig(NamingConfigResource resource) public ActionResult<NamingConfigResource> UpdateNamingConfig([FromBody] NamingConfigResource resource)
{ {
var nameSpec = resource.ToModel(); var nameSpec = resource.ToModel();
ValidateFormatResult(nameSpec); ValidateFormatResult(nameSpec);

View file

@ -32,7 +32,7 @@ public UiConfigController(IConfigFileProvider configFileProvider, IConfigService
} }
[RestPutById] [RestPutById]
public override ActionResult<UiConfigResource> SaveConfig(UiConfigResource resource) public override ActionResult<UiConfigResource> SaveConfig([FromBody] UiConfigResource resource)
{ {
var dictionary = resource.GetType() var dictionary = resource.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public) .GetProperties(BindingFlags.Instance | BindingFlags.Public)

View file

@ -31,7 +31,7 @@ public List<CustomFilterResource> GetCustomFilters()
[RestPostById] [RestPostById]
[Consumes("application/json")] [Consumes("application/json")]
public ActionResult<CustomFilterResource> AddCustomFilter(CustomFilterResource resource) public ActionResult<CustomFilterResource> AddCustomFilter([FromBody] CustomFilterResource resource)
{ {
var customFilter = _customFilterService.Add(resource.ToModel()); var customFilter = _customFilterService.Add(resource.ToModel());
@ -40,7 +40,7 @@ public ActionResult<CustomFilterResource> AddCustomFilter(CustomFilterResource r
[RestPutById] [RestPutById]
[Consumes("application/json")] [Consumes("application/json")]
public ActionResult<CustomFilterResource> UpdateCustomFilter(CustomFilterResource resource) public ActionResult<CustomFilterResource> UpdateCustomFilter([FromBody] CustomFilterResource resource)
{ {
_customFilterService.Update(resource.ToModel()); _customFilterService.Update(resource.ToModel());
return Accepted(resource.Id); return Accepted(resource.Id);

View file

@ -56,7 +56,7 @@ public List<CustomFormatResource> GetAll()
[RestPostById] [RestPostById]
[Consumes("application/json")] [Consumes("application/json")]
public ActionResult<CustomFormatResource> Create(CustomFormatResource customFormatResource) public ActionResult<CustomFormatResource> Create([FromBody] CustomFormatResource customFormatResource)
{ {
var model = customFormatResource.ToModel(_specifications); var model = customFormatResource.ToModel(_specifications);
@ -67,7 +67,7 @@ public ActionResult<CustomFormatResource> Create(CustomFormatResource customForm
[RestPutById] [RestPutById]
[Consumes("application/json")] [Consumes("application/json")]
public ActionResult<CustomFormatResource> Update(CustomFormatResource resource) public ActionResult<CustomFormatResource> Update([FromBody] CustomFormatResource resource)
{ {
var model = resource.ToModel(_specifications); var model = resource.ToModel(_specifications);

View file

@ -40,7 +40,7 @@ public List<ImportListExclusionResource> GetImportListExclusions()
} }
[RestPostById] [RestPostById]
public ActionResult<ImportListExclusionResource> AddImportListExclusion(ImportListExclusionResource resource) public ActionResult<ImportListExclusionResource> AddImportListExclusion([FromBody] ImportListExclusionResource resource)
{ {
var customFilter = _importListExclusionService.Add(resource.ToModel()); var customFilter = _importListExclusionService.Add(resource.ToModel());
@ -48,7 +48,7 @@ public ActionResult<ImportListExclusionResource> AddImportListExclusion(ImportLi
} }
[RestPutById] [RestPutById]
public ActionResult<ImportListExclusionResource> UpdateImportListExclusion(ImportListExclusionResource resource) public ActionResult<ImportListExclusionResource> UpdateImportListExclusion([FromBody] ImportListExclusionResource resource)
{ {
_importListExclusionService.Update(resource.ToModel()); _importListExclusionService.Update(resource.ToModel());
return Accepted(resource.Id); return Accepted(resource.Id);

View file

@ -66,7 +66,7 @@ public ReleaseController(IAlbumService albumService,
} }
[HttpPost] [HttpPost]
public async Task<ActionResult<ReleaseResource>> DownloadRelease(ReleaseResource release) public async Task<ActionResult<ReleaseResource>> DownloadRelease([FromBody] ReleaseResource release)
{ {
ValidateResource(release); ValidateResource(release);

View file

@ -49,7 +49,7 @@ public ReleasePushController(IMakeDownloadDecision downloadDecisionMaker,
[HttpPost] [HttpPost]
[Consumes("application/json")] [Consumes("application/json")]
public ActionResult<ReleaseResource> Create(ReleaseResource release) public ActionResult<ReleaseResource> Create([FromBody] ReleaseResource release)
{ {
_logger.Info("Release pushed: {0} - {1}", release.Title, release.DownloadUrl ?? release.MagnetUrl); _logger.Info("Release pushed: {0} - {1}", release.Title, release.DownloadUrl ?? release.MagnetUrl);

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Lidarr.Http\Lidarr.Http.csproj" /> <ProjectReference Include="..\Lidarr.Http\Lidarr.Http.csproj" />
@ -9,7 +9,7 @@
<ProjectReference Include="..\NzbDrone.SignalR\Lidarr.SignalR.csproj" /> <ProjectReference Include="..\NzbDrone.SignalR\Lidarr.SignalR.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.6.2" /> <PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="9.0.6" />
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.7.0" /> <PackageReference Include="System.Reflection.TypeExtensions" Version="4.7.0" />
<PackageReference Include="FluentValidation" Version="9.5.4" /> <PackageReference Include="FluentValidation" Version="9.5.4" />
<PackageReference Include="Ical.Net" Version="4.3.1" /> <PackageReference Include="Ical.Net" Version="4.3.1" />

View file

@ -33,7 +33,7 @@ public ManualImportController(IManualImportService manualImportService,
} }
[HttpPost] [HttpPost]
public IActionResult UpdateItems(List<ManualImportUpdateResource> resource) public IActionResult UpdateItems([FromBody] List<ManualImportUpdateResource> resource)
{ {
return Accepted(UpdateImportItems(resource)); return Accepted(UpdateImportItems(resource));
} }

View file

@ -34,7 +34,7 @@ public DelayProfileController(IDelayProfileService delayProfileService, DelayPro
} }
[RestPostById] [RestPostById]
public ActionResult<DelayProfileResource> Create(DelayProfileResource resource) public ActionResult<DelayProfileResource> Create([FromBody] DelayProfileResource resource)
{ {
var model = resource.ToModel(); var model = resource.ToModel();
model = _delayProfileService.Add(model); model = _delayProfileService.Add(model);
@ -54,7 +54,7 @@ public void DeleteProfile(int id)
} }
[RestPutById] [RestPutById]
public ActionResult<DelayProfileResource> Update(DelayProfileResource resource) public ActionResult<DelayProfileResource> Update([FromBody] DelayProfileResource resource)
{ {
var model = resource.ToModel(); var model = resource.ToModel();
_delayProfileService.Update(model); _delayProfileService.Update(model);

View file

@ -24,7 +24,7 @@ public MetadataProfileController(IMetadataProfileService profileService)
} }
[RestPostById] [RestPostById]
public ActionResult<MetadataProfileResource> Create(MetadataProfileResource resource) public ActionResult<MetadataProfileResource> Create([FromBody] MetadataProfileResource resource)
{ {
var model = resource.ToModel(); var model = resource.ToModel();
model = _profileService.Add(model); model = _profileService.Add(model);
@ -38,7 +38,7 @@ public void DeleteProfile(int id)
} }
[RestPutById] [RestPutById]
public ActionResult<MetadataProfileResource> Update(MetadataProfileResource resource) public ActionResult<MetadataProfileResource> Update([FromBody] MetadataProfileResource resource)
{ {
var model = resource.ToModel(); var model = resource.ToModel();

View file

@ -44,7 +44,7 @@ public QualityProfileController(IQualityProfileService qualityProfileService, IC
} }
[RestPostById] [RestPostById]
public ActionResult<QualityProfileResource> Create(QualityProfileResource resource) public ActionResult<QualityProfileResource> Create([FromBody] QualityProfileResource resource)
{ {
var model = resource.ToModel(); var model = resource.ToModel();
model = _qualityProfileService.Add(model); model = _qualityProfileService.Add(model);
@ -58,7 +58,7 @@ public void DeleteProfile(int id)
} }
[RestPutById] [RestPutById]
public ActionResult<QualityProfileResource> Update(QualityProfileResource resource) public ActionResult<QualityProfileResource> Update([FromBody] QualityProfileResource resource)
{ {
var model = resource.ToModel(); var model = resource.ToModel();

View file

@ -47,13 +47,13 @@ public List<ReleaseProfileResource> GetAll()
} }
[RestPostById] [RestPostById]
public ActionResult<ReleaseProfileResource> Create(ReleaseProfileResource resource) public ActionResult<ReleaseProfileResource> Create([FromBody] ReleaseProfileResource resource)
{ {
return Created(_releaseProfileService.Add(resource.ToModel()).Id); return Created(_releaseProfileService.Add(resource.ToModel()).Id);
} }
[RestPutById] [RestPutById]
public ActionResult<ReleaseProfileResource> Update(ReleaseProfileResource resource) public ActionResult<ReleaseProfileResource> Update([FromBody] ReleaseProfileResource resource)
{ {
_releaseProfileService.Update(resource.ToModel()); _releaseProfileService.Update(resource.ToModel());
return Accepted(resource.Id); return Accepted(resource.Id);

View file

@ -23,7 +23,7 @@ public QualityDefinitionController(IQualityDefinitionService qualityDefinitionSe
} }
[RestPutById] [RestPutById]
public ActionResult<QualityDefinitionResource> Update(QualityDefinitionResource resource) public ActionResult<QualityDefinitionResource> Update([FromBody] QualityDefinitionResource resource)
{ {
var model = resource.ToModel(); var model = resource.ToModel();
_qualityDefinitionService.Update(model); _qualityDefinitionService.Update(model);

View file

@ -147,8 +147,8 @@ public PagingResource<QueueResource> GetQueue([FromQuery] PagingRequestResource
var filteredQueue = includeUnknownArtistItems ? queue : queue.Where(q => q.Artist != null); var filteredQueue = includeUnknownArtistItems ? queue : queue.Where(q => q.Artist != null);
var pending = _pendingReleaseService.GetPendingQueue(); var pending = _pendingReleaseService.GetPendingQueue();
var hasArtistIdFilter = artistIds.Any(); var hasArtistIdFilter = artistIds is { Count: > 0 };
var hasQualityFilter = quality.Any(); var hasQualityFilter = quality is { Count: > 0 };
var fullQueue = filteredQueue.Concat(pending).Where(q => var fullQueue = filteredQueue.Concat(pending).Where(q =>
{ {

View file

@ -66,7 +66,7 @@ public override RootFolderResource GetResourceById(int id)
[RestPostById] [RestPostById]
[Consumes("application/json")] [Consumes("application/json")]
public ActionResult<RootFolderResource> CreateRootFolder(RootFolderResource rootFolderResource) public ActionResult<RootFolderResource> CreateRootFolder([FromBody] RootFolderResource rootFolderResource)
{ {
var model = rootFolderResource.ToModel(); var model = rootFolderResource.ToModel();
@ -74,7 +74,7 @@ public ActionResult<RootFolderResource> CreateRootFolder(RootFolderResource root
} }
[RestPutById] [RestPutById]
public ActionResult<RootFolderResource> UpdateRootFolder(RootFolderResource rootFolderResource) public ActionResult<RootFolderResource> UpdateRootFolder([FromBody] RootFolderResource rootFolderResource)
{ {
var model = rootFolderResource.ToModel(); var model = rootFolderResource.ToModel();

View file

@ -42,14 +42,14 @@ public List<TagResource> GetAll()
[RestPostById] [RestPostById]
[Consumes("application/json")] [Consumes("application/json")]
public ActionResult<TagResource> Create(TagResource resource) public ActionResult<TagResource> Create([FromBody] TagResource resource)
{ {
return Created(_tagService.Add(resource.ToModel()).Id); return Created(_tagService.Add(resource.ToModel()).Id);
} }
[RestPutById] [RestPutById]
[Consumes("application/json")] [Consumes("application/json")]
public ActionResult<TagResource> Update(TagResource resource) public ActionResult<TagResource> Update([FromBody] TagResource resource)
{ {
_tagService.Update(resource.ToModel()); _tagService.Update(resource.ToModel());
return Accepted(resource.Id); return Accepted(resource.Id);

View file

@ -1,5 +1,5 @@
{ {
"openapi": "3.0.1", "openapi": "3.0.4",
"info": { "info": {
"title": "Lidarr", "title": "Lidarr",
"description": "Lidarr API docs", "description": "Lidarr API docs",

View file

@ -27,9 +27,8 @@ public class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthentic
public ApiKeyAuthenticationHandler(IOptionsMonitor<ApiKeyAuthenticationOptions> options, public ApiKeyAuthenticationHandler(IOptionsMonitor<ApiKeyAuthenticationOptions> options,
ILoggerFactory logger, ILoggerFactory logger,
UrlEncoder encoder, UrlEncoder encoder,
ISystemClock clock,
IConfigFileProvider config) IConfigFileProvider config)
: base(options, logger, encoder, clock) : base(options, logger, encoder)
{ {
_apiKey = config.ApiKey; _apiKey = config.ApiKey;
} }

View file

@ -18,11 +18,6 @@ public static AuthenticationBuilder AddApiKey(this AuthenticationBuilder authent
return authenticationBuilder.AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>(name, options); return authenticationBuilder.AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>(name, options);
} }
public static AuthenticationBuilder AddBasic(this AuthenticationBuilder authenticationBuilder, string name)
{
return authenticationBuilder.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>(name, options => { });
}
public static AuthenticationBuilder AddNone(this AuthenticationBuilder authenticationBuilder, string name) public static AuthenticationBuilder AddNone(this AuthenticationBuilder authenticationBuilder, string name)
{ {
return authenticationBuilder.AddScheme<AuthenticationSchemeOptions, NoAuthenticationHandler>(name, options => { }); return authenticationBuilder.AddScheme<AuthenticationSchemeOptions, NoAuthenticationHandler>(name, options => { });
@ -35,7 +30,7 @@ public static AuthenticationBuilder AddExternal(this AuthenticationBuilder authe
public static AuthenticationBuilder AddAppAuthentication(this IServiceCollection services) public static AuthenticationBuilder AddAppAuthentication(this IServiceCollection services)
{ {
services.AddOptions<CookieAuthenticationOptions>(AuthenticationType.Forms.ToString()) services.AddOptions<CookieAuthenticationOptions>(nameof(AuthenticationType.Forms))
.Configure<IConfigFileProvider>((options, configFileProvider) => .Configure<IConfigFileProvider>((options, configFileProvider) =>
{ {
// Replace diacritics and replace non-word characters to ensure cookie name doesn't contain any valid URL characters not allowed in cookie names // Replace diacritics and replace non-word characters to ensure cookie name doesn't contain any valid URL characters not allowed in cookie names
@ -52,10 +47,9 @@ public static AuthenticationBuilder AddAppAuthentication(this IServiceCollection
}); });
return services.AddAuthentication() return services.AddAuthentication()
.AddNone(AuthenticationType.None.ToString()) .AddNone(nameof(AuthenticationType.None))
.AddExternal(AuthenticationType.External.ToString()) .AddExternal(nameof(AuthenticationType.External))
.AddBasic(AuthenticationType.Basic.ToString()) .AddCookie(nameof(AuthenticationType.Forms))
.AddCookie(AuthenticationType.Forms.ToString())
.AddApiKey("API", options => .AddApiKey("API", options =>
{ {
options.HeaderName = "X-Api-Key"; options.HeaderName = "X-Api-Key";

View file

@ -1,85 +0,0 @@
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Authentication;
namespace Lidarr.Http.Authentication
{
public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private readonly IAuthenticationService _authService;
public BasicAuthenticationHandler(IAuthenticationService authService,
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
: base(options, logger, encoder, clock)
{
_authService = authService;
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.ContainsKey("Authorization"))
{
return Task.FromResult(AuthenticateResult.Fail("Authorization header missing."));
}
// Get authorization key
var authorizationHeader = Request.Headers["Authorization"].ToString();
var authHeaderRegex = new Regex(@"Basic (.*)");
if (!authHeaderRegex.IsMatch(authorizationHeader))
{
return Task.FromResult(AuthenticateResult.Fail("Authorization code not formatted properly."));
}
var authBase64 = Encoding.UTF8.GetString(Convert.FromBase64String(authHeaderRegex.Replace(authorizationHeader, "$1")));
var authSplit = authBase64.Split(':', 2);
var authUsername = authSplit[0];
var authPassword = authSplit.Length > 1 ? authSplit[1] : throw new Exception("Unable to get password");
var user = _authService.Login(Request, authUsername, authPassword);
if (user == null)
{
return Task.FromResult(AuthenticateResult.Fail("The username or password is not correct."));
}
var claims = new List<Claim>
{
new Claim("user", user.Username),
new Claim("identifier", user.Identifier.ToString()),
new Claim("AuthType", AuthenticationType.Basic.ToString())
};
var identity = new ClaimsIdentity(claims, "Basic", "user", "identifier");
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, "Basic");
return Task.FromResult(AuthenticateResult.Success(ticket));
}
protected override Task HandleChallengeAsync(AuthenticationProperties properties)
{
Response.Headers.Add("WWW-Authenticate", $"Basic realm=\"{BuildInfo.AppName}\"");
Response.StatusCode = 401;
return Task.CompletedTask;
}
protected override Task HandleForbiddenAsync(AuthenticationProperties properties)
{
Response.StatusCode = 403;
return Task.CompletedTask;
}
}
}

View file

@ -13,9 +13,8 @@ public class NoAuthenticationHandler : AuthenticationHandler<AuthenticationSchem
{ {
public NoAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, public NoAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger, ILoggerFactory logger,
UrlEncoder encoder, UrlEncoder encoder)
ISystemClock clock) : base(options, logger, encoder)
: base(options, logger, encoder, clock)
{ {
} }

View file

@ -8,7 +8,7 @@ namespace NzbDrone.Http.Authentication
{ {
public class UiAuthorizationPolicyProvider : IAuthorizationPolicyProvider public class UiAuthorizationPolicyProvider : IAuthorizationPolicyProvider
{ {
private const string POLICY_NAME = "UI"; private const string PolicyName = "UI";
private readonly IConfigFileProvider _config; private readonly IConfigFileProvider _config;
public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; } public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; }
@ -26,7 +26,7 @@ public UiAuthorizationPolicyProvider(IOptions<AuthorizationOptions> options,
public Task<AuthorizationPolicy> GetPolicyAsync(string policyName) public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{ {
if (policyName.Equals(POLICY_NAME, StringComparison.OrdinalIgnoreCase)) if (policyName.Equals(PolicyName, StringComparison.OrdinalIgnoreCase))
{ {
var policy = new AuthorizationPolicyBuilder(_config.AuthenticationMethod.ToString()) var policy = new AuthorizationPolicyBuilder(_config.AuthenticationMethod.ToString())
.AddRequirements(new BypassableDenyAnonymousAuthorizationRequirement()); .AddRequirements(new BypassableDenyAnonymousAuthorizationRequirement());

View file

@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentValidation" Version="9.5.4" /> <PackageReference Include="FluentValidation" Version="9.5.4" />
<PackageReference Include="ImpromptuInterface" Version="7.0.1" /> <PackageReference Include="ImpromptuInterface" Version="8.0.6" />
<PackageReference Include="NLog" Version="5.4.0" /> <PackageReference Include="NLog" Version="5.4.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -22,7 +22,7 @@ public async Task InvokeAsync(HttpContext context)
{ {
if (context.Request.IsApiRequest() && !context.Response.Headers.ContainsKey(VERSIONHEADER)) if (context.Request.IsApiRequest() && !context.Response.Headers.ContainsKey(VERSIONHEADER))
{ {
context.Response.Headers.Add(VERSIONHEADER, _version); context.Response.Headers[VERSIONHEADER] = _version;
} }
await _next(context); await _next(context);

View file

@ -6,7 +6,5 @@
<add key="Taglib" value="https://pkgs.dev.azure.com/Lidarr/Lidarr/_packaging/Taglib/nuget/v3/index.json" /> <add key="Taglib" value="https://pkgs.dev.azure.com/Lidarr/Lidarr/_packaging/Taglib/nuget/v3/index.json" />
<add key="dotnet-bsd-crossbuild" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/dotnet-bsd-crossbuild/nuget/v3/index.json" /> <add key="dotnet-bsd-crossbuild" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/dotnet-bsd-crossbuild/nuget/v3/index.json" />
<add key="Mono.Posix.NETStandard" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/Mono.Posix.NETStandard/nuget/v3/index.json" /> <add key="Mono.Posix.NETStandard" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/Mono.Posix.NETStandard/nuget/v3/index.json" />
<add key="SQLite" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/SQLite/nuget/v3/index.json" />
<add key="FluentMigrator" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/FluentMigrator/nuget/v3/index.json" />
</packageSources> </packageSources>
</configuration> </configuration>

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="NBuilder" Version="6.1.0" /> <PackageReference Include="NBuilder" Version="6.1.0" />

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Selenium.Support" Version="3.141.0" /> <PackageReference Include="Selenium.Support" Version="3.141.0" />

View file

@ -10,7 +10,7 @@ public class BuildInfoFixture
[Test] [Test]
public void should_return_version() public void should_return_version()
{ {
BuildInfo.Version.Major.Should().BeOneOf(2, 10); BuildInfo.Version.Major.Should().BeOneOf(3, 10);
} }
[Test] [Test]

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\NzbDrone.Host\Lidarr.Host.csproj" /> <ProjectReference Include="..\NzbDrone.Host\Lidarr.Host.csproj" />

View file

@ -1,6 +1,5 @@
using System; using System;
using System.IO; using System.IO;
using System.Runtime.Serialization;
namespace NzbDrone.Common.Disk namespace NzbDrone.Common.Disk
{ {
@ -24,10 +23,5 @@ public DestinationAlreadyExistsException(string message, Exception innerExceptio
: base(message, innerException) : base(message, innerException)
{ {
} }
protected DestinationAlreadyExistsException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
} }
} }

View file

@ -18,6 +18,7 @@ public class AppFolderFactory : IAppFolderFactory
{ {
private readonly IAppFolderInfo _appFolderInfo; private readonly IAppFolderInfo _appFolderInfo;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IDiskTransferService _diskTransferService;
private readonly Logger _logger; private readonly Logger _logger;
public AppFolderFactory(IAppFolderInfo appFolderInfo, public AppFolderFactory(IAppFolderInfo appFolderInfo,
@ -27,6 +28,7 @@ public AppFolderFactory(IAppFolderInfo appFolderInfo,
{ {
_appFolderInfo = appFolderInfo; _appFolderInfo = appFolderInfo;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_diskTransferService = diskTransferService;
_logger = NzbDroneLogger.GetLogger(this); _logger = NzbDroneLogger.GetLogger(this);
} }
@ -34,6 +36,7 @@ public void Register()
{ {
try try
{ {
MigrateAppDataFolder();
_diskProvider.EnsureFolder(_appFolderInfo.AppDataFolder); _diskProvider.EnsureFolder(_appFolderInfo.AppDataFolder);
} }
catch (UnauthorizedAccessException) catch (UnauthorizedAccessException)
@ -66,6 +69,28 @@ public void SetPermissions()
} }
} }
private void MigrateAppDataFolder()
{
try
{
if (OsInfo.IsOsx)
{
var userAppDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile, Environment.SpecialFolderOption.DoNotVerify), ".config", "Lidarr");
if (_diskProvider.FolderExists(userAppDataFolder) && !_diskProvider.FileExists(_appFolderInfo.GetConfigPath()))
{
_diskTransferService.MirrorFolder(userAppDataFolder, _appFolderInfo.AppDataFolder);
_diskProvider.DeleteFolder(userAppDataFolder, true);
}
}
}
catch (Exception ex)
{
_logger.Debug(ex, ex.Message);
throw new LidarrStartupException(ex, "Unable to migrate configuration folder to {0}. Migrate manually", _appFolderInfo.AppDataFolder);
}
}
private void InitializeMonoApplicationData() private void InitializeMonoApplicationData()
{ {
if (OsInfo.IsWindows) if (OsInfo.IsWindows)

View file

@ -26,9 +26,9 @@ public AppFolderInfo(IStartupContext startupContext)
_dataSpecialFolder = Environment.SpecialFolder.ApplicationData; _dataSpecialFolder = Environment.SpecialFolder.ApplicationData;
} }
if (startupContext.Args.ContainsKey(StartupContext.APPDATA)) if (startupContext.Args.TryGetValue(StartupContext.APPDATA, out var argsAppDataFolder))
{ {
AppDataFolder = startupContext.Args[StartupContext.APPDATA]; AppDataFolder = argsAppDataFolder;
Logger.Info("Data directory is being overridden to [{0}]", AppDataFolder); Logger.Info("Data directory is being overridden to [{0}]", AppDataFolder);
} }
else else

View file

@ -21,7 +21,7 @@ public RuntimeInfo(Logger logger, IHostLifetime hostLifetime = null)
IsWindowsService = hostLifetime is WindowsServiceLifetime; IsWindowsService = hostLifetime is WindowsServiceLifetime;
IsStarting = true; IsStarting = true;
// net6.0 will return Lidarr.dll for entry assembly, we need the actual // net8.0 will return Lidarr.dll for entry assembly, we need the actual
// executable name (Lidarr on linux). On mono this will return the location of // executable name (Lidarr on linux). On mono this will return the location of
// the mono executable itself, which is not what we want. // the mono executable itself, which is not what we want.
var entry = Process.GetCurrentProcess().MainModule; var entry = Process.GetCurrentProcess().MainModule;

View file

@ -1,27 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net8.0</TargetFrameworks>
<NeutralLanguage>en</NeutralLanguage> <NeutralLanguage>en</NeutralLanguage>
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants> <DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DryIoc.dll" Version="5.4.3" /> <PackageReference Include="DryIoc.dll" Version="5.4.3" />
<PackageReference Include="IPAddressRange" Version="6.2.0" /> <PackageReference Include="IPAddressRange" Version="6.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.1" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="NLog" Version="5.4.0" /> <PackageReference Include="NLog" Version="5.4.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.2" /> <PackageReference Include="NLog.Layouts.ClefJsonLayout" Version="1.0.4" />
<PackageReference Include="NLog.Layouts.ClefJsonLayout" Version="1.0.3" />
<PackageReference Include="Sentry" Version="4.0.2" /> <PackageReference Include="Sentry" Version="4.0.2" />
<PackageReference Include="SharpZipLib" Version="1.4.2" /> <PackageReference Include="SharpZipLib" Version="1.4.2" />
<PackageReference Include="SourceGear.sqlite3" Version="3.50.4.2" />
<PackageReference Include="System.Data.SQLite" Version="2.0.2" />
<PackageReference Include="System.IO.Abstractions" Version="17.0.24" /> <PackageReference Include="System.IO.Abstractions" Version="17.0.24" />
<PackageReference Include="System.Text.Json" Version="6.0.10" /> <PackageReference Include="System.Text.Json" Version="8.0.6" />
<PackageReference Include="System.ValueTuple" Version="4.6.1" /> <PackageReference Include="System.ValueTuple" Version="4.6.1" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" /> <PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.1" /> <PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.1" />
<PackageReference Include="System.ServiceProcess.ServiceController" Version="6.0.1" /> <PackageReference Include="System.ServiceProcess.ServiceController" Version="8.0.1" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="EnsureThat\Resources\ExceptionMessages.Designer.cs"> <Compile Update="EnsureThat\Resources\ExceptionMessages.Designer.cs">

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net8.0</TargetFrameworks>
<ApplicationIcon>..\NzbDrone.Host\NzbDrone.ico</ApplicationIcon> <ApplicationIcon>..\NzbDrone.Host\NzbDrone.ico</ApplicationIcon>
</PropertyGroup> </PropertyGroup>

View file

@ -8,6 +8,7 @@
namespace NzbDrone.Core.Test.Http namespace NzbDrone.Core.Test.Http
{ {
[TestFixture] [TestFixture]
[Platform(Exclude = "MacOsX")]
public class HttpProxySettingsProviderFixture : TestBase<HttpProxySettingsProvider> public class HttpProxySettingsProviderFixture : TestBase<HttpProxySettingsProvider>
{ {
private HttpProxySettings GetProxySettings() private HttpProxySettings GetProxySettings()

View file

@ -80,7 +80,7 @@ public void map_artist_should_not_update_id_if_http_throws()
} }
[Test] [Test]
[Ignore("Pending mapping fixes", Until = "2025-10-20 00:00:00Z")] [Ignore("Pending mapping fixes", Until = "2025-12-31 00:00:00Z")]
public void map_artist_should_work() public void map_artist_should_work()
{ {
UseRealHttp(); UseRealHttp();
@ -159,7 +159,7 @@ public void map_album_should_not_update_id_if_http_throws()
} }
[Test] [Test]
[Ignore("Pending mapping fixes", Until = "2025-10-20 00:00:00Z")] [Ignore("Pending mapping fixes", Until = "2025-12-31 00:00:00Z")]
public void map_album_should_work() public void map_album_should_work()
{ {
UseRealHttp(); UseRealHttp();

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="NBuilder" Version="6.1.0" /> <PackageReference Include="NBuilder" Version="6.1.0" />

View file

@ -14,7 +14,7 @@
namespace NzbDrone.Core.Test.MetadataSource.SkyHook namespace NzbDrone.Core.Test.MetadataSource.SkyHook
{ {
[TestFixture] [TestFixture]
[Ignore("Waiting for metadata to be back again", Until = "2025-11-15 00:00:00Z")] [Ignore("Waiting for metadata to be back again", Until = "2025-12-31 00:00:00Z")]
public class SkyHookProxyFixture : CoreTest<SkyHookProxy> public class SkyHookProxyFixture : CoreTest<SkyHookProxy>
{ {
private MetadataProfile _metadataProfile; private MetadataProfile _metadataProfile;

View file

@ -12,7 +12,7 @@
namespace NzbDrone.Core.Test.MetadataSource.SkyHook namespace NzbDrone.Core.Test.MetadataSource.SkyHook
{ {
[TestFixture] [TestFixture]
[Ignore("Waiting for metadata to be back again", Until = "2025-11-15 00:00:00Z")] [Ignore("Waiting for metadata to be back again", Until = "2025-12-31 00:00:00Z")]
public class SkyHookProxySearchFixture : CoreTest<SkyHookProxy> public class SkyHookProxySearchFixture : CoreTest<SkyHookProxy>
{ {
[SetUp] [SetUp]

View file

@ -1,8 +1,11 @@
using System;
namespace NzbDrone.Core.Authentication namespace NzbDrone.Core.Authentication
{ {
public enum AuthenticationType public enum AuthenticationType
{ {
None = 0, None = 0,
[Obsolete("Use Forms authentication instead")]
Basic = 1, Basic = 1,
Forms = 2, Forms = 2,
External = 3 External = 3

View file

@ -204,13 +204,24 @@ public AuthenticationType AuthenticationMethod
if (enabled) if (enabled)
{ {
SetValue("AuthenticationMethod", AuthenticationType.Basic); SetValue("AuthenticationMethod", AuthenticationType.Forms);
return AuthenticationType.Basic; return AuthenticationType.Forms;
} }
return Enum.TryParse<AuthenticationType>(_authOptions.Method, out var enumValue) var value = Enum.TryParse<AuthenticationType>(_authOptions.Method, out var enumValue)
? enumValue ? enumValue
: GetValueEnum("AuthenticationMethod", AuthenticationType.None); : GetValueEnum("AuthenticationMethod", AuthenticationType.None);
#pragma warning disable CS0618 // Type or member is obsolete
if (value == AuthenticationType.Basic)
#pragma warning restore CS0618 // Type or member is obsolete
{
SetValue("AuthenticationMethod", AuthenticationType.Forms);
return AuthenticationType.Forms;
}
return value;
} }
} }
@ -385,6 +396,12 @@ public void MigrateConfigFile()
{ {
SetValue("EnableSsl", false); SetValue("EnableSsl", false);
} }
#pragma warning disable CS0618 // Type or member is obsolete
if (AuthenticationMethod == AuthenticationType.Basic)
#pragma warning restore CS0618 // Type or member is obsolete
{
SetValue("AuthenticationMethod", AuthenticationType.Forms);
}
} }
private void DeleteOldValues() private void DeleteOldValues()

View file

@ -7,7 +7,7 @@
namespace NzbDrone.Core.Datastore.Migration namespace NzbDrone.Core.Datastore.Migration
{ {
[Maintenance(MigrationStage.BeforeAll, TransactionBehavior.None)] [Maintenance(MigrationStage.BeforeAll, TransactionBehavior.None)]
public class DatabaseEngineVersionCheck : FluentMigrator.Migration public class DatabaseEngineVersionCheck : ForwardOnlyMigration
{ {
protected readonly Logger _logger; protected readonly Logger _logger;
@ -22,11 +22,6 @@ public override void Up()
IfDatabase("postgres").Execute.WithConnection(LogPostgresVersion); IfDatabase("postgres").Execute.WithConnection(LogPostgresVersion);
} }
public override void Down()
{
// No-op
}
private void LogSqliteVersion(IDbConnection conn, IDbTransaction tran) private void LogSqliteVersion(IDbConnection conn, IDbTransaction tran)
{ {
using (var versionCmd = conn.CreateCommand()) using (var versionCmd = conn.CreateCommand())

View file

@ -6,7 +6,6 @@
using FluentMigrator.Runner.Initialization; using FluentMigrator.Runner.Initialization;
using FluentMigrator.Runner.Processors; using FluentMigrator.Runner.Processors;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NLog; using NLog;
using NLog.Extensions.Logging; using NLog.Extensions.Logging;
@ -20,13 +19,10 @@ public interface IMigrationController
public class MigrationController : IMigrationController public class MigrationController : IMigrationController
{ {
private readonly Logger _logger; private readonly Logger _logger;
private readonly ILoggerProvider _migrationLoggerProvider;
public MigrationController(Logger logger, public MigrationController(Logger logger)
ILoggerProvider migrationLoggerProvider)
{ {
_logger = logger; _logger = logger;
_migrationLoggerProvider = migrationLoggerProvider;
} }
public void Migrate(string connectionString, MigrationContext migrationContext, DatabaseType databaseType) public void Migrate(string connectionString, MigrationContext migrationContext, DatabaseType databaseType)
@ -35,16 +31,13 @@ public void Migrate(string connectionString, MigrationContext migrationContext,
_logger.Info("*** Migrating {0} ***", connectionString); _logger.Info("*** Migrating {0} ***", connectionString);
ServiceProvider serviceProvider;
var db = databaseType == DatabaseType.SQLite ? "sqlite" : "postgres"; var db = databaseType == DatabaseType.SQLite ? "sqlite" : "postgres";
serviceProvider = new ServiceCollection() var serviceProvider = new ServiceCollection()
.AddLogging(b => b.AddNLog()) .AddLogging(b => b.AddNLog())
.AddFluentMigratorCore() .AddFluentMigratorCore()
.Configure<RunnerOptions>(cfg => cfg.IncludeUntaggedMaintenances = true) .Configure<RunnerOptions>(cfg => cfg.IncludeUntaggedMaintenances = true)
.ConfigureRunner( .ConfigureRunner(builder => builder
builder => builder
.AddPostgres() .AddPostgres()
.AddNzbDroneSQLite() .AddNzbDroneSQLite()
.WithGlobalConnectionString(connectionString) .WithGlobalConnectionString(connectionString)

View file

@ -4,9 +4,14 @@
using FluentMigrator.Builders.Create.Table; using FluentMigrator.Builders.Create.Table;
using FluentMigrator.Runner; using FluentMigrator.Runner;
using FluentMigrator.Runner.BatchParser; using FluentMigrator.Runner.BatchParser;
using FluentMigrator.Runner.Generators;
using FluentMigrator.Runner.Generators.SQLite; using FluentMigrator.Runner.Generators.SQLite;
using FluentMigrator.Runner.Initialization;
using FluentMigrator.Runner.Processors;
using FluentMigrator.Runner.Processors.SQLite; using FluentMigrator.Runner.Processors.SQLite;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace NzbDrone.Core.Datastore.Migration.Framework namespace NzbDrone.Core.Datastore.Migration.Framework
{ {
@ -26,23 +31,40 @@ public static IDbCommand CreateCommand(this IDbConnection conn, IDbTransaction t
return command; return command;
} }
public static void AddParameter(this System.Data.IDbCommand command, object value) public static void AddParameter(this IDbCommand command, object value)
{ {
var parameter = command.CreateParameter(); var parameter = command.CreateParameter();
parameter.Value = value; parameter.Value = value;
command.Parameters.Add(parameter); command.Parameters.Add(parameter);
} }
public static IMigrationRunnerBuilder AddNzbDroneSQLite(this IMigrationRunnerBuilder builder) public static IMigrationRunnerBuilder AddNzbDroneSQLite(this IMigrationRunnerBuilder builder, bool binaryGuid = false, bool useStrictTables = false)
{ {
builder.Services builder.Services
.AddTransient<SQLiteBatchParser>() .AddTransient<SQLiteBatchParser>()
.AddScoped<SQLiteDbFactory>() .AddScoped<SQLiteDbFactory>()
.AddScoped<NzbDroneSQLiteProcessor>() .AddScoped<NzbDroneSQLiteProcessor>(sp =>
{
var factory = sp.GetService<SQLiteDbFactory>();
var logger = sp.GetService<ILogger<NzbDroneSQLiteProcessor>>();
var options = sp.GetService<IOptionsSnapshot<ProcessorOptions>>();
var connectionStringAccessor = sp.GetService<IConnectionStringAccessor>();
var sqliteQuoter = new SQLiteQuoter(false);
return new NzbDroneSQLiteProcessor(factory, sp.GetService<SQLiteGenerator>(), logger, options, connectionStringAccessor, sp, sqliteQuoter);
})
.AddScoped<ISQLiteTypeMap>(_ => new NzbDroneSQLiteTypeMap(useStrictTables))
.AddScoped<IMigrationProcessor>(sp => sp.GetRequiredService<NzbDroneSQLiteProcessor>()) .AddScoped<IMigrationProcessor>(sp => sp.GetRequiredService<NzbDroneSQLiteProcessor>())
.AddScoped<SQLiteQuoter>() .AddScoped(
.AddScoped<SQLiteGenerator>() sp =>
{
var typeMap = sp.GetRequiredService<ISQLiteTypeMap>();
return new SQLiteGenerator(
new SQLiteQuoter(binaryGuid),
typeMap,
new OptionsWrapper<GeneratorOptions>(new GeneratorOptions()));
})
.AddScoped<IMigrationGenerator>(sp => sp.GetRequiredService<SQLiteGenerator>()); .AddScoped<IMigrationGenerator>(sp => sp.GetRequiredService<SQLiteGenerator>());
return builder; return builder;
} }
} }

View file

@ -15,15 +15,18 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
{ {
public class NzbDroneSQLiteProcessor : SQLiteProcessor public class NzbDroneSQLiteProcessor : SQLiteProcessor
{ {
private readonly SQLiteQuoter _quoter;
public NzbDroneSQLiteProcessor(SQLiteDbFactory factory, public NzbDroneSQLiteProcessor(SQLiteDbFactory factory,
SQLiteGenerator generator, SQLiteGenerator generator,
ILogger<NzbDroneSQLiteProcessor> logger, ILogger<NzbDroneSQLiteProcessor> logger,
IOptionsSnapshot<ProcessorOptions> options, IOptionsSnapshot<ProcessorOptions> options,
IConnectionStringAccessor connectionStringAccessor, IConnectionStringAccessor connectionStringAccessor,
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
SQLiteQuoter sqliteQuoter) SQLiteQuoter quoter)
: base(factory, generator, logger, options, connectionStringAccessor, serviceProvider, sqliteQuoter) : base(factory, generator, logger, options, connectionStringAccessor, serviceProvider, quoter)
{ {
_quoter = quoter;
} }
public override void Process(AlterColumnExpression expression) public override void Process(AlterColumnExpression expression)
@ -35,7 +38,7 @@ public override void Process(AlterColumnExpression expression)
if (columnIndex == -1) if (columnIndex == -1)
{ {
throw new ApplicationException(string.Format("Column {0} does not exist on table {1}.", expression.Column.Name, expression.TableName)); throw new ApplicationException($"Column {expression.Column.Name} does not exist on table {expression.TableName}.");
} }
columnDefinitions[columnIndex] = expression.Column; columnDefinitions[columnIndex] = expression.Column;
@ -45,6 +48,28 @@ public override void Process(AlterColumnExpression expression)
ProcessAlterTable(tableDefinition); ProcessAlterTable(tableDefinition);
} }
public override void Process(AlterDefaultConstraintExpression expression)
{
var tableDefinition = GetTableSchema(expression.TableName);
var columnDefinitions = tableDefinition.Columns.ToList();
var columnIndex = columnDefinitions.FindIndex(c => c.Name == expression.ColumnName);
if (columnIndex == -1)
{
throw new ApplicationException($"Column {expression.ColumnName} does not exist on table {expression.TableName}.");
}
var changedColumn = columnDefinitions[columnIndex];
changedColumn.DefaultValue = expression.DefaultValue;
columnDefinitions[columnIndex] = changedColumn;
tableDefinition.Columns = columnDefinitions;
ProcessAlterTable(tableDefinition);
}
public override void Process(DeleteColumnExpression expression) public override void Process(DeleteColumnExpression expression)
{ {
var tableDefinition = GetTableSchema(expression.TableName); var tableDefinition = GetTableSchema(expression.TableName);
@ -62,7 +87,7 @@ public override void Process(DeleteColumnExpression expression)
if (columnsToRemove.Any()) if (columnsToRemove.Any())
{ {
throw new ApplicationException(string.Format("Column {0} does not exist on table {1}.", columnsToRemove.First(), expression.TableName)); throw new ApplicationException($"Column {columnsToRemove.First()} does not exist on table {expression.TableName}.");
} }
ProcessAlterTable(tableDefinition); ProcessAlterTable(tableDefinition);
@ -78,12 +103,12 @@ public override void Process(RenameColumnExpression expression)
if (columnIndex == -1) if (columnIndex == -1)
{ {
throw new ApplicationException(string.Format("Column {0} does not exist on table {1}.", expression.OldName, expression.TableName)); throw new ApplicationException($"Column {expression.OldName} does not exist on table {expression.TableName}.");
} }
if (columnDefinitions.Any(c => c.Name == expression.NewName)) if (columnDefinitions.Any(c => c.Name == expression.NewName))
{ {
throw new ApplicationException(string.Format("Column {0} already exists on table {1}.", expression.NewName, expression.TableName)); throw new ApplicationException($"Column {expression.NewName} already exists on table {expression.TableName}.");
} }
oldColumnDefinitions[columnIndex] = (ColumnDefinition)columnDefinitions[columnIndex].Clone(); oldColumnDefinitions[columnIndex] = (ColumnDefinition)columnDefinitions[columnIndex].Clone();
@ -128,21 +153,20 @@ protected virtual void ProcessAlterTable(TableDefinition tableDefinition, List<C
} }
// What is the cleanest way to do this? Add function to Generator? // What is the cleanest way to do this? Add function to Generator?
var quoter = new SQLiteQuoter(); var columnsToInsert = string.Join(", ", tableDefinition.Columns.Select(c => _quoter.QuoteColumnName(c.Name)));
var columnsToInsert = string.Join(", ", tableDefinition.Columns.Select(c => quoter.QuoteColumnName(c.Name))); var columnsToFetch = string.Join(", ", (oldColumnDefinitions ?? tableDefinition.Columns).Select(c => _quoter.QuoteColumnName(c.Name)));
var columnsToFetch = string.Join(", ", (oldColumnDefinitions ?? tableDefinition.Columns).Select(c => quoter.QuoteColumnName(c.Name)));
Process(new CreateTableExpression() { TableName = tempTableName, Columns = tableDefinition.Columns.ToList() }); Process(new CreateTableExpression { TableName = tempTableName, Columns = tableDefinition.Columns.ToList() });
Process(string.Format("INSERT INTO {0} ({1}) SELECT {2} FROM {3}", quoter.QuoteTableName(tempTableName), columnsToInsert, columnsToFetch, quoter.QuoteTableName(tableName))); Process($"INSERT INTO {_quoter.QuoteTableName(tempTableName)} ({columnsToInsert}) SELECT {columnsToFetch} FROM {_quoter.QuoteTableName(tableName)}");
Process(new DeleteTableExpression() { TableName = tableName }); Process(new DeleteTableExpression { TableName = tableName });
Process(new RenameTableExpression() { OldName = tempTableName, NewName = tableName }); Process(new RenameTableExpression { OldName = tempTableName, NewName = tableName });
foreach (var index in tableDefinition.Indexes) foreach (var index in tableDefinition.Indexes)
{ {
Process(new CreateIndexExpression() { Index = index }); Process(new CreateIndexExpression { Index = index });
} }
} }
} }

View file

@ -0,0 +1,76 @@
using System.Data;
using FluentMigrator.Runner.Generators.Base;
using FluentMigrator.Runner.Generators.SQLite;
namespace NzbDrone.Core.Datastore.Migration.Framework;
// Based on https://github.com/fluentmigrator/fluentmigrator/blob/v6.2.0/src/FluentMigrator.Runner.SQLite/Generators/SQLite/SQLiteTypeMap.cs
public sealed class NzbDroneSQLiteTypeMap : TypeMapBase, ISQLiteTypeMap
{
public bool UseStrictTables { get; }
public NzbDroneSQLiteTypeMap(bool useStrictTables = false)
{
UseStrictTables = useStrictTables;
SetupTypeMaps();
}
// Must be kept in sync with upstream
protected override void SetupTypeMaps()
{
SetTypeMap(DbType.Binary, "BLOB");
SetTypeMap(DbType.Byte, "INTEGER");
SetTypeMap(DbType.Int16, "INTEGER");
SetTypeMap(DbType.Int32, "INTEGER");
SetTypeMap(DbType.Int64, "INTEGER");
SetTypeMap(DbType.SByte, "INTEGER");
SetTypeMap(DbType.UInt16, "INTEGER");
SetTypeMap(DbType.UInt32, "INTEGER");
SetTypeMap(DbType.UInt64, "INTEGER");
if (!UseStrictTables)
{
SetTypeMap(DbType.Currency, "NUMERIC");
SetTypeMap(DbType.Decimal, "NUMERIC");
SetTypeMap(DbType.Double, "NUMERIC");
SetTypeMap(DbType.Single, "NUMERIC");
SetTypeMap(DbType.VarNumeric, "NUMERIC");
SetTypeMap(DbType.Date, "DATETIME");
SetTypeMap(DbType.DateTime, "DATETIME");
SetTypeMap(DbType.DateTime2, "DATETIME");
SetTypeMap(DbType.Time, "DATETIME");
SetTypeMap(DbType.Guid, "UNIQUEIDENTIFIER");
// Custom so that we can use DateTimeOffset in Postgres for appropriate DB typing
SetTypeMap(DbType.DateTimeOffset, "DATETIME");
}
else
{
SetTypeMap(DbType.Currency, "TEXT");
SetTypeMap(DbType.Decimal, "TEXT");
SetTypeMap(DbType.Double, "REAL");
SetTypeMap(DbType.Single, "REAL");
SetTypeMap(DbType.VarNumeric, "TEXT");
SetTypeMap(DbType.Date, "TEXT");
SetTypeMap(DbType.DateTime, "TEXT");
SetTypeMap(DbType.DateTime2, "TEXT");
SetTypeMap(DbType.Time, "TEXT");
SetTypeMap(DbType.Guid, "TEXT");
// Custom so that we can use DateTimeOffset in Postgres for appropriate DB typing
SetTypeMap(DbType.DateTimeOffset, "TEXT");
}
SetTypeMap(DbType.AnsiString, "TEXT");
SetTypeMap(DbType.String, "TEXT");
SetTypeMap(DbType.AnsiStringFixedLength, "TEXT");
SetTypeMap(DbType.StringFixedLength, "TEXT");
SetTypeMap(DbType.Boolean, "INTEGER");
}
public override string GetTypeMap(DbType type, int? size, int? precision)
{
return base.GetTypeMap(type, size: null, precision: null);
}
}

View file

@ -424,8 +424,8 @@ private void AuthenticateClient(HttpRequestBuilder requestBuilder, QBittorrentSe
} }
catch (HttpException ex) catch (HttpException ex)
{ {
_logger.Debug("qbitTorrent authentication failed."); _logger.Debug(ex, "qbitTorrent authentication failed.");
if (ex.Response.StatusCode == HttpStatusCode.Forbidden) if (ex.Response.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden)
{ {
throw new DownloadClientAuthenticationException("Failed to authenticate with qBittorrent.", ex); throw new DownloadClientAuthenticationException("Failed to authenticate with qBittorrent.", ex);
} }
@ -438,7 +438,7 @@ private void AuthenticateClient(HttpRequestBuilder requestBuilder, QBittorrentSe
} }
// returns "Fails." on bad login // returns "Fails." on bad login
if (response.Content != "Ok.") if (response.Content.IsNotNullOrWhiteSpace() && response.Content != "Ok.")
{ {
_logger.Debug("qbitTorrent authentication failed."); _logger.Debug("qbitTorrent authentication failed.");
throw new DownloadClientAuthenticationException("Failed to authenticate with qBittorrent."); throw new DownloadClientAuthenticationException("Failed to authenticate with qBittorrent.");

View file

@ -1,34 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Dapper" Version="2.0.151" /> <PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="Diacritical.Net" Version="1.0.4" /> <PackageReference Include="Diacritical.Net" Version="1.0.4" />
<PackageReference Include="Equ" Version="2.3.0" /> <PackageReference Include="Equ" Version="2.3.0" />
<PackageReference Include="MailKit" Version="4.8.0" /> <PackageReference Include="MailKit" Version="4.14.0" />
<PackageReference Include="Polly" Version="8.5.2" /> <PackageReference Include="Polly" Version="8.6.4" />
<PackageReference Include="System.Text.Json" Version="6.0.10" /> <PackageReference Include="System.Text.Json" Version="8.0.6" />
<PackageReference Include="System.Memory" Version="4.6.2" /> <PackageReference Include="System.Memory" Version="4.6.3" />
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="6.0.35" /> <PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="8.0.20" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.3" /> <PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Servarr.FluentMigrator.Runner" Version="3.3.2.9" /> <PackageReference Include="FluentMigrator.Runner.Core" Version="6.2.0" />
<PackageReference Include="Servarr.FluentMigrator.Runner.SQLite" Version="3.3.2.9" /> <PackageReference Include="FluentMigrator.Runner.SQLite" Version="6.2.0" />
<PackageReference Include="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" /> <PackageReference Include="FluentMigrator.Runner.Postgres" Version="6.2.0" />
<PackageReference Include="FluentValidation" Version="9.5.4" /> <PackageReference Include="FluentValidation" Version="9.5.4" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="NLog" Version="5.4.0" /> <PackageReference Include="NLog" Version="5.4.0" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.4.0" /> <PackageReference Include="NLog.Extensions.Logging" Version="5.4.0" />
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" /> <PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageReference Include="System.IO.Abstractions" Version="17.0.24" /> <PackageReference Include="System.IO.Abstractions" Version="17.0.24" />
<PackageReference Include="TagLibSharp-Lidarr" Version="2.2.0.27" /> <PackageReference Include="TagLibSharp-Lidarr" Version="2.2.0.27" />
<PackageReference Include="Npgsql" Version="7.0.10" /> <PackageReference Include="Npgsql" Version="9.0.3" />
<PackageReference Include="SpotifyAPI.Web" Version="5.1.1" /> <PackageReference Include="SpotifyAPI.Web" Version="5.1.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" /> <PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
<PackageReference Include="MonoTorrent" Version="3.0.2" /> <PackageReference Include="MonoTorrent" Version="3.0.2" />
<PackageReference Include="System.Drawing.Common" Version="8.0.20" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\NzbDrone.Common\Lidarr.Common.csproj" /> <ProjectReference Include="..\NzbDrone.Common\Lidarr.Common.csproj" />

View file

@ -18,7 +18,7 @@
"AlbumStudio": "Studio Album", "AlbumStudio": "Studio Album",
"AllAlbums": "Tutti gli album", "AllAlbums": "Tutti gli album",
"Actions": "Azioni", "Actions": "Azioni",
"AllAlbumsData": "Monitora tutti gli album tranne quelli speciali", "AllAlbumsData": "Monitora tutti gli album",
"AllowFingerprintingHelpText": "Consenti impronte per migliorare il riconoscimento delle tracce", "AllowFingerprintingHelpText": "Consenti impronte per migliorare il riconoscimento delle tracce",
"AllowFingerprintingHelpTextWarning": "Questo richiede che {appName} legga le parti dei file e potrebbe causare unalta attività del disco e della rete.", "AllowFingerprintingHelpTextWarning": "Questo richiede che {appName} legga le parti dei file e potrebbe causare unalta attività del disco e della rete.",
"AllArtistAlbums": "Tutti gli Artisti degli album", "AllArtistAlbums": "Tutti gli Artisti degli album",
@ -95,17 +95,17 @@
"DeleteImportListMessageText": "Sei sicuro di voler eliminare la lista '{name}'?", "DeleteImportListMessageText": "Sei sicuro di voler eliminare la lista '{name}'?",
"DeleteIndexer": "Cancella Indicizzatore", "DeleteIndexer": "Cancella Indicizzatore",
"DeleteIndexerMessageText": "Sei sicuro di voler eliminare l'indicizzatore '{name}'?", "DeleteIndexerMessageText": "Sei sicuro di voler eliminare l'indicizzatore '{name}'?",
"DeleteMetadataProfileMessageText": "Sicuro di voler cancellare il profilo di qualità {0}", "DeleteMetadataProfileMessageText": "Sicuro di voler cancellare il profilo di qualità {name}",
"DeleteNotification": "Cancella Notifica", "DeleteNotification": "Cancella Notifica",
"DeleteNotificationMessageText": "Sei sicuro di voler eliminare la notifica '{name}'?", "DeleteNotificationMessageText": "Sei sicuro di voler eliminare la notifica '{name}'?",
"DeleteQualityProfile": "Cancellare il profilo di qualità", "DeleteQualityProfile": "Cancellare il profilo di qualità",
"DeleteQualityProfileMessageText": "Sicuro di voler cancellare il profilo di qualità {0}", "DeleteQualityProfileMessageText": "Sicuro di voler cancellare il profilo di qualità {name}?",
"DeleteReleaseProfile": "Cancellare il profilo di ritardo", "DeleteReleaseProfile": "Cancellare il profilo di Rilascio",
"DeleteReleaseProfileMessageText": "Sei sicuro di voler cancellare questo profilo di ritardo?", "DeleteReleaseProfileMessageText": "Sei sicuro di voler cancellare questo profilo di rilascio?",
"DeleteSelectedTrackFiles": "Cancellare i film selezionati", "DeleteSelectedTrackFiles": "Cancellare i film selezionati",
"DeleteSelectedTrackFilesMessageText": "Sei sicuro di voler eliminare i file del film selezionato?", "DeleteSelectedTrackFilesMessageText": "Sei sicuro di voler eliminare i file del film selezionato?",
"DeleteTag": "Cancella Tag", "DeleteTag": "Cancella Tag",
"DeleteTagMessageText": "Sei sicuro di voler eliminare il tag '{0}'?", "DeleteTagMessageText": "Sei sicuro di voler eliminare il tag '{label}'?",
"DestinationPath": "Percorso di destinazione", "DestinationPath": "Percorso di destinazione",
"DetailedProgressBar": "Barra di avanzamento dettagliata", "DetailedProgressBar": "Barra di avanzamento dettagliata",
"DetailedProgressBarHelpText": "Mostra testo sulla barra di avanzamento", "DetailedProgressBarHelpText": "Mostra testo sulla barra di avanzamento",
@ -144,7 +144,7 @@
"Fixed": "Fissato", "Fixed": "Fissato",
"Folder": "Cartella", "Folder": "Cartella",
"Folders": "Cartelle", "Folders": "Cartelle",
"ForMoreInformationOnTheIndividualDownloadClientsClickOnTheInfoButtons": "Per maggiori informazioni sui singoli Indexer clicca sul pulsante info.", "ForMoreInformationOnTheIndividualDownloadClientsClickOnTheInfoButtons": "Per maggiori informazioni sui singoli client di download, clicca sul pulsante info.",
"ForMoreInformationOnTheIndividualIndexersClickOnTheInfoButtons": "Per maggiori informazioni sui singoli Indexer clicca sul pulsante info.", "ForMoreInformationOnTheIndividualIndexersClickOnTheInfoButtons": "Per maggiori informazioni sui singoli Indexer clicca sul pulsante info.",
"ForMoreInformationOnTheIndividualListsClickOnTheInfoButtons": "Per maggiori informazioni sui singoli Indexer clicca sul pulsante info.", "ForMoreInformationOnTheIndividualListsClickOnTheInfoButtons": "Per maggiori informazioni sui singoli Indexer clicca sul pulsante info.",
"GeneralSettings": "Impostazioni Generali", "GeneralSettings": "Impostazioni Generali",
@ -198,7 +198,7 @@
"LogLevelvalueTraceTraceLoggingShouldOnlyBeEnabledTemporarily": "Il Trace Log dovrebbe essere abilitato solo temporaneamente", "LogLevelvalueTraceTraceLoggingShouldOnlyBeEnabledTemporarily": "Il Trace Log dovrebbe essere abilitato solo temporaneamente",
"Logs": "Logs", "Logs": "Logs",
"LongDateFormat": "Formato Data Lungo", "LongDateFormat": "Formato Data Lungo",
"MaintenanceRelease": "Release di Manutenzione", "MaintenanceRelease": "Release di Manutenzione: bug fix e altri miglioramenti. Vedi la storia dei commit Github per maggiori dettagli",
"ManualImport": "Import Manuale", "ManualImport": "Import Manuale",
"MarkAsFailed": "Segna come fallito", "MarkAsFailed": "Segna come fallito",
"MarkAsFailedMessageText": "Sei sicuro di voler segnare '{0}' come fallito?", "MarkAsFailedMessageText": "Sei sicuro di voler segnare '{0}' come fallito?",
@ -664,7 +664,7 @@
"CutoffFormatScoreHelpText": "Una volta raggiunto questo formato personalizzato, {appName} non scaricherà più i film", "CutoffFormatScoreHelpText": "Una volta raggiunto questo formato personalizzato, {appName} non scaricherà più i film",
"DeleteCustomFormat": "Cancella Formato Personalizzato", "DeleteCustomFormat": "Cancella Formato Personalizzato",
"DeleteCustomFormatMessageText": "Sei sicuro di voler eliminare il formato personalizzato '{name}'?", "DeleteCustomFormatMessageText": "Sei sicuro di voler eliminare il formato personalizzato '{name}'?",
"DeleteFormatMessageText": "Sei sicuro di voler cancellare il formato etichetta {0} ?", "DeleteFormatMessageText": "Sei sicuro di voler cancellare il formato etichetta {name} ?",
"DownloadPropersAndRepacksHelpTextWarning": "Usa i formati personalizzati per aggiornare automaticamente ai Proper/Repack", "DownloadPropersAndRepacksHelpTextWarning": "Usa i formati personalizzati per aggiornare automaticamente ai Proper/Repack",
"DownloadedUnableToImportCheckLogsForDetails": "Scaricato - Impossibile importare: controlla i log per i dettagli", "DownloadedUnableToImportCheckLogsForDetails": "Scaricato - Impossibile importare: controlla i log per i dettagli",
"ExportCustomFormat": "Esporta formato personalizzato", "ExportCustomFormat": "Esporta formato personalizzato",
@ -732,7 +732,7 @@
"BlocklistReleaseHelpText": "Impedisci a {appName} di re-acquisire automaticamente questa versione", "BlocklistReleaseHelpText": "Impedisci a {appName} di re-acquisire automaticamente questa versione",
"FailedToLoadQueue": "Impossibile caricare la coda", "FailedToLoadQueue": "Impossibile caricare la coda",
"BlocklistReleases": "Blocca questa Release", "BlocklistReleases": "Blocca questa Release",
"DeleteConditionMessageText": "Sei sicuro di voler eliminare l'etichetta '{0}'?", "DeleteConditionMessageText": "Sei sicuro di voler eliminare l'etichetta '{name}'?",
"Negated": "Negato", "Negated": "Negato",
"RemoveSelectedItems": "Rimuovi elementi selezionati", "RemoveSelectedItems": "Rimuovi elementi selezionati",
"Required": "necessario", "Required": "necessario",
@ -755,9 +755,9 @@
"Yes": "Si", "Yes": "Si",
"RemoveSelectedItemQueueMessageText": "Sei sicuro di voler rimuovere {0} dalla coda?", "RemoveSelectedItemQueueMessageText": "Sei sicuro di voler rimuovere {0} dalla coda?",
"RemoveSelectedItemsQueueMessageText": "Sei sicuro di voler rimuovere {0} dalla coda?", "RemoveSelectedItemsQueueMessageText": "Sei sicuro di voler rimuovere {0} dalla coda?",
"DeleteSelectedDownloadClientsMessageText": "Sei sicuro di voler eliminare l'indexer '{0}'?", "DeleteSelectedDownloadClientsMessageText": "Sei sicuro di voler eliminare {count} client di download selezionato(i)?",
"DeleteSelectedImportListsMessageText": "Sei sicuro di voler eliminare l'indexer '{0}'?", "DeleteSelectedImportListsMessageText": "Sei sicuro di voler eliminare {count} lista(e) di importazione selezionata(e)?",
"DeleteSelectedIndexersMessageText": "Sei sicuro di voler eliminare l'indexer '{0}'?", "DeleteSelectedIndexersMessageText": "Sei sicuro di voler eliminare {count} indice(i) selezionato(i)?",
"ApplyTagsHelpTextHowToApplyArtists": "Come applicare etichette agli indicizzatori selezionati", "ApplyTagsHelpTextHowToApplyArtists": "Come applicare etichette agli indicizzatori selezionati",
"ApplyTagsHelpTextHowToApplyDownloadClients": "Come applicare etichette ai client di download selezionati", "ApplyTagsHelpTextHowToApplyDownloadClients": "Come applicare etichette ai client di download selezionati",
"ApplyTagsHelpTextHowToApplyImportLists": "Come applicare etichette alle liste di importazione selezionate", "ApplyTagsHelpTextHowToApplyImportLists": "Come applicare etichette alle liste di importazione selezionate",

View file

@ -126,11 +126,11 @@ public bool ShouldDeleteFolder(IDirectoryInfo directoryInfo, Artist artist)
if (albumParseResult == null) if (albumParseResult == null)
{ {
_logger.Warn("Unable to parse file on import: [{0}]", audioFile); _logger.Warn("Unable to parse file on import: [{0}]", audioFile.FullName);
return false; return false;
} }
_logger.Warn("Audio file detected: [{0}]", audioFile); _logger.Warn("Audio file detected: [{0}]", audioFile.FullName);
return false; return false;
} }

View file

@ -1,6 +1,5 @@
using System; using System;
using System.IO; using System.IO;
using System.Runtime.Serialization;
namespace NzbDrone.Core.MediaFiles.TrackImport namespace NzbDrone.Core.MediaFiles.TrackImport
{ {
@ -19,10 +18,5 @@ public RecycleBinException(string message, Exception innerException)
: base(message, innerException) : base(message, innerException)
{ {
} }
protected RecycleBinException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
} }
} }

View file

@ -1,6 +1,5 @@
using System; using System;
using System.IO; using System.IO;
using System.Runtime.Serialization;
namespace NzbDrone.Core.MediaFiles.TrackImport namespace NzbDrone.Core.MediaFiles.TrackImport
{ {
@ -19,10 +18,5 @@ public RootFolderNotFoundException(string message, Exception innerException)
: base(message, innerException) : base(message, innerException)
{ {
} }
protected RootFolderNotFoundException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
} }
} }

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\NzbDrone.Host\Lidarr.Host.csproj" /> <ProjectReference Include="..\NzbDrone.Host\Lidarr.Host.csproj" />

View file

@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net8.0</TargetFrameworks>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" /> <PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.2" /> <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.6.2" /> <PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="9.0.6" />
<PackageReference Include="DryIoc.dll" Version="5.4.3" /> <PackageReference Include="DryIoc.dll" Version="5.4.3" />
<PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" /> <PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" />
</ItemGroup> </ItemGroup>

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net;
using DryIoc; using DryIoc;
using Lidarr.Api.V1.System; using Lidarr.Api.V1.System;
using Lidarr.Http; using Lidarr.Http;
@ -32,6 +33,7 @@
using NzbDrone.Host.AccessControl; using NzbDrone.Host.AccessControl;
using NzbDrone.Http.Authentication; using NzbDrone.Http.Authentication;
using NzbDrone.SignalR; using NzbDrone.SignalR;
using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
using LogLevel = Microsoft.Extensions.Logging.LogLevel; using LogLevel = Microsoft.Extensions.Logging.LogLevel;
namespace NzbDrone.Host namespace NzbDrone.Host
@ -60,8 +62,11 @@ public void ConfigureServices(IServiceCollection services)
services.Configure<ForwardedHeadersOptions>(options => services.Configure<ForwardedHeadersOptions>(options =>
{ {
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost; options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost;
options.KnownNetworks.Clear(); options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("10.0.0.0"), 8));
options.KnownProxies.Clear(); options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("172.16.0.0"), 12));
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("192.168.0.0"), 16));
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("fc00::"), 7));
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("fe80::"), 10));
}); });
services.AddRouting(options => options.LowercaseUrls = true); services.AddRouting(options => options.LowercaseUrls = true);

View file

@ -7,7 +7,7 @@
namespace NzbDrone.Integration.Test.ApiTests namespace NzbDrone.Integration.Test.ApiTests
{ {
[TestFixture] [TestFixture]
[Ignore("Waiting for metadata to be back again", Until = "2025-11-15 00:00:00Z")] [Ignore("Waiting for metadata to be back again", Until = "2025-12-31 00:00:00Z")]
public class ArtistEditorFixture : IntegrationTest public class ArtistEditorFixture : IntegrationTest
{ {
private void GivenExistingArtist() private void GivenExistingArtist()

View file

@ -7,7 +7,7 @@
namespace NzbDrone.Integration.Test.ApiTests namespace NzbDrone.Integration.Test.ApiTests
{ {
[TestFixture] [TestFixture]
[Ignore("Waiting for metadata to be back again", Until = "2025-11-15 00:00:00Z")] [Ignore("Waiting for metadata to be back again", Until = "2025-12-31 00:00:00Z")]
public class ArtistFixture : IntegrationTest public class ArtistFixture : IntegrationTest
{ {
[Test] [Test]

View file

@ -4,7 +4,7 @@
namespace NzbDrone.Integration.Test.ApiTests namespace NzbDrone.Integration.Test.ApiTests
{ {
[TestFixture] [TestFixture]
[Ignore("Waiting for metadata to be back again", Until = "2025-11-15 00:00:00Z")] [Ignore("Waiting for metadata to be back again", Until = "2025-12-31 00:00:00Z")]
public class ArtistLookupFixture : IntegrationTest public class ArtistLookupFixture : IntegrationTest
{ {
[TestCase("Kiss", "Kiss")] [TestCase("Kiss", "Kiss")]

View file

@ -6,7 +6,7 @@
namespace NzbDrone.Integration.Test.ApiTests namespace NzbDrone.Integration.Test.ApiTests
{ {
[TestFixture] [TestFixture]
[Ignore("Waiting for metadata to be back again", Until = "2025-11-15 00:00:00Z")] [Ignore("Waiting for metadata to be back again", Until = "2025-12-31 00:00:00Z")]
public class BlocklistFixture : IntegrationTest public class BlocklistFixture : IntegrationTest
{ {
private ArtistResource _artist; private ArtistResource _artist;

View file

@ -9,7 +9,7 @@
namespace NzbDrone.Integration.Test.ApiTests namespace NzbDrone.Integration.Test.ApiTests
{ {
[TestFixture] [TestFixture]
[Ignore("Waiting for metadata to be back again", Until = "2025-11-15 00:00:00Z")] [Ignore("Waiting for metadata to be back again", Until = "2025-12-31 00:00:00Z")]
public class CalendarFixture : IntegrationTest public class CalendarFixture : IntegrationTest
{ {
public ClientBase<AlbumResource> Calendar; public ClientBase<AlbumResource> Calendar;

View file

@ -7,7 +7,7 @@
namespace NzbDrone.Integration.Test.ApiTests namespace NzbDrone.Integration.Test.ApiTests
{ {
[TestFixture] [TestFixture]
[Ignore("Waiting for metadata to be back again", Until = "2025-11-15 00:00:00Z")] [Ignore("Waiting for metadata to be back again", Until = "2025-12-31 00:00:00Z")]
public class TrackFixture : IntegrationTest public class TrackFixture : IntegrationTest
{ {
private ArtistResource _artist; private ArtistResource _artist;

View file

@ -8,7 +8,7 @@
namespace NzbDrone.Integration.Test.ApiTests.WantedTests namespace NzbDrone.Integration.Test.ApiTests.WantedTests
{ {
[TestFixture] [TestFixture]
[Ignore("Waiting for metadata to be back again", Until = "2025-11-15 00:00:00Z")] [Ignore("Waiting for metadata to be back again", Until = "2025-12-31 00:00:00Z")]
public class CutoffUnmetFixture : IntegrationTest public class CutoffUnmetFixture : IntegrationTest
{ {
[SetUp] [SetUp]

View file

@ -7,7 +7,7 @@
namespace NzbDrone.Integration.Test.ApiTests.WantedTests namespace NzbDrone.Integration.Test.ApiTests.WantedTests
{ {
[TestFixture] [TestFixture]
[Ignore("Waiting for metadata to be back again", Until = "2025-11-15 00:00:00Z")] [Ignore("Waiting for metadata to be back again", Until = "2025-12-31 00:00:00Z")]
public class MissingFixture : IntegrationTest public class MissingFixture : IntegrationTest
{ {
[SetUp] [SetUp]

View file

@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net8.0</TargetFrameworks>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.35" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.20" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Lidarr.Api.V1\Lidarr.Api.V1.csproj" /> <ProjectReference Include="..\Lidarr.Api.V1\Lidarr.Api.V1.csproj" />

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\NzbDrone.Test.Common\Lidarr.Test.Common.csproj" /> <ProjectReference Include="..\NzbDrone.Test.Common\Lidarr.Test.Common.csproj" />

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr20" /> <PackageReference Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr20" />

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net8.0</TargetFrameworks>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net8.0</TargetFrameworks>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentAssertions" Version="5.10.3" /> <PackageReference Include="FluentAssertions" Version="5.10.3" />

View file

@ -58,7 +58,7 @@ public void Start(bool enableAuth = false)
_startupLog = new List<string>(); _startupLog = new List<string>();
if (BuildInfo.IsDebug) if (BuildInfo.IsDebug)
{ {
Start(Path.Combine(TestContext.CurrentContext.TestDirectory, "..", "..", "_output", "net6.0", lidarrConsoleExe)); Start(Path.Combine(TestContext.CurrentContext.TestDirectory, "..", "..", "_output", "net8.0", lidarrConsoleExe));
} }
else else
{ {

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\NzbDrone.Test.Common\Lidarr.Test.Common.csproj" /> <ProjectReference Include="..\NzbDrone.Test.Common\Lidarr.Test.Common.csproj" />

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DryIoc.dll" Version="5.4.3" /> <PackageReference Include="DryIoc.dll" Version="5.4.3" />

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\NzbDrone.Common.Test\Lidarr.Common.Test.csproj" /> <ProjectReference Include="..\NzbDrone.Common.Test\Lidarr.Common.Test.csproj" />

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net8.0</TargetFrameworks>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View file

@ -1,14 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop"> <Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFrameworks>net6.0-windows</TargetFrameworks> <TargetFrameworks>net8.0-windows</TargetFrameworks>
<RuntimeIdentifiers>win-x64;win-x86</RuntimeIdentifiers> <RuntimeIdentifiers>win-x64;win-x86</RuntimeIdentifiers>
<UseWindowsForms>true</UseWindowsForms> <UseWindowsForms>true</UseWindowsForms>
<ApplicationIcon>..\NzbDrone.Host\NzbDrone.ico</ApplicationIcon> <ApplicationIcon>..\NzbDrone.Host\NzbDrone.ico</ApplicationIcon>
<GenerateResourceUsePreserializedResources>true</GenerateResourceUsePreserializedResources> <GenerateResourceUsePreserializedResources>true</GenerateResourceUsePreserializedResources>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Resources.Extensions" Version="6.0.0" /> <PackageReference Include="System.Resources.Extensions" Version="8.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\NzbDrone.Host\Lidarr.Host.csproj" /> <ProjectReference Include="..\NzbDrone.Host\Lidarr.Host.csproj" />

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Security.Principal.Windows" Version="5.0.0" /> <PackageReference Include="System.Security.Principal.Windows" Version="5.0.0" />

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Security.Principal.Windows" Version="5.0.0" /> <PackageReference Include="System.Security.Principal.Windows" Version="5.0.0" />

View file

@ -2,10 +2,10 @@
<ItemGroup> <ItemGroup>
<RuntimeFiles Include="..\Runtimes\$(RuntimeIdentifier)\*" /> <RuntimeFiles Include="..\Runtimes\$(RuntimeIdentifier)\*" />
</ItemGroup> </ItemGroup>
<Target Name="CopyRuntimeFilesOnBuild" AfterTargets="AfterBuild" Condition="!$(RuntimeIdentifier.StartsWith('linux')) or '$(TargetFramework)' == 'net6.0'"> <Target Name="CopyRuntimeFilesOnBuild" AfterTargets="AfterBuild" Condition="!$(RuntimeIdentifier.StartsWith('linux')) or '$(TargetFramework)' == 'net8.0'">
<Copy SourceFiles="@(RuntimeFiles)" DestinationFolder="$(OutDir)" /> <Copy SourceFiles="@(RuntimeFiles)" DestinationFolder="$(OutDir)" />
</Target> </Target>
<Target Name="CopyRuntimeFilesOnPublish" AfterTargets="Publish" Condition="!$(RuntimeIdentifier.StartsWith('linux')) or '$(TargetFramework)' == 'net6.0'"> <Target Name="CopyRuntimeFilesOnPublish" AfterTargets="Publish" Condition="!$(RuntimeIdentifier.StartsWith('linux')) or '$(TargetFramework)' == 'net8.0'">
<Copy SourceFiles="@(RuntimeFiles)" DestinationFolder="$(PublishDir)" /> <Copy SourceFiles="@(RuntimeFiles)" DestinationFolder="$(PublishDir)" />
</Target> </Target>
</Project> </Project>

View file

@ -37,11 +37,14 @@ fi
if [ "$PLATFORM" = "Windows" ]; then if [ "$PLATFORM" = "Windows" ]; then
mkdir -p "$ProgramData/Lidarr" mkdir -p "$ProgramData/Lidarr"
WHERE="$WHERE&Category!=LINUX" WHERE="$WHERE&Category!=LINUX"
elif [ "$PLATFORM" = "Linux" ] || [ "$PLATFORM" = "Mac" ] ; then elif [ "$PLATFORM" = "Linux" ]; then
mkdir -p ~/.config/Lidarr mkdir -p ~/.config/Lidarr
WHERE="$WHERE&Category!=WINDOWS" WHERE="$WHERE&Category!=WINDOWS"
elif [ "$PLATFORM" = "Mac" ]; then
mkdir -p ~/Library/Application\ Support/Lidarr
WHERE="$WHERE&Category!=WINDOWS"
else else
echo "Platform must be provided as first arguement: Windows, Linux or Mac" echo "Platform must be provided as first argument: Windows, Linux or Mac"
exit 1 exit 1
fi fi

View file

@ -1246,16 +1246,16 @@
resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60" resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60"
integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA== integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==
"@microsoft/signalr@6.0.25": "@microsoft/signalr@8.0.17":
version "6.0.25" version "8.0.17"
resolved "https://registry.yarnpkg.com/@microsoft/signalr/-/signalr-6.0.25.tgz#009f043066d383e2de41a483bd7e02bfd74d3cf8" resolved "https://registry.yarnpkg.com/@microsoft/signalr/-/signalr-8.0.17.tgz#6eac218beac38e4e4ba4a3577ed39ac33711b2ed"
integrity sha512-8AzrpxS+E0yn1tXSlv7+UlURLmSxTQDgbvOT0pGKXjZT7MkhnDP+/GLuk7veRtUjczou/x32d9PHhYlr2NBy6Q== integrity sha512-5pM6xPtKZNJLO0Tq5nQasVyPFwi/WBY3QB5uc/v3dIPTpS1JXQbaXAQAPxFoQ5rTBFE094w8bbqkp17F9ReQvA==
dependencies: dependencies:
abort-controller "^3.0.0" abort-controller "^3.0.0"
eventsource "^1.0.7" eventsource "^2.0.2"
fetch-cookie "^0.11.0" fetch-cookie "^2.0.3"
node-fetch "^2.6.7" node-fetch "^2.6.7"
ws "^7.4.5" ws "^7.5.10"
"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
version "5.1.1-v1" version "5.1.1-v1"
@ -3428,10 +3428,10 @@ events@^3.2.0:
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
eventsource@^1.0.7: eventsource@^2.0.2:
version "1.1.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.1.2.tgz#bc75ae1c60209e7cb1541231980460343eaea7c2" resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-2.0.2.tgz#76dfcc02930fb2ff339520b6d290da573a9e8508"
integrity sha512-xAH3zWhgO2/3KIniEKYPr8plNSzlGINOUqYj0m0u7AB81iRw8b/3E73W6AuU+6klLbaSFmZnaETQ2lXPfAydrA== integrity sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3" version "3.1.3"
@ -3488,12 +3488,13 @@ faye-websocket@~0.10.0:
dependencies: dependencies:
websocket-driver ">=0.5.1" websocket-driver ">=0.5.1"
fetch-cookie@^0.11.0: fetch-cookie@^2.0.3:
version "0.11.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/fetch-cookie/-/fetch-cookie-0.11.0.tgz#e046d2abadd0ded5804ce7e2cae06d4331c15407" resolved "https://registry.yarnpkg.com/fetch-cookie/-/fetch-cookie-2.2.0.tgz#01086b6b5b1c3e08f15ffd8647b02ca100377365"
integrity sha512-BQm7iZLFhMWFy5CZ/162sAGjBfdNWb7a8LEqqnzsHFhxT/X/SVj/z2t2nu3aJvjlbQkrAlTUApplPRjWyH4mhA== integrity sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ==
dependencies: dependencies:
tough-cookie "^2.3.3 || ^3.0.1 || ^4.0.0" set-cookie-parser "^2.4.8"
tough-cookie "^4.0.0"
file-entry-cache@^6.0.1: file-entry-cache@^6.0.1:
version "6.0.1" version "6.0.1"
@ -6337,6 +6338,11 @@ serialize-javascript@^6.0.1:
dependencies: dependencies:
randombytes "^2.1.0" randombytes "^2.1.0"
set-cookie-parser@^2.4.8:
version "2.7.1"
resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz#3016f150072202dfbe90fadee053573cc89d2943"
integrity sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==
set-function-length@^1.2.1, set-function-length@^1.2.2: set-function-length@^1.2.1, set-function-length@^1.2.2:
version "1.2.2" version "1.2.2"
resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
@ -6875,7 +6881,7 @@ to-space-case@^1.0.0:
dependencies: dependencies:
to-no-case "^1.0.0" to-no-case "^1.0.0"
"tough-cookie@^2.3.3 || ^3.0.1 || ^4.0.0": tough-cookie@^4.0.0:
version "4.1.4" version "4.1.4"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.4.tgz#945f1461b45b5a8c76821c33ea49c3ac192c1b36" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.4.tgz#945f1461b45b5a8c76821c33ea49c3ac192c1b36"
integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag== integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==
@ -7414,7 +7420,7 @@ write-file-atomic@^5.0.1:
imurmurhash "^0.1.4" imurmurhash "^0.1.4"
signal-exit "^4.0.1" signal-exit "^4.0.1"
ws@^7.4.5: ws@^7.5.10:
version "7.5.10" version "7.5.10"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9"
integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==