From 330ed32f115c4a72391b264178a4f8b285ca60a8 Mon Sep 17 00:00:00 2001 From: Cody Kickertz Date: Mon, 22 Dec 2025 15:37:23 -0600 Subject: [PATCH] refactor: extract BaseMediaService base class (#137) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: extract BaseMediaService base class Extract common CRUD operations into BaseMediaService: - Get, GetAll, Paged, Add, AddMany, Delete, DeleteMany, Update, UpdateMany - SetAddedTimestamp with reflection for non-MediaItem types - Virtual event hooks (OnItemAdded, OnItemDeleted, etc.) Migrate services to use base class: - BookService: 180 → 89 lines - AudiobookService: 192 → 93 lines - AlbumService: 132 → 58 lines - ArtistService: 107 → 50 lines - TrackService: 114 → 50 lines Net reduction: ~385 lines * chore: cleanup stale files and fix branding Remove IDE/editor config files that should not be tracked: - .vscode/, frontend/.vscode/, src/.idea/ - azure-pipelines.yml (obsolete CI) - Empty localization files (bs, ta, et, lt, sr, es_MX) Remove unused npm packages: - react-addons-shallow-compare - react-async-script Fix remaining Radarr→Aletheia branding: - ConsoleApp.cs error messages - openapi.json title/description/license - FileNameBuilder.cs default release group --------- Co-authored-by: admin --- .vscode/extensions.json | 7 - .vscode/launch.json | 26 - .vscode/tasks.json | 44 - azure-pipelines.yml | 1244 ----------------- frontend/.vscode/extensions.json | 7 - frontend/.vscode/settings.json | 23 - package.json | 2 - src/.idea/.idea.NzbDrone/.idea/.name | 1 - src/.idea/.idea.NzbDrone/.idea/encodings.xml | 4 - .../.idea.NzbDrone/.idea/indexLayout.xml | 8 - src/.idea/.idea.NzbDrone/.idea/misc.xml | 6 - src/.idea/.idea.NzbDrone/.idea/vcs.xml | 7 - src/NzbDrone.Console/ConsoleApp.cs | 4 +- .../Audiobooks/AudiobookService.cs | 179 +-- src/NzbDrone.Core/Books/BookService.cs | 167 +-- src/NzbDrone.Core/Localization/Core/bs.json | 1 - .../Localization/Core/es_MX.json | 1 - src/NzbDrone.Core/Localization/Core/et.json | 1 - src/NzbDrone.Core/Localization/Core/lt.json | 1 - src/NzbDrone.Core/Localization/Core/sr.json | 1 - src/NzbDrone.Core/Localization/Core/ta.json | 1 - .../MediaItems/BaseMediaService.cs | 122 ++ src/NzbDrone.Core/Music/AlbumService.cs | 114 +- src/NzbDrone.Core/Music/ArtistService.cs | 93 +- src/NzbDrone.Core/Music/TrackService.cs | 98 +- .../Organizer/FileNameBuilder.cs | 2 +- src/Radarr.Api.V3/openapi.json | 6 +- 27 files changed, 261 insertions(+), 1909 deletions(-) delete mode 100644 .vscode/extensions.json delete mode 100644 .vscode/launch.json delete mode 100644 .vscode/tasks.json delete mode 100644 azure-pipelines.yml delete mode 100644 frontend/.vscode/extensions.json delete mode 100644 frontend/.vscode/settings.json delete mode 100644 src/.idea/.idea.NzbDrone/.idea/.name delete mode 100644 src/.idea/.idea.NzbDrone/.idea/encodings.xml delete mode 100644 src/.idea/.idea.NzbDrone/.idea/indexLayout.xml delete mode 100644 src/.idea/.idea.NzbDrone/.idea/misc.xml delete mode 100644 src/.idea/.idea.NzbDrone/.idea/vcs.xml delete mode 100644 src/NzbDrone.Core/Localization/Core/bs.json delete mode 100644 src/NzbDrone.Core/Localization/Core/es_MX.json delete mode 100644 src/NzbDrone.Core/Localization/Core/et.json delete mode 100644 src/NzbDrone.Core/Localization/Core/lt.json delete mode 100644 src/NzbDrone.Core/Localization/Core/sr.json delete mode 100644 src/NzbDrone.Core/Localization/Core/ta.json create mode 100644 src/NzbDrone.Core/MediaItems/BaseMediaService.cs diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 7a36fefe19..0000000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "recommendations": [ - "esbenp.prettier-vscode", - "ms-dotnettools.csdevkit", - "ms-vscode-remote.remote-containers" - ] -} diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 832711c354..0000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md - "name": "Run Radarr", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build dotnet", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/_output/net8.0/Radarr", - "args": [], - "cwd": "${workspaceFolder}", - // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console - "console": "integratedTerminal", - "stopAtEntry": false - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach" - } - ] -} diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 13b0a6254c..0000000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "build dotnet", - "command": "dotnet", - "type": "process", - "args": [ - "msbuild", - "-restore", - "${workspaceFolder}/src/Radarr.sln", - "-p:GenerateFullPaths=true", - "-p:Configuration=Debug", - "-p:Platform=Posix", - "-consoleloggerparameters:NoSummary;ForceNoAlign" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "publish", - "command": "dotnet", - "type": "process", - "args": [ - "publish", - "${workspaceFolder}/src/Radarr.sln", - "-property:GenerateFullPaths=true", - "-consoleloggerparameters:NoSummary;ForceNoAlign" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "watch", - "command": "dotnet", - "type": "process", - "args": [ - "watch", - "run", - "--project", - "${workspaceFolder}/src/Radarr.sln" - ], - "problemMatcher": "$msCompile" - } - ] -} diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index f9852aee16..0000000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,1244 +0,0 @@ -# Starter pipeline -# Start with a minimal pipeline that you can customize to build and deploy your code. -# Add steps that build, run tests, deploy, and more: -# https://aka.ms/yaml - -variables: - outputFolder: './_output' - artifactsFolder: './_artifacts' - testsFolder: './_tests' - yarnCacheFolder: $(Pipeline.Workspace)/.yarn - nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages - majorVersion: '6.1.0' - minorVersion: $[counter('minorVersion', 2000)] - radarrVersion: '$(majorVersion).$(minorVersion)' - buildName: '$(Build.SourceBranchName).$(radarrVersion)' - sentryOrg: 'servarr' - sentryUrl: 'https://sentry.servarr.com' - dotnetVersion: '8.0.405' - nodeVersion: '20.X' - innoVersion: '6.2.2' - windowsImage: 'windows-2025' - linuxImage: 'ubuntu-24.04' - macImage: 'macOS-15' - -trigger: - branches: - include: - - develop - - master - paths: - exclude: - - .github - - src/Radarr.Api.*/openapi.json - -pr: - branches: - include: - - develop - paths: - exclude: - - .github - - src/NzbDrone.Core/Localization/Core - - src/Radarr.Api.*/openapi.json - -stages: - - stage: Setup - displayName: Setup - jobs: - - job: - displayName: Build Variables - pool: - vmImage: ${{ variables.linuxImage }} - steps: - # Set the build name properly. The 'name' property won't recursively expand so hack here: - - bash: echo "##vso[build.updatebuildnumber]$RADARRVERSION" - displayName: Set Build Name - - bash: | - if [[ $BUILD_REASON == "PullRequest" ]]; then - git diff origin/develop...HEAD --name-only | grep -E "^(src/|azure-pipelines.yml)" - echo $? > not_backend_update - else - echo 0 > not_backend_update - fi - cat not_backend_update - displayName: Check for Backend File Changes - - publish: not_backend_update - artifact: not_backend_update - displayName: Publish update type - - stage: Build_Backend - displayName: Build Backend - dependsOn: Setup - jobs: - - job: Backend - strategy: - matrix: - Linux: - osName: 'Linux' - imageName: ${{ variables.linuxImage }} - enableAnalysis: 'true' - Mac: - osName: 'Mac' - imageName: ${{ variables.macImage }} - enableAnalysis: 'false' - Windows: - osName: 'Windows' - imageName: ${{ variables.windowsImage }} - enableAnalysis: 'false' - - pool: - vmImage: $(imageName) - variables: - # Disable stylecop here - linting errors get caught by the analyze task - EnableAnalyzers: $(enableAnalysis) - steps: - - checkout: self - submodules: true - fetchDepth: 1 - - task: UseDotNet@2 - displayName: 'Install .net core' - inputs: - version: $(dotnetVersion) - - bash: | - BUNDLEDVERSIONS=${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}/Microsoft.NETCoreSdk.BundledVersions.props - echo $BUNDLEDVERSIONS - if grep -q freebsd-x64 $BUNDLEDVERSIONS; then - echo "Extra platforms already enabled" - else - echo "Enabling extra platform support" - sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64/' "$BUNDLEDVERSIONS" - fi - displayName: Enable Extra Platform Support - - bash: ./build.sh --backend --enable-extra-platforms - displayName: Build Radarr Backend - - bash: | - find ${OUTPUTFOLDER} -type f ! -path "*/publish/*" -exec rm -rf {} \; - find ${OUTPUTFOLDER} -depth -empty -type d -exec rm -r "{}" \; - find ${TESTSFOLDER} -type f ! -path "*/publish/*" -exec rm -rf {} \; - find ${TESTSFOLDER} -depth -empty -type d -exec rm -r "{}" \; - displayName: Clean up intermediate output - condition: and(succeeded(), ne(variables['osName'], 'Windows')) - - publish: $(outputFolder) - artifact: '$(osName)Backend' - displayName: Publish Backend - condition: and(succeeded(), eq(variables['osName'], 'Windows')) - - publish: '$(testsFolder)/net8.0/win-x64/publish' - artifact: win-x64-tests - displayName: Publish win-x64 Test Package - condition: and(succeeded(), eq(variables['osName'], 'Windows')) - - publish: '$(testsFolder)/net8.0/linux-x64/publish' - artifact: linux-x64-tests - displayName: Publish linux-x64 Test Package - condition: and(succeeded(), eq(variables['osName'], 'Windows')) - - publish: '$(testsFolder)/net8.0/linux-musl-x64/publish' - artifact: linux-musl-x64-tests - displayName: Publish linux-musl-x64 Test Package - condition: and(succeeded(), eq(variables['osName'], 'Windows')) - - publish: '$(testsFolder)/net8.0/freebsd-x64/publish' - artifact: freebsd-x64-tests - displayName: Publish freebsd-x64 Test Package - condition: and(succeeded(), eq(variables['osName'], 'Windows')) - - publish: '$(testsFolder)/net8.0/osx-x64/publish' - artifact: osx-x64-tests - displayName: Publish osx-x64 Test Package - condition: and(succeeded(), eq(variables['osName'], 'Windows')) - - - stage: Build_Frontend - displayName: Frontend - dependsOn: Setup - jobs: - - job: Build - strategy: - matrix: - Linux: - osName: 'Linux' - imageName: ${{ variables.linuxImage }} - Mac: - osName: 'Mac' - imageName: ${{ variables.macImage }} - Windows: - osName: 'Windows' - imageName: ${{ variables.windowsImage }} - pool: - vmImage: $(imageName) - steps: - - task: UseNode@1 - displayName: Set Node.js version - inputs: - version: $(nodeVersion) - - checkout: self - submodules: true - fetchDepth: 1 - - task: Cache@2 - inputs: - key: 'yarn | "$(osName)" | yarn.lock' - restoreKeys: | - yarn | "$(osName)" - path: $(yarnCacheFolder) - displayName: Cache Yarn packages - - bash: ./build.sh --frontend - displayName: Build Radarr Frontend - env: - FORCE_COLOR: 0 - YARN_CACHE_FOLDER: $(yarnCacheFolder) - - publish: $(outputFolder) - artifact: '$(osName)Frontend' - displayName: Publish Frontend - condition: and(succeeded(), eq(variables['osName'], 'Windows')) - - - stage: Installer - dependsOn: - - Build_Backend - - Build_Frontend - jobs: - - job: Windows_Installer - displayName: Create Installer - pool: - vmImage: ${{ variables.windowsImage }} - steps: - - checkout: self - fetchDepth: 1 - - task: DownloadPipelineArtifact@2 - inputs: - buildType: 'current' - artifactName: WindowsBackend - targetPath: _output - displayName: Fetch Backend - - task: DownloadPipelineArtifact@2 - inputs: - buildType: 'current' - artifactName: WindowsFrontend - targetPath: _output - displayName: Fetch Frontend - - bash: | - ./build.sh --packages --installer - cp distribution/windows/setup/output/Radarr.*win-x64.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x64-installer.exe - cp distribution/windows/setup/output/Radarr.*win-x86.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x86-installer.exe - displayName: Create Installers - - publish: $(Build.ArtifactStagingDirectory) - artifact: 'WindowsInstaller' - displayName: Publish Installer - - - stage: Packages - dependsOn: - - Build_Backend - - Build_Frontend - jobs: - - job: Other_Packages - displayName: Create Standard Packages - pool: - vmImage: ${{ variables.linuxImage }} - steps: - - checkout: self - fetchDepth: 1 - - task: DownloadPipelineArtifact@2 - inputs: - buildType: 'current' - artifactName: WindowsBackend - targetPath: _output - displayName: Fetch Backend - - task: DownloadPipelineArtifact@2 - inputs: - buildType: 'current' - artifactName: WindowsFrontend - targetPath: _output - displayName: Fetch Frontend - - bash: ./build.sh --packages --enable-extra-platforms - displayName: Create Packages - - bash: | - find . -name "ffprobe" -exec chmod a+x {} \; - find . -name "Radarr" -exec chmod a+x {} \; - find . -name "Radarr.Update" -exec chmod a+x {} \; - displayName: Set executable bits - - task: ArchiveFiles@2 - displayName: Create win-x64 zip - inputs: - archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x64.zip' - archiveType: 'zip' - includeRootFolder: false - rootFolderOrFile: $(artifactsFolder)/win-x64/net8.0 - - task: ArchiveFiles@2 - displayName: Create win-x86 zip - inputs: - archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x86.zip' - archiveType: 'zip' - includeRootFolder: false - rootFolderOrFile: $(artifactsFolder)/win-x86/net8.0 - - task: ArchiveFiles@2 - displayName: Create osx-x64 app - inputs: - archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-x64.zip' - archiveType: 'zip' - includeRootFolder: false - rootFolderOrFile: $(artifactsFolder)/osx-x64-app/net8.0 - - task: ArchiveFiles@2 - displayName: Create osx-x64 tar - inputs: - archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-core-x64.tar.gz' - archiveType: 'tar' - tarCompression: 'gz' - includeRootFolder: false - rootFolderOrFile: $(artifactsFolder)/osx-x64/net8.0 - - task: ArchiveFiles@2 - displayName: Create osx-arm64 app - inputs: - archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-arm64.zip' - archiveType: 'zip' - includeRootFolder: false - rootFolderOrFile: $(artifactsFolder)/osx-arm64-app/net8.0 - - task: ArchiveFiles@2 - displayName: Create osx-arm64 tar - inputs: - archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-core-arm64.tar.gz' - archiveType: 'tar' - tarCompression: 'gz' - includeRootFolder: false - rootFolderOrFile: $(artifactsFolder)/osx-arm64/net8.0 - - task: ArchiveFiles@2 - displayName: Create linux-x64 tar - inputs: - archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-core-x64.tar.gz' - archiveType: 'tar' - tarCompression: 'gz' - includeRootFolder: false - rootFolderOrFile: $(artifactsFolder)/linux-x64/net8.0 - - task: ArchiveFiles@2 - displayName: Create linux-musl-x64 tar - inputs: - archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-musl-core-x64.tar.gz' - archiveType: 'tar' - tarCompression: 'gz' - includeRootFolder: false - rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net8.0 - - task: ArchiveFiles@2 - displayName: Create linux-arm tar - inputs: - archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-core-arm.tar.gz' - archiveType: 'tar' - tarCompression: 'gz' - includeRootFolder: false - rootFolderOrFile: $(artifactsFolder)/linux-arm/net8.0 - - task: ArchiveFiles@2 - displayName: Create linux-musl-arm tar - inputs: - archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-musl-core-arm.tar.gz' - archiveType: 'tar' - tarCompression: 'gz' - includeRootFolder: false - rootFolderOrFile: $(artifactsFolder)/linux-musl-arm/net8.0 - - task: ArchiveFiles@2 - displayName: Create linux-arm64 tar - inputs: - archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-core-arm64.tar.gz' - archiveType: 'tar' - tarCompression: 'gz' - includeRootFolder: false - rootFolderOrFile: $(artifactsFolder)/linux-arm64/net8.0 - - task: ArchiveFiles@2 - displayName: Create linux-musl-arm64 tar - inputs: - archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-musl-core-arm64.tar.gz' - archiveType: 'tar' - tarCompression: 'gz' - includeRootFolder: false - rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net8.0 - - task: ArchiveFiles@2 - displayName: Create freebsd-x64 tar - inputs: - archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).freebsd-core-x64.tar.gz' - archiveType: 'tar' - tarCompression: 'gz' - includeRootFolder: false - rootFolderOrFile: $(artifactsFolder)/freebsd-x64/net8.0 - - publish: $(Build.ArtifactStagingDirectory) - artifact: 'Packages' - displayName: Publish Packages - - bash: | - echo "Uploading source maps to sentry" - curl -sL https://sentry.io/get-cli/ | bash - RELEASENAME="Radarr@${RADARRVERSION}-${BUILD_SOURCEBRANCHNAME}" - sentry-cli releases new --finalize -p radarr -p radarr-ui -p radarr-update "${RELEASENAME}" - sentry-cli releases -p radarr-ui files "${RELEASENAME}" upload-sourcemaps _output/UI/ --rewrite - sentry-cli releases set-commits --auto "${RELEASENAME}" - if [[ ${BUILD_SOURCEBRANCH} == "refs/heads/develop" ]]; then - sentry-cli releases deploys "${RELEASENAME}" new -e nightly - else - sentry-cli releases deploys "${RELEASENAME}" new -e production - fi - if [ $? -gt 0 ]; then - echo "##vso[task.logissue type=warning]Error uploading source maps." - fi - exit 0 - displayName: Publish Sentry Source Maps - condition: | - or - ( - and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop')), - and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master')) - ) - env: - SENTRY_AUTH_TOKEN: $(sentryAuthTokenServarr) - SENTRY_ORG: $(sentryOrg) - SENTRY_URL: $(sentryUrl) - - - stage: Unit_Test - displayName: Unit Tests - dependsOn: Build_Backend - - jobs: - - job: Prepare - pool: - vmImage: ${{ variables.linuxImage }} - steps: - - checkout: none - - task: DownloadPipelineArtifact@2 - inputs: - buildType: 'current' - artifactName: 'not_backend_update' - targetPath: '.' - - bash: echo "##vso[task.setvariable variable=backendNotUpdated;isOutput=true]$(cat not_backend_update)" - name: setVar - - - job: Unit - displayName: Unit Native - dependsOn: Prepare - condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0')) - workspace: - clean: all - - strategy: - matrix: - MacCore: - osName: 'Mac' - testName: 'osx-x64' - poolName: 'Azure Pipelines' - imageName: ${{ variables.macImage }} - WindowsCore: - osName: 'Windows' - testName: 'win-x64' - poolName: 'Azure Pipelines' - imageName: ${{ variables.windowsImage }} - LinuxCore: - osName: 'Linux' - testName: 'linux-x64' - poolName: 'Azure Pipelines' - imageName: ${{ variables.linuxImage }} - FreebsdCore: - osName: 'Linux' - testName: 'freebsd-x64' - poolName: 'FreeBSD' - imageName: - - pool: - name: $(poolName) - vmImage: $(imageName) - - steps: - - checkout: none - - task: UseDotNet@2 - displayName: 'Install .net core' - inputs: - version: $(dotnetVersion) - condition: ne(variables['poolName'], 'FreeBSD') - - task: DownloadPipelineArtifact@2 - displayName: Download Test Artifact - inputs: - buildType: 'current' - artifactName: '$(testName)-tests' - targetPath: $(testsFolder) - - powershell: Set-Service SCardSvr -StartupType Manual - displayName: Enable Windows Test Service - condition: and(succeeded(), eq(variables['osName'], 'Windows')) - - bash: | - chmod a+x _tests/ffprobe - displayName: Make ffprobe Executable - condition: and(succeeded(), ne(variables['osName'], 'Windows')) - - bash: find ${TESTSFOLDER} -name "Radarr.Test.Dummy" -exec chmod a+x {} \; - displayName: Make Test Dummy Executable - condition: and(succeeded(), ne(variables['osName'], 'Windows')) - - bash: | - chmod a+x ${TESTSFOLDER}/test.sh - ${TESTSFOLDER}/test.sh ${OSNAME} Unit Test - displayName: Run Tests - env: - TEST_DIR: $(Build.SourcesDirectory)/_tests - - task: PublishTestResults@2 - displayName: Publish Test Results - inputs: - testResultsFormat: 'NUnit' - testResultsFiles: '**/TestResult.xml' - testRunTitle: '$(testName) Unit Tests' - failTaskOnFailedTests: true - failTaskOnMissingResultsFile: ne(variables['testName'], 'freebsd-x64') - - - job: Unit_Docker - displayName: Unit Docker - dependsOn: Prepare - condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0')) - strategy: - matrix: - alpine: - testName: 'Musl Net Core' - artifactName: linux-musl-x64-tests - containerImage: ghcr.io/servarr/testimages:alpine - - pool: - vmImage: ${{ variables.linuxImage }} - - container: $[ variables['containerImage'] ] - - timeoutInMinutes: 10 - - steps: - - task: UseDotNet@2 - displayName: 'Install .NET' - inputs: - version: $(dotnetVersion) - - checkout: none - - task: DownloadPipelineArtifact@2 - displayName: Download Test Artifact - inputs: - buildType: 'current' - artifactName: $(artifactName) - targetPath: $(testsFolder) - - bash: | - chmod a+x _tests/ffprobe - displayName: Make ffprobe Executable - - bash: find ${TESTSFOLDER} -name "Radarr.Test.Dummy" -exec chmod a+x {} \; - displayName: Make Test Dummy Executable - condition: and(succeeded(), ne(variables['osName'], 'Windows')) - - bash: | - chmod a+x ${TESTSFOLDER}/test.sh - ls -lR ${TESTSFOLDER} - ${TESTSFOLDER}/test.sh Linux Unit Test - displayName: Run Tests - - task: PublishTestResults@2 - displayName: Publish Test Results - inputs: - testResultsFormat: 'NUnit' - testResultsFiles: '**/TestResult.xml' - testRunTitle: '$(testName) Unit Tests' - failTaskOnFailedTests: true - failTaskOnMissingResultsFile: true - - - job: Unit_LinuxCore_Postgres14 - displayName: Unit Native LinuxCore with Postgres14 Database - dependsOn: Prepare - condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0')) - variables: - pattern: 'Radarr.*.linux-core-x64.tar.gz' - artifactName: linux-x64-tests - Radarr__Postgres__Host: 'localhost' - Radarr__Postgres__Port: '5432' - Radarr__Postgres__User: 'radarr' - Radarr__Postgres__Password: 'radarr' - - pool: - vmImage: ${{ variables.linuxImage }} - - timeoutInMinutes: 10 - - steps: - - task: UseDotNet@2 - displayName: 'Install .net core' - inputs: - version: $(dotnetVersion) - - checkout: none - - task: DownloadPipelineArtifact@2 - displayName: Download Test Artifact - inputs: - buildType: 'current' - artifactName: $(artifactName) - targetPath: $(testsFolder) - - bash: | - chmod a+x _tests/ffprobe - displayName: Make ffprobe Executable - - bash: find ${TESTSFOLDER} -name "Radarr.Test.Dummy" -exec chmod a+x {} \; - displayName: Make Test Dummy Executable - condition: and(succeeded(), ne(variables['osName'], 'Windows')) - - bash: | - docker run -d --name=postgres14 \ - -e POSTGRES_PASSWORD=radarr \ - -e POSTGRES_USER=radarr \ - -p 5432:5432/tcp \ - -v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \ - postgres:14 - displayName: Start postgres - - bash: | - chmod a+x ${TESTSFOLDER}/test.sh - ls -lR ${TESTSFOLDER} - ${TESTSFOLDER}/test.sh Linux Unit Test - displayName: Run Tests - - task: PublishTestResults@2 - displayName: Publish Test Results - inputs: - testResultsFormat: 'NUnit' - testResultsFiles: '**/TestResult.xml' - testRunTitle: 'LinuxCore Postgres14 Unit Tests' - failTaskOnFailedTests: true - failTaskOnMissingResultsFile: true - - - job: Unit_LinuxCore_Postgres15 - displayName: Unit Native LinuxCore with Postgres15 Database - dependsOn: Prepare - condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0')) - variables: - pattern: 'Radarr.*.linux-core-x64.tar.gz' - artifactName: linux-x64-tests - Radarr__Postgres__Host: 'localhost' - Radarr__Postgres__Port: '5432' - Radarr__Postgres__User: 'radarr' - Radarr__Postgres__Password: 'radarr' - - pool: - vmImage: ${{ variables.linuxImage }} - - timeoutInMinutes: 10 - - steps: - - task: UseDotNet@2 - displayName: 'Install .net core' - inputs: - version: $(dotnetVersion) - - checkout: none - - task: DownloadPipelineArtifact@2 - displayName: Download Test Artifact - inputs: - buildType: 'current' - artifactName: $(artifactName) - targetPath: $(testsFolder) - - bash: | - chmod a+x _tests/ffprobe - displayName: Make ffprobe Executable - - bash: find ${TESTSFOLDER} -name "Radarr.Test.Dummy" -exec chmod a+x {} \; - displayName: Make Test Dummy Executable - condition: and(succeeded(), ne(variables['osName'], 'Windows')) - - bash: | - docker run -d --name=postgres15 \ - -e POSTGRES_PASSWORD=radarr \ - -e POSTGRES_USER=radarr \ - -p 5432:5432/tcp \ - -v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \ - postgres:15 - displayName: Start postgres - - bash: | - chmod a+x ${TESTSFOLDER}/test.sh - ls -lR ${TESTSFOLDER} - ${TESTSFOLDER}/test.sh Linux Unit Test - displayName: Run Tests - - task: PublishTestResults@2 - displayName: Publish Test Results - inputs: - testResultsFormat: 'NUnit' - testResultsFiles: '**/TestResult.xml' - testRunTitle: 'LinuxCore Postgres15 Unit Tests' - failTaskOnFailedTests: true - failTaskOnMissingResultsFile: true - - - stage: Integration - displayName: Integration - dependsOn: Packages - - jobs: - - job: Prepare - pool: - vmImage: ${{ variables.linuxImage }} - steps: - - checkout: none - - task: DownloadPipelineArtifact@2 - inputs: - buildType: 'current' - artifactName: 'not_backend_update' - targetPath: '.' - - bash: echo "##vso[task.setvariable variable=backendNotUpdated;isOutput=true]$(cat not_backend_update)" - name: setVar - - - job: Integration_Native - displayName: Integration Native - dependsOn: Prepare - condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0')) - strategy: - matrix: - MacCore: - osName: 'Mac' - testName: 'osx-x64' - imageName: ${{ variables.macImage }} - pattern: 'Radarr.*.osx-core-x64.tar.gz' - WindowsCore: - osName: 'Windows' - testName: 'win-x64' - imageName: ${{ variables.windowsImage }} - pattern: 'Radarr.*.windows-core-x64.zip' - LinuxCore: - osName: 'Linux' - testName: 'linux-x64' - imageName: ${{ variables.linuxImage }} - pattern: 'Radarr.*.linux-core-x64.tar.gz' - - pool: - vmImage: $(imageName) - - steps: - - task: UseDotNet@2 - displayName: 'Install .net core' - inputs: - version: $(dotnetVersion) - - checkout: none - - task: DownloadPipelineArtifact@2 - displayName: Download Test Artifact - inputs: - buildType: 'current' - artifactName: '$(testName)-tests' - targetPath: $(testsFolder) - - task: DownloadPipelineArtifact@2 - displayName: Download Build Artifact - inputs: - buildType: 'current' - artifactName: Packages - itemPattern: '**/$(pattern)' - targetPath: $(Build.ArtifactStagingDirectory) - - task: ExtractFiles@1 - inputs: - archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)' - destinationFolder: '$(Build.ArtifactStagingDirectory)/bin' - displayName: Extract Package - - bash: | - mkdir -p ./bin/ - cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Radarr/. ./bin/ - displayName: Move Package Contents - - bash: | - chmod a+x ${TESTSFOLDER}/test.sh - ${TESTSFOLDER}/test.sh ${OSNAME} Integration Test - displayName: Run Integration Tests - - task: PublishTestResults@2 - inputs: - testResultsFormat: 'NUnit' - testResultsFiles: '**/TestResult.xml' - testRunTitle: '$(testName) Integration Tests' - failTaskOnFailedTests: true - failTaskOnMissingResultsFile: true - displayName: Publish Test Results - - - job: Integration_LinuxCore_Postgres14 - displayName: Integration Native LinuxCore with Postgres14 Database - dependsOn: Prepare - condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0')) - variables: - pattern: 'Radarr.*.linux-core-x64.tar.gz' - Radarr__Postgres__Host: 'localhost' - Radarr__Postgres__Port: '5432' - Radarr__Postgres__User: 'radarr' - Radarr__Postgres__Password: 'radarr' - - pool: - vmImage: ${{ variables.linuxImage }} - - steps: - - task: UseDotNet@2 - displayName: 'Install .net core' - inputs: - version: $(dotnetVersion) - - checkout: none - - task: DownloadPipelineArtifact@2 - displayName: Download Test Artifact - inputs: - buildType: 'current' - artifactName: 'linux-x64-tests' - targetPath: $(testsFolder) - - task: DownloadPipelineArtifact@2 - displayName: Download Build Artifact - inputs: - buildType: 'current' - artifactName: Packages - itemPattern: '**/$(pattern)' - targetPath: $(Build.ArtifactStagingDirectory) - - task: ExtractFiles@1 - inputs: - archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)' - destinationFolder: '$(Build.ArtifactStagingDirectory)/bin' - displayName: Extract Package - - bash: | - mkdir -p ./bin/ - cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Radarr/. ./bin/ - displayName: Move Package Contents - - bash: | - docker run -d --name=postgres14 \ - -e POSTGRES_PASSWORD=radarr \ - -e POSTGRES_USER=radarr \ - -p 5432:5432/tcp \ - -v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \ - postgres:14 - displayName: Start postgres - - bash: | - chmod a+x ${TESTSFOLDER}/test.sh - ${TESTSFOLDER}/test.sh Linux Integration Test - displayName: Run Integration Tests - - task: PublishTestResults@2 - inputs: - testResultsFormat: 'NUnit' - testResultsFiles: '**/TestResult.xml' - testRunTitle: 'Integration LinuxCore Postgres14 Database Integration Tests' - failTaskOnFailedTests: true - failTaskOnMissingResultsFile: true - displayName: Publish Test Results - - - - job: Integration_LinuxCore_Postgres15 - displayName: Integration Native LinuxCore with Postgres Database - dependsOn: Prepare - condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0')) - variables: - pattern: 'Radarr.*.linux-core-x64.tar.gz' - Radarr__Postgres__Host: 'localhost' - Radarr__Postgres__Port: '5432' - Radarr__Postgres__User: 'radarr' - Radarr__Postgres__Password: 'radarr' - - pool: - vmImage: ${{ variables.linuxImage }} - - steps: - - task: UseDotNet@2 - displayName: 'Install .net core' - inputs: - version: $(dotnetVersion) - - checkout: none - - task: DownloadPipelineArtifact@2 - displayName: Download Test Artifact - inputs: - buildType: 'current' - artifactName: 'linux-x64-tests' - targetPath: $(testsFolder) - - task: DownloadPipelineArtifact@2 - displayName: Download Build Artifact - inputs: - buildType: 'current' - artifactName: Packages - itemPattern: '**/$(pattern)' - targetPath: $(Build.ArtifactStagingDirectory) - - task: ExtractFiles@1 - inputs: - archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)' - destinationFolder: '$(Build.ArtifactStagingDirectory)/bin' - displayName: Extract Package - - bash: | - mkdir -p ./bin/ - cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Radarr/. ./bin/ - displayName: Move Package Contents - - bash: | - docker run -d --name=postgres15 \ - -e POSTGRES_PASSWORD=radarr \ - -e POSTGRES_USER=radarr \ - -p 5432:5432/tcp \ - -v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \ - postgres:15 - displayName: Start postgres - - bash: | - chmod a+x ${TESTSFOLDER}/test.sh - ${TESTSFOLDER}/test.sh Linux Integration Test - displayName: Run Integration Tests - - task: PublishTestResults@2 - inputs: - testResultsFormat: 'NUnit' - testResultsFiles: '**/TestResult.xml' - testRunTitle: 'Integration LinuxCore Postgres15 Database Integration Tests' - failTaskOnFailedTests: true - failTaskOnMissingResultsFile: true - displayName: Publish Test Results - - - job: Integration_FreeBSD - displayName: Integration Native FreeBSD - dependsOn: Prepare - condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0')) - workspace: - clean: all - variables: - pattern: 'Radarr.*.freebsd-core-x64.tar.gz' - pool: - name: 'FreeBSD' - - steps: - - checkout: none - - task: DownloadPipelineArtifact@2 - displayName: Download Test Artifact - inputs: - buildType: 'current' - artifactName: 'freebsd-x64-tests' - targetPath: $(testsFolder) - - task: DownloadPipelineArtifact@2 - displayName: Download Build Artifact - inputs: - buildType: 'current' - artifactName: Packages - itemPattern: '**/$(pattern)' - targetPath: $(Build.ArtifactStagingDirectory) - - bash: | - mkdir -p ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin - tar xf ${BUILD_ARTIFACTSTAGINGDIRECTORY}/$(pattern) -C ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin - displayName: Extract Package - - bash: | - mkdir -p ./bin/ - cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Radarr/. ./bin/ - displayName: Move Package Contents - - bash: | - chmod a+x ${TESTSFOLDER}/test.sh - ${TESTSFOLDER}/test.sh Linux Integration Test - displayName: Run Integration Tests - - task: PublishTestResults@2 - inputs: - testResultsFormat: 'NUnit' - testResultsFiles: '**/TestResult.xml' - testRunTitle: 'FreeBSD Integration Tests' - failTaskOnFailedTests: true - failTaskOnMissingResultsFile: false - displayName: Publish Test Results - - - job: Integration_Docker - displayName: Integration Docker - dependsOn: Prepare - condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0')) - strategy: - matrix: - alpine: - testName: 'linux-musl-x64' - artifactName: linux-musl-x64-tests - containerImage: ghcr.io/servarr/testimages:alpine - pattern: 'Radarr.*.linux-musl-core-x64.tar.gz' - pool: - vmImage: ${{ variables.linuxImage }} - - container: $[ variables['containerImage'] ] - - timeoutInMinutes: 15 - - steps: - - task: UseDotNet@2 - displayName: 'Install .NET' - inputs: - version: $(dotnetVersion) - - checkout: none - - task: DownloadPipelineArtifact@2 - displayName: Download Test Artifact - inputs: - buildType: 'current' - artifactName: $(artifactName) - targetPath: $(testsFolder) - - task: DownloadPipelineArtifact@2 - displayName: Download Build Artifact - inputs: - buildType: 'current' - artifactName: Packages - itemPattern: '**/$(pattern)' - targetPath: $(Build.ArtifactStagingDirectory) - - task: ExtractFiles@1 - inputs: - archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)' - destinationFolder: '$(Build.ArtifactStagingDirectory)/bin' - displayName: Extract Package - - bash: | - mkdir -p ./bin/ - cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Radarr/. ./bin/ - displayName: Move Package Contents - - bash: | - chmod a+x ${TESTSFOLDER}/test.sh - ${TESTSFOLDER}/test.sh Linux Integration Test - displayName: Run Integration Tests - - task: PublishTestResults@2 - inputs: - testResultsFormat: 'NUnit' - testResultsFiles: '**/TestResult.xml' - testRunTitle: '$(testName) Integration Tests' - failTaskOnFailedTests: true - failTaskOnMissingResultsFile: true - displayName: Publish Test Results - - - stage: Automation - displayName: Automation - dependsOn: Packages - - jobs: - - job: Automation - strategy: - matrix: - Linux: - osName: 'Linux' - artifactName: 'linux-x64' - imageName: ${{ variables.linuxImage }} - pattern: 'Radarr.*.linux-core-x64.tar.gz' - failBuild: true - Mac: - osName: 'Mac' - artifactName: 'osx-x64' - imageName: ${{ variables.macImage }} - pattern: 'Radarr.*.osx-core-x64.tar.gz' - failBuild: true - Windows: - osName: 'Windows' - artifactName: 'win-x64' - imageName: ${{ variables.windowsImage }} - pattern: 'Radarr.*.windows-core-x64.zip' - failBuild: true - - pool: - vmImage: $(imageName) - - steps: - - task: UseDotNet@2 - displayName: 'Install .net core' - inputs: - version: $(dotnetVersion) - - checkout: none - - task: DownloadPipelineArtifact@2 - displayName: Download Test Artifact - inputs: - buildType: 'current' - artifactName: '$(artifactName)-tests' - targetPath: $(testsFolder) - - task: DownloadPipelineArtifact@2 - displayName: Download Build Artifact - inputs: - buildType: 'current' - artifactName: Packages - itemPattern: '**/$(pattern)' - targetPath: $(Build.ArtifactStagingDirectory) - - task: ExtractFiles@1 - inputs: - archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)' - destinationFolder: '$(Build.ArtifactStagingDirectory)/bin' - displayName: Extract Package - - bash: | - mkdir -p ./bin/ - cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Radarr/. ./bin/ - displayName: Move Package Contents - - bash: | - chmod a+x ${TESTSFOLDER}/test.sh - ${TESTSFOLDER}/test.sh ${OSNAME} Automation Test - displayName: Run Automation Tests - - task: CopyFiles@2 - displayName: 'Copy Screenshot to: $(Build.ArtifactStagingDirectory)' - inputs: - SourceFolder: '$(Build.SourcesDirectory)' - Contents: | - **/*_test_screenshot.png - TargetFolder: '$(Build.ArtifactStagingDirectory)/screenshots' - - publish: $(Build.ArtifactStagingDirectory)/screenshots - artifact: '$(osName)AutomationScreenshots' - displayName: Publish Screenshot Bundle - condition: and(succeeded(), eq(variables['System.JobAttempt'], '1')) - - task: PublishTestResults@2 - inputs: - testResultsFormat: 'NUnit' - testResultsFiles: '**/TestResult.xml' - testRunTitle: '$(osName) Automation Tests' - failTaskOnFailedTests: $(failBuild) - failTaskOnMissingResultsFile: $(failBuild) - displayName: Publish Test Results - - - stage: Analyze - dependsOn: - - Setup - displayName: Analyze - - jobs: - - job: Prepare - pool: - vmImage: ${{ variables.linuxImage }} - steps: - - checkout: none - - task: DownloadPipelineArtifact@2 - inputs: - buildType: 'current' - artifactName: 'not_backend_update' - targetPath: '.' - - bash: echo "##vso[task.setvariable variable=backendNotUpdated;isOutput=true]$(cat not_backend_update)" - name: setVar - - - job: Lint_Frontend - displayName: Lint Frontend - strategy: - matrix: - Linux: - osName: 'Linux' - imageName: ${{ variables.linuxImage }} - Windows: - osName: 'Windows' - imageName: ${{ variables.windowsImage }} - pool: - vmImage: $(imageName) - steps: - - task: UseNode@1 - displayName: Set Node.js version - inputs: - version: $(nodeVersion) - - checkout: self - submodules: true - fetchDepth: 1 - - task: Cache@2 - inputs: - key: 'yarn | "$(osName)" | yarn.lock' - restoreKeys: | - yarn | "$(osName)" - path: $(yarnCacheFolder) - displayName: Cache Yarn packages - - bash: ./build.sh --lint - displayName: Lint Radarr Frontend - env: - FORCE_COLOR: 0 - YARN_CACHE_FOLDER: $(yarnCacheFolder) - - - job: Analyze_Frontend - displayName: Frontend - condition: eq(variables['System.PullRequest.IsFork'], 'False') - pool: - vmImage: ${{ variables.windowsImage }} - steps: - - checkout: self # Need history for Sonar analysis - - task: SonarCloudPrepare@3 - env: - SONAR_SCANNER_OPTS: '' - inputs: - SonarCloud: 'SonarCloud' - organization: 'radarr' - scannerMode: 'cli' - configMode: 'manual' - cliProjectKey: 'Radarr_Radarr.UI' - cliProjectName: 'RadarrUI' - cliProjectVersion: '$(radarrVersion)' - cliSources: './frontend' - - task: SonarCloudAnalyze@3 - - - job: Api_Docs - displayName: API Docs - dependsOn: Prepare - condition: | - and - ( - and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop')), - and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0')) - ) - - pool: - vmImage: ${{ variables.windowsImage }} - - steps: - - task: UseDotNet@2 - displayName: 'Install .net core' - inputs: - version: $(dotnetVersion) - - checkout: self - submodules: true - persistCredentials: true - fetchDepth: 1 - - bash: ./docs.sh Windows - displayName: Create openapi.json - - bash: | - git config --global user.email "development@lidarr.audio" - git config --global user.name "Servarr" - git checkout -b api-docs - git add . - git status - if git status | grep modified - then - git commit -am 'Automated API Docs update' - git push -f --set-upstream origin api-docs - curl -X POST -H "Authorization: token ${GITHUBTOKEN}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/radarr/radarr/pulls -d '{"head":"api-docs","base":"develop","title":"Update API docs"}' - else - echo "No changes since last run" - fi - displayName: Commit API Doc Change - continueOnError: true - env: - GITHUBTOKEN: $(githubToken) - - task: CopyFiles@2 - displayName: 'Copy openapi.json to: $(Build.ArtifactStagingDirectory)' - inputs: - SourceFolder: '$(Build.SourcesDirectory)' - Contents: | - **/*openapi.json - TargetFolder: '$(Build.ArtifactStagingDirectory)/api_docs' - - publish: $(Build.ArtifactStagingDirectory)/api_docs - artifact: 'APIDocs' - displayName: Publish API Docs Bundle - condition: and(succeeded(), eq(variables['System.JobAttempt'], '1')) - - - job: Analyze_Backend - displayName: Backend - dependsOn: Prepare - condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0')) - - variables: - disable.coverage.autogenerate: 'true' - EnableAnalyzers: 'false' - - pool: - vmImage: ${{ variables.windowsImage }} - - steps: - - task: UseDotNet@2 - displayName: 'Install .net core' - inputs: - version: $(dotnetVersion) - - checkout: self # Need history for Sonar analysis - submodules: true - - powershell: Set-Service SCardSvr -StartupType Manual - displayName: Enable Windows Test Service - - task: SonarCloudPrepare@3 - condition: eq(variables['System.PullRequest.IsFork'], 'False') - inputs: - SonarCloud: 'SonarCloud' - organization: 'radarr' - scannerMode: 'dotnet' - projectKey: 'Radarr_Radarr' - projectName: 'Radarr' - projectVersion: '$(radarrVersion)' - extraProperties: | - sonar.exclusions=**/obj/**,**/*.dll,**/NzbDrone.Core.Test/Files/**/*,./frontend/**,**/ExternalModules/**,./src/Libraries/** - sonar.coverage.exclusions=**/Radarr.Api.V3/**/* - sonar.cs.cobertura.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.cobertura.xml - sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml - - bash: | - ./build.sh --backend -f net8.0 -r win-x64 - TEST_DIR=_tests/net8.0/win-x64/publish/ ./test.sh Windows Unit Coverage - displayName: Coverage Unit Tests - - task: SonarCloudAnalyze@3 - condition: eq(variables['System.PullRequest.IsFork'], 'False') - displayName: Publish SonarCloud Results - - task: reportgenerator@5 - displayName: Generate Coverage Report - inputs: - reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.cobertura.xml' - targetdir: '$(Build.SourcesDirectory)/CoverageResults/combined' - reporttypes: 'HtmlInline_AzurePipelines;Cobertura;Badges' - publishCodeCoverageResults: true - sourcedirs: src - - - stage: Report_Out - dependsOn: - - Analyze - - Installer - - Unit_Test - - Integration - - Automation - condition: eq(variables['system.pullrequest.isfork'], false) - displayName: Build Status Report - jobs: - - job: - displayName: Discord Notification - pool: - vmImage: ${{ variables.linuxImage }} - steps: - - task: DownloadPipelineArtifact@2 - continueOnError: true - displayName: Download Screenshot Artifact - inputs: - buildType: 'current' - artifactName: 'WindowsAutomationScreenshots' - targetPath: $(Build.SourcesDirectory) - - checkout: none - - pwsh: | - iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/Servarr/AzureDiscordNotify/master/DiscordNotify.ps1')) - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - DISCORDCHANNELID: $(discordChannelId) - DISCORDWEBHOOKKEY: $(discordWebhookKey) - DISCORDTHREADID: $(discordThreadId) diff --git a/frontend/.vscode/extensions.json b/frontend/.vscode/extensions.json deleted file mode 100644 index 0e005a3cd8..0000000000 --- a/frontend/.vscode/extensions.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "recommendations": [ - "stylelint.vscode-stylelint", - "dbaeumer.vscode-eslint", - "esbenp.prettier-vscode" - ] -} \ No newline at end of file diff --git a/frontend/.vscode/settings.json b/frontend/.vscode/settings.json deleted file mode 100644 index 8da95337f6..0000000000 --- a/frontend/.vscode/settings.json +++ /dev/null @@ -1,23 +0,0 @@ -// Place your settings in this file to overwrite default and user settings. -{ - "files.insertFinalNewline": true, - - "files.exclude": { - "**/node_modules": true, - "**/*.d.css": true - }, - - "editor.formatOnSave": false, - "editor.codeActionsOnSave": { - "source.fixAll": "explicit" - }, - - "typescript.preferences.quoteStyle": "single", - - "eslint.validate": [ - "javascript", - "javascriptreact", - "typescript", - "typescriptreact" - ], -} diff --git a/package.json b/package.json index f3d02bcdc6..56618bffe9 100644 --- a/package.json +++ b/package.json @@ -55,8 +55,6 @@ "prop-types": "15.8.1", "qs": "6.13.0", "react": "18.3.1", - "react-addons-shallow-compare": "15.6.3", - "react-async-script": "1.2.0", "react-autosuggest": "10.1.0", "react-custom-scrollbars-2": "4.5.0", "react-dnd": "14.0.4", diff --git a/src/.idea/.idea.NzbDrone/.idea/.name b/src/.idea/.idea.NzbDrone/.idea/.name deleted file mode 100644 index 37baec0ab3..0000000000 --- a/src/.idea/.idea.NzbDrone/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -NzbDrone \ No newline at end of file diff --git a/src/.idea/.idea.NzbDrone/.idea/encodings.xml b/src/.idea/.idea.NzbDrone/.idea/encodings.xml deleted file mode 100644 index 15a15b218a..0000000000 --- a/src/.idea/.idea.NzbDrone/.idea/encodings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/.idea/.idea.NzbDrone/.idea/indexLayout.xml b/src/.idea/.idea.NzbDrone/.idea/indexLayout.xml deleted file mode 100644 index 27ba142e96..0000000000 --- a/src/.idea/.idea.NzbDrone/.idea/indexLayout.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/.idea/.idea.NzbDrone/.idea/misc.xml b/src/.idea/.idea.NzbDrone/.idea/misc.xml deleted file mode 100644 index 1d8c84d0af..0000000000 --- a/src/.idea/.idea.NzbDrone/.idea/misc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/.idea/.idea.NzbDrone/.idea/vcs.xml b/src/.idea/.idea.NzbDrone/.idea/vcs.xml deleted file mode 100644 index ea6ca8297f..0000000000 --- a/src/.idea/.idea.NzbDrone/.idea/vcs.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/NzbDrone.Console/ConsoleApp.cs b/src/NzbDrone.Console/ConsoleApp.cs index 65c22338ee..6107e22554 100644 --- a/src/NzbDrone.Console/ConsoleApp.cs +++ b/src/NzbDrone.Console/ConsoleApp.cs @@ -53,7 +53,7 @@ public static void Main(string[] args) { System.Console.WriteLine(""); System.Console.WriteLine(""); - Logger.Fatal(ex.Message + " This can happen if another instance of Radarr is already running another application is using the same port (default: 7878) or the user has insufficient permissions"); + Logger.Fatal(ex.Message + " This can happen if another instance of Aletheia is already running another application is using the same port (default: 7878) or the user has insufficient permissions"); Exit(ExitCodes.RecoverableFailure, startupArgs); } catch (IOException ex) @@ -62,7 +62,7 @@ public static void Main(string[] args) { System.Console.WriteLine(""); System.Console.WriteLine(""); - Logger.Fatal(ex.Message + " This can happen if another instance of Radarr is already running another application is using the same port (default: 7878) or the user has insufficient permissions"); + Logger.Fatal(ex.Message + " This can happen if another instance of Aletheia is already running another application is using the same port (default: 7878) or the user has insufficient permissions"); Exit(ExitCodes.RecoverableFailure, startupArgs); } else diff --git a/src/NzbDrone.Core/Audiobooks/AudiobookService.cs b/src/NzbDrone.Core/Audiobooks/AudiobookService.cs index c6c1ed2d37..fa949c88c5 100644 --- a/src/NzbDrone.Core/Audiobooks/AudiobookService.cs +++ b/src/NzbDrone.Core/Audiobooks/AudiobookService.cs @@ -1,17 +1,16 @@ using System; using System.Collections.Generic; -using System.Linq; using NzbDrone.Core.Audiobooks.Events; using NzbDrone.Core.Datastore; +using NzbDrone.Core.MediaItems; using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.Audiobooks { - public interface IAudiobookService + public interface IAudiobookService : IBaseMediaService { Audiobook GetAudiobook(int audiobookId); List GetAudiobooks(IEnumerable audiobookIds); - PagingSpec Paged(PagingSpec pagingSpec); Audiobook AddAudiobook(Audiobook newAudiobook); List AddAudiobooks(List newAudiobooks); Audiobook FindByIsbn(string isbn); @@ -34,159 +33,61 @@ public interface IAudiobookService bool AudiobookPathExists(string folder); } - public class AudiobookService : IAudiobookService + public class AudiobookService : BaseMediaService, IAudiobookService { private readonly IAudiobookRepository _audiobookRepository; private readonly IEventAggregator _eventAggregator; - public AudiobookService(IAudiobookRepository audiobookRepository, - IEventAggregator eventAggregator) + public AudiobookService(IAudiobookRepository audiobookRepository, IEventAggregator eventAggregator) { _audiobookRepository = audiobookRepository; _eventAggregator = eventAggregator; } - public Audiobook GetAudiobook(int audiobookId) - { - return _audiobookRepository.Get(audiobookId); - } + protected override IBasicRepository Repository => _audiobookRepository; + protected override IEventAggregator EventAggregator => _eventAggregator; - public List GetAudiobooks(IEnumerable audiobookIds) - { - return _audiobookRepository.Get(audiobookIds).ToList(); - } - - public PagingSpec Paged(PagingSpec pagingSpec) - { - return _audiobookRepository.GetPaged(pagingSpec); - } - - public Audiobook AddAudiobook(Audiobook newAudiobook) - { - newAudiobook.Added = DateTime.UtcNow; - var audiobook = _audiobookRepository.Insert(newAudiobook); - - _eventAggregator.PublishEvent(new AudiobookAddedEvent(GetAudiobook(audiobook.Id))); - - return audiobook; - } - - public List AddAudiobooks(List newAudiobooks) - { - var now = DateTime.UtcNow; - foreach (var audiobook in newAudiobooks) - { - audiobook.Added = now; - } - - _audiobookRepository.InsertMany(newAudiobooks); - - _eventAggregator.PublishEvent(new AudiobooksImportedEvent(newAudiobooks)); - - return newAudiobooks; - } - - public Audiobook FindByIsbn(string isbn) - { - return _audiobookRepository.FindByIsbn(isbn); - } - - public Audiobook FindByIsbn13(string isbn13) - { - return _audiobookRepository.FindByIsbn13(isbn13); - } - - public Audiobook FindByAsin(string asin) - { - return _audiobookRepository.FindByAsin(asin); - } - - public Audiobook FindByForeignId(string foreignAudiobookId) - { - return _audiobookRepository.FindByForeignId(foreignAudiobookId); - } - - public Audiobook FindByPath(string path) - { - return _audiobookRepository.FindByPath(path); - } - - public List FindByAuthorId(int authorId) - { - return _audiobookRepository.FindByAuthorId(authorId); - } - - public List FindBySeriesId(int seriesId) - { - return _audiobookRepository.FindBySeriesId(seriesId); - } - - public List FindByBookId(int bookId) - { - return _audiobookRepository.FindByBookId(bookId); - } - - public List FindByNarrator(string narrator) - { - return _audiobookRepository.FindByNarrator(narrator); - } - - public Dictionary AllAudiobookPaths() - { - return _audiobookRepository.AllAudiobookPaths(); - } + public Audiobook GetAudiobook(int audiobookId) => Get(audiobookId); + public List GetAudiobooks(IEnumerable audiobookIds) => Get(audiobookIds); + public Audiobook AddAudiobook(Audiobook newAudiobook) => Add(newAudiobook); + public List AddAudiobooks(List newAudiobooks) => AddMany(newAudiobooks); + public void DeleteAudiobook(int audiobookId, bool deleteFiles) => Delete(audiobookId, deleteFiles); + public void DeleteAudiobooks(List audiobookIds, bool deleteFiles) => DeleteMany(audiobookIds, deleteFiles); + public List GetAllAudiobooks() => GetAll(); + public Audiobook UpdateAudiobook(Audiobook audiobook) => Update(audiobook); + public List UpdateAudiobooks(List audiobooks) => UpdateMany(audiobooks); + public Audiobook FindByIsbn(string isbn) => _audiobookRepository.FindByIsbn(isbn); + public Audiobook FindByIsbn13(string isbn13) => _audiobookRepository.FindByIsbn13(isbn13); + public Audiobook FindByAsin(string asin) => _audiobookRepository.FindByAsin(asin); + public Audiobook FindByForeignId(string foreignAudiobookId) => _audiobookRepository.FindByForeignId(foreignAudiobookId); + public Audiobook FindByPath(string path) => _audiobookRepository.FindByPath(path); + public List FindByAuthorId(int authorId) => _audiobookRepository.FindByAuthorId(authorId); + public List FindBySeriesId(int seriesId) => _audiobookRepository.FindBySeriesId(seriesId); + public List FindByBookId(int bookId) => _audiobookRepository.FindByBookId(bookId); + public List FindByNarrator(string narrator) => _audiobookRepository.FindByNarrator(narrator); + public Dictionary AllAudiobookPaths() => _audiobookRepository.AllAudiobookPaths(); public List GetAudiobooksBetweenDates(DateTime start, DateTime end, bool includeUnmonitored) - { - return _audiobookRepository.AudiobooksBetweenDates(start, end, includeUnmonitored); - } + => _audiobookRepository.AudiobooksBetweenDates(start, end, includeUnmonitored); + public Dictionary> AllAudiobookTags() => _audiobookRepository.AllAudiobookTags(); + public bool AudiobookPathExists(string folder) => _audiobookRepository.AudiobookPathExists(folder); - public void DeleteAudiobook(int audiobookId, bool deleteFiles) - { - var audiobook = _audiobookRepository.Get(audiobookId); - _audiobookRepository.Delete(audiobookId); - _eventAggregator.PublishEvent(new AudiobookDeletedEvent(audiobook, deleteFiles)); - } + protected override void OnItemAdded(Audiobook item) + => _eventAggregator.PublishEvent(new AudiobookAddedEvent(item)); - public void DeleteAudiobooks(List audiobookIds, bool deleteFiles) - { - var audiobooks = _audiobookRepository.Get(audiobookIds).ToList(); - _audiobookRepository.DeleteMany(audiobookIds); - _eventAggregator.PublishEvent(new AudiobooksDeletedEvent(audiobooks, deleteFiles)); - } + protected override void OnItemsImported(List items) + => _eventAggregator.PublishEvent(new AudiobooksImportedEvent(items)); - public List GetAllAudiobooks() - { - return _audiobookRepository.All().ToList(); - } + protected override void OnItemDeleted(Audiobook item, bool deleteFiles) + => _eventAggregator.PublishEvent(new AudiobookDeletedEvent(item, deleteFiles)); - public Dictionary> AllAudiobookTags() - { - return _audiobookRepository.AllAudiobookTags(); - } + protected override void OnItemsDeleted(List items, bool deleteFiles) + => _eventAggregator.PublishEvent(new AudiobooksDeletedEvent(items, deleteFiles)); - public Audiobook UpdateAudiobook(Audiobook audiobook) - { - var storedAudiobook = GetAudiobook(audiobook.Id); - var updatedAudiobook = _audiobookRepository.Update(audiobook); + protected override void OnItemEdited(Audiobook updated, Audiobook stored) + => _eventAggregator.PublishEvent(new AudiobookEditedEvent(updated, stored)); - _eventAggregator.PublishEvent(new AudiobookEditedEvent(updatedAudiobook, storedAudiobook)); - - return updatedAudiobook; - } - - public List UpdateAudiobooks(List audiobooks) - { - _audiobookRepository.UpdateMany(audiobooks); - - _eventAggregator.PublishEvent(new AudiobooksBulkEditedEvent(audiobooks)); - - return audiobooks; - } - - public bool AudiobookPathExists(string folder) - { - return _audiobookRepository.AudiobookPathExists(folder); - } + protected override void OnItemsBulkEdited(List items) + => _eventAggregator.PublishEvent(new AudiobooksBulkEditedEvent(items)); } } diff --git a/src/NzbDrone.Core/Books/BookService.cs b/src/NzbDrone.Core/Books/BookService.cs index 2f779f5baa..14e5e9ece1 100644 --- a/src/NzbDrone.Core/Books/BookService.cs +++ b/src/NzbDrone.Core/Books/BookService.cs @@ -1,17 +1,16 @@ using System; using System.Collections.Generic; -using System.Linq; using NzbDrone.Core.Books.Events; using NzbDrone.Core.Datastore; +using NzbDrone.Core.MediaItems; using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.Books { - public interface IBookService + public interface IBookService : IBaseMediaService { Book GetBook(int bookId); List GetBooks(IEnumerable bookIds); - PagingSpec Paged(PagingSpec pagingSpec); Book AddBook(Book newBook); List AddBooks(List newBooks); Book FindByIsbn(string isbn); @@ -32,149 +31,59 @@ public interface IBookService bool BookPathExists(string folder); } - public class BookService : IBookService + public class BookService : BaseMediaService, IBookService { private readonly IBookRepository _bookRepository; private readonly IEventAggregator _eventAggregator; - public BookService(IBookRepository bookRepository, - IEventAggregator eventAggregator) + public BookService(IBookRepository bookRepository, IEventAggregator eventAggregator) { _bookRepository = bookRepository; _eventAggregator = eventAggregator; } - public Book GetBook(int bookId) - { - return _bookRepository.Get(bookId); - } + protected override IBasicRepository Repository => _bookRepository; + protected override IEventAggregator EventAggregator => _eventAggregator; - public List GetBooks(IEnumerable bookIds) - { - return _bookRepository.Get(bookIds).ToList(); - } - - public PagingSpec Paged(PagingSpec pagingSpec) - { - return _bookRepository.GetPaged(pagingSpec); - } - - public Book AddBook(Book newBook) - { - newBook.Added = DateTime.UtcNow; - var book = _bookRepository.Insert(newBook); - - _eventAggregator.PublishEvent(new BookAddedEvent(GetBook(book.Id))); - - return book; - } - - public List AddBooks(List newBooks) - { - var now = DateTime.UtcNow; - foreach (var book in newBooks) - { - book.Added = now; - } - - _bookRepository.InsertMany(newBooks); - - _eventAggregator.PublishEvent(new BooksImportedEvent(newBooks)); - - return newBooks; - } - - public Book FindByIsbn(string isbn) - { - return _bookRepository.FindByIsbn(isbn); - } - - public Book FindByIsbn13(string isbn13) - { - return _bookRepository.FindByIsbn13(isbn13); - } - - public Book FindByAsin(string asin) - { - return _bookRepository.FindByAsin(asin); - } - - public Book FindByForeignId(string foreignBookId) - { - return _bookRepository.FindByForeignId(foreignBookId); - } - - public Book FindByPath(string path) - { - return _bookRepository.FindByPath(path); - } - - public List FindByAuthorId(int authorId) - { - return _bookRepository.FindByAuthorId(authorId); - } - - public List FindBySeriesId(int seriesId) - { - return _bookRepository.FindBySeriesId(seriesId); - } - - public Dictionary AllBookPaths() - { - return _bookRepository.AllBookPaths(); - } + public Book GetBook(int bookId) => Get(bookId); + public List GetBooks(IEnumerable bookIds) => Get(bookIds); + public Book AddBook(Book newBook) => Add(newBook); + public List AddBooks(List newBooks) => AddMany(newBooks); + public void DeleteBook(int bookId, bool deleteFiles) => Delete(bookId, deleteFiles); + public void DeleteBooks(List bookIds, bool deleteFiles) => DeleteMany(bookIds, deleteFiles); + public List GetAllBooks() => GetAll(); + public Book UpdateBook(Book book) => Update(book); + public List UpdateBooks(List books) => UpdateMany(books); + public Book FindByIsbn(string isbn) => _bookRepository.FindByIsbn(isbn); + public Book FindByIsbn13(string isbn13) => _bookRepository.FindByIsbn13(isbn13); + public Book FindByAsin(string asin) => _bookRepository.FindByAsin(asin); + public Book FindByForeignId(string foreignBookId) => _bookRepository.FindByForeignId(foreignBookId); + public Book FindByPath(string path) => _bookRepository.FindByPath(path); + public List FindByAuthorId(int authorId) => _bookRepository.FindByAuthorId(authorId); + public List FindBySeriesId(int seriesId) => _bookRepository.FindBySeriesId(seriesId); + public Dictionary AllBookPaths() => _bookRepository.AllBookPaths(); public List GetBooksBetweenDates(DateTime start, DateTime end, bool includeUnmonitored) - { - return _bookRepository.BooksBetweenDates(start, end, includeUnmonitored); - } + => _bookRepository.BooksBetweenDates(start, end, includeUnmonitored); + public Dictionary> AllBookTags() => _bookRepository.AllBookTags(); + public bool BookPathExists(string folder) => _bookRepository.BookPathExists(folder); - public void DeleteBook(int bookId, bool deleteFiles) - { - var book = _bookRepository.Get(bookId); - _bookRepository.Delete(bookId); - _eventAggregator.PublishEvent(new BookDeletedEvent(book, deleteFiles)); - } + protected override void OnItemAdded(Book item) + => _eventAggregator.PublishEvent(new BookAddedEvent(item)); - public void DeleteBooks(List bookIds, bool deleteFiles) - { - var books = _bookRepository.Get(bookIds).ToList(); - _bookRepository.DeleteMany(bookIds); - _eventAggregator.PublishEvent(new BooksDeletedEvent(books, deleteFiles)); - } + protected override void OnItemsImported(List items) + => _eventAggregator.PublishEvent(new BooksImportedEvent(items)); - public List GetAllBooks() - { - return _bookRepository.All().ToList(); - } + protected override void OnItemDeleted(Book item, bool deleteFiles) + => _eventAggregator.PublishEvent(new BookDeletedEvent(item, deleteFiles)); - public Dictionary> AllBookTags() - { - return _bookRepository.AllBookTags(); - } + protected override void OnItemsDeleted(List items, bool deleteFiles) + => _eventAggregator.PublishEvent(new BooksDeletedEvent(items, deleteFiles)); - public Book UpdateBook(Book book) - { - var storedBook = GetBook(book.Id); - var updatedBook = _bookRepository.Update(book); + protected override void OnItemEdited(Book updated, Book stored) + => _eventAggregator.PublishEvent(new BookEditedEvent(updated, stored)); - _eventAggregator.PublishEvent(new BookEditedEvent(updatedBook, storedBook)); - - return updatedBook; - } - - public List UpdateBooks(List books) - { - _bookRepository.UpdateMany(books); - - _eventAggregator.PublishEvent(new BooksBulkEditedEvent(books)); - - return books; - } - - public bool BookPathExists(string folder) - { - return _bookRepository.BookPathExists(folder); - } + protected override void OnItemsBulkEdited(List items) + => _eventAggregator.PublishEvent(new BooksBulkEditedEvent(items)); } } diff --git a/src/NzbDrone.Core/Localization/Core/bs.json b/src/NzbDrone.Core/Localization/Core/bs.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/src/NzbDrone.Core/Localization/Core/bs.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/src/NzbDrone.Core/Localization/Core/es_MX.json b/src/NzbDrone.Core/Localization/Core/es_MX.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/src/NzbDrone.Core/Localization/Core/es_MX.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/src/NzbDrone.Core/Localization/Core/et.json b/src/NzbDrone.Core/Localization/Core/et.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/src/NzbDrone.Core/Localization/Core/et.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/src/NzbDrone.Core/Localization/Core/lt.json b/src/NzbDrone.Core/Localization/Core/lt.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/src/NzbDrone.Core/Localization/Core/lt.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/src/NzbDrone.Core/Localization/Core/sr.json b/src/NzbDrone.Core/Localization/Core/sr.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/src/NzbDrone.Core/Localization/Core/sr.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/src/NzbDrone.Core/Localization/Core/ta.json b/src/NzbDrone.Core/Localization/Core/ta.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/src/NzbDrone.Core/Localization/Core/ta.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/src/NzbDrone.Core/MediaItems/BaseMediaService.cs b/src/NzbDrone.Core/MediaItems/BaseMediaService.cs new file mode 100644 index 0000000000..3492df9a94 --- /dev/null +++ b/src/NzbDrone.Core/MediaItems/BaseMediaService.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging.Events; + +namespace NzbDrone.Core.MediaItems +{ + public interface IBaseMediaService where T : ModelBase + { + T Get(int id); + List Get(IEnumerable ids); + PagingSpec Paged(PagingSpec pagingSpec); + T Add(T item); + List AddMany(List items); + void Delete(int id, bool deleteFiles); + void DeleteMany(List ids, bool deleteFiles); + List GetAll(); + T Update(T item); + List UpdateMany(List items); + } + + public abstract class BaseMediaService : IBaseMediaService where T : ModelBase + { + protected abstract IBasicRepository Repository { get; } + protected virtual IEventAggregator EventAggregator => null; + + public T Get(int id) + { + return Repository.Get(id); + } + + public List Get(IEnumerable ids) + { + return Repository.Get(ids).ToList(); + } + + public PagingSpec Paged(PagingSpec pagingSpec) + { + return Repository.GetPaged(pagingSpec); + } + + public virtual T Add(T item) + { + SetAddedTimestamp(item); + var inserted = Repository.Insert(item); + OnItemAdded(Get(inserted.Id)); + return inserted; + } + + public virtual List AddMany(List items) + { + var now = DateTime.UtcNow; + foreach (var item in items) + { + SetAddedTimestamp(item, now); + } + + Repository.InsertMany(items); + OnItemsImported(items); + return items; + } + + public virtual void Delete(int id, bool deleteFiles) + { + var item = Repository.Get(id); + Repository.Delete(id); + OnItemDeleted(item, deleteFiles); + } + + public virtual void DeleteMany(List ids, bool deleteFiles) + { + var items = Repository.Get(ids).ToList(); + Repository.DeleteMany(ids); + OnItemsDeleted(items, deleteFiles); + } + + public List GetAll() + { + return Repository.All().ToList(); + } + + public virtual T Update(T item) + { + var stored = Get(item.Id); + var updated = Repository.Update(item); + OnItemEdited(updated, stored); + return updated; + } + + public virtual List UpdateMany(List items) + { + Repository.UpdateMany(items); + OnItemsBulkEdited(items); + return items; + } + + protected virtual void SetAddedTimestamp(T item, DateTime? timestamp = null) + { + var ts = timestamp ?? DateTime.UtcNow; + + if (item is MediaItem mediaItem) + { + mediaItem.Added = ts; + return; + } + + var addedProperty = item.GetType().GetProperty("Added"); + if (addedProperty != null && addedProperty.PropertyType == typeof(DateTime) && addedProperty.CanWrite) + { + addedProperty.SetValue(item, ts); + } + } + + protected virtual void OnItemAdded(T item) { } + protected virtual void OnItemsImported(List items) { } + protected virtual void OnItemDeleted(T item, bool deleteFiles) { } + protected virtual void OnItemsDeleted(List items, bool deleteFiles) { } + protected virtual void OnItemEdited(T updated, T stored) { } + protected virtual void OnItemsBulkEdited(List items) { } + } +} diff --git a/src/NzbDrone.Core/Music/AlbumService.cs b/src/NzbDrone.Core/Music/AlbumService.cs index 7edc4cf132..791e4adf3a 100644 --- a/src/NzbDrone.Core/Music/AlbumService.cs +++ b/src/NzbDrone.Core/Music/AlbumService.cs @@ -1,15 +1,14 @@ using System; using System.Collections.Generic; -using System.Linq; using NzbDrone.Core.Datastore; +using NzbDrone.Core.MediaItems; namespace NzbDrone.Core.Music { - public interface IAlbumService + public interface IAlbumService : IBaseMediaService { Album GetAlbum(int albumId); List GetAlbums(IEnumerable albumIds); - PagingSpec Paged(PagingSpec pagingSpec); Album AddAlbum(Album newAlbum); List AddAlbums(List newAlbums); Album FindByForeignId(string foreignAlbumId); @@ -26,7 +25,7 @@ public interface IAlbumService bool AlbumPathExists(string folder); } - public class AlbumService : IAlbumService + public class AlbumService : BaseMediaService, IAlbumService { private readonly IAlbumRepository _albumRepository; @@ -35,98 +34,25 @@ public AlbumService(IAlbumRepository albumRepository) _albumRepository = albumRepository; } - public Album GetAlbum(int albumId) - { - return _albumRepository.Get(albumId); - } + protected override IBasicRepository Repository => _albumRepository; - public List GetAlbums(IEnumerable albumIds) - { - return _albumRepository.Get(albumIds).ToList(); - } - - public PagingSpec Paged(PagingSpec pagingSpec) - { - return _albumRepository.GetPaged(pagingSpec); - } - - public Album AddAlbum(Album newAlbum) - { - newAlbum.Added = DateTime.UtcNow; - return _albumRepository.Insert(newAlbum); - } - - public List AddAlbums(List newAlbums) - { - var now = DateTime.UtcNow; - foreach (var album in newAlbums) - { - album.Added = now; - } - - _albumRepository.InsertMany(newAlbums); - return newAlbums; - } - - public Album FindByForeignId(string foreignAlbumId) - { - return _albumRepository.FindByForeignId(foreignAlbumId); - } - - public Album FindByPath(string path) - { - return _albumRepository.FindByPath(path); - } - - public List FindByArtistId(int artistId) - { - return _albumRepository.FindByArtistId(artistId); - } - - public Dictionary AllAlbumPaths() - { - return _albumRepository.AllAlbumPaths(); - } + public Album GetAlbum(int albumId) => Get(albumId); + public List GetAlbums(IEnumerable albumIds) => Get(albumIds); + public Album AddAlbum(Album newAlbum) => Add(newAlbum); + public List AddAlbums(List newAlbums) => AddMany(newAlbums); + public void DeleteAlbum(int albumId, bool deleteFiles) => Delete(albumId, deleteFiles); + public void DeleteAlbums(List albumIds, bool deleteFiles) => DeleteMany(albumIds, deleteFiles); + public List GetAllAlbums() => GetAll(); + public Album UpdateAlbum(Album album) => Update(album); + public List UpdateAlbums(List albums) => UpdateMany(albums); + public Album FindByForeignId(string foreignAlbumId) => _albumRepository.FindByForeignId(foreignAlbumId); + public Album FindByPath(string path) => _albumRepository.FindByPath(path); + public List FindByArtistId(int artistId) => _albumRepository.FindByArtistId(artistId); + public Dictionary AllAlbumPaths() => _albumRepository.AllAlbumPaths(); public List GetAlbumsBetweenDates(DateTime start, DateTime end, bool includeUnmonitored) - { - return _albumRepository.AlbumsBetweenDates(start, end, includeUnmonitored); - } - - public void DeleteAlbum(int albumId, bool deleteFiles) - { - _albumRepository.Delete(albumId); - } - - public void DeleteAlbums(List albumIds, bool deleteFiles) - { - _albumRepository.DeleteMany(albumIds); - } - - public List GetAllAlbums() - { - return _albumRepository.All().ToList(); - } - - public Dictionary> AllAlbumTags() - { - return _albumRepository.AllAlbumTags(); - } - - public Album UpdateAlbum(Album album) - { - return _albumRepository.Update(album); - } - - public List UpdateAlbums(List albums) - { - _albumRepository.UpdateMany(albums); - return albums; - } - - public bool AlbumPathExists(string folder) - { - return _albumRepository.AlbumPathExists(folder); - } + => _albumRepository.AlbumsBetweenDates(start, end, includeUnmonitored); + public Dictionary> AllAlbumTags() => _albumRepository.AllAlbumTags(); + public bool AlbumPathExists(string folder) => _albumRepository.AlbumPathExists(folder); } } diff --git a/src/NzbDrone.Core/Music/ArtistService.cs b/src/NzbDrone.Core/Music/ArtistService.cs index 21aed54b5d..a2b5c44469 100644 --- a/src/NzbDrone.Core/Music/ArtistService.cs +++ b/src/NzbDrone.Core/Music/ArtistService.cs @@ -1,10 +1,10 @@ -using System; using System.Collections.Generic; -using System.Linq; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.MediaItems; namespace NzbDrone.Core.Music { - public interface IArtistService + public interface IArtistService : IBaseMediaService { Artist GetArtist(int artistId); List GetArtists(IEnumerable artistIds); @@ -21,7 +21,7 @@ public interface IArtistService bool ArtistPathExists(string path); } - public class ArtistService : IArtistService + public class ArtistService : BaseMediaService, IArtistService { private readonly IArtistRepository _artistRepository; @@ -30,78 +30,21 @@ public ArtistService(IArtistRepository artistRepository) _artistRepository = artistRepository; } - public Artist GetArtist(int artistId) - { - return _artistRepository.Get(artistId); - } + protected override IBasicRepository Repository => _artistRepository; - public List GetArtists(IEnumerable artistIds) - { - return _artistRepository.Get(artistIds).ToList(); - } + public Artist GetArtist(int artistId) => Get(artistId); + public List GetArtists(IEnumerable artistIds) => Get(artistIds); + public Artist AddArtist(Artist newArtist) => Add(newArtist); + public List AddArtists(List newArtists) => AddMany(newArtists); + public void DeleteArtist(int artistId, bool deleteFiles) => Delete(artistId, deleteFiles); + public void DeleteArtists(List artistIds, bool deleteFiles) => DeleteMany(artistIds, deleteFiles); + public List GetAllArtists() => GetAll(); + public Artist UpdateArtist(Artist artist) => Update(artist); + public List UpdateArtists(List artists) => UpdateMany(artists); - public Artist AddArtist(Artist newArtist) - { - newArtist.Added = DateTime.UtcNow; - return _artistRepository.Insert(newArtist); - } - - public List AddArtists(List newArtists) - { - var now = DateTime.UtcNow; - foreach (var artist in newArtists) - { - artist.Added = now; - } - - _artistRepository.InsertMany(newArtists); - return newArtists; - } - - public Artist FindByName(string name) - { - return _artistRepository.FindByName(name); - } - - public Artist FindByForeignId(string foreignArtistId) - { - return _artistRepository.FindByForeignId(foreignArtistId); - } - - public void DeleteArtist(int artistId, bool deleteFiles) - { - _artistRepository.Delete(artistId); - } - - public void DeleteArtists(List artistIds, bool deleteFiles) - { - _artistRepository.DeleteMany(artistIds); - } - - public List GetAllArtists() - { - return _artistRepository.All().ToList(); - } - - public List GetMonitoredArtists() - { - return _artistRepository.GetMonitored(); - } - - public Artist UpdateArtist(Artist artist) - { - return _artistRepository.Update(artist); - } - - public List UpdateArtists(List artists) - { - _artistRepository.UpdateMany(artists); - return artists; - } - - public bool ArtistPathExists(string path) - { - return _artistRepository.ArtistPathExists(path); - } + public Artist FindByName(string name) => _artistRepository.FindByName(name); + public Artist FindByForeignId(string foreignArtistId) => _artistRepository.FindByForeignId(foreignArtistId); + public List GetMonitoredArtists() => _artistRepository.GetMonitored(); + public bool ArtistPathExists(string path) => _artistRepository.ArtistPathExists(path); } } diff --git a/src/NzbDrone.Core/Music/TrackService.cs b/src/NzbDrone.Core/Music/TrackService.cs index db0db14b5a..fd23134903 100644 --- a/src/NzbDrone.Core/Music/TrackService.cs +++ b/src/NzbDrone.Core/Music/TrackService.cs @@ -1,15 +1,13 @@ -using System; using System.Collections.Generic; -using System.Linq; using NzbDrone.Core.Datastore; +using NzbDrone.Core.MediaItems; namespace NzbDrone.Core.Music { - public interface ITrackService + public interface ITrackService : IBaseMediaService { Track GetTrack(int trackId); List GetTracks(IEnumerable trackIds); - PagingSpec Paged(PagingSpec pagingSpec); Track AddTrack(Track newTrack); List AddTracks(List newTracks); Track FindByForeignId(string foreignTrackId); @@ -23,7 +21,7 @@ public interface ITrackService bool TrackPathExists(string path); } - public class TrackService : ITrackService + public class TrackService : BaseMediaService, ITrackService { private readonly ITrackRepository _trackRepository; @@ -32,83 +30,21 @@ public TrackService(ITrackRepository trackRepository) _trackRepository = trackRepository; } - public Track GetTrack(int trackId) - { - return _trackRepository.Get(trackId); - } + protected override IBasicRepository Repository => _trackRepository; - public List GetTracks(IEnumerable trackIds) - { - return _trackRepository.Get(trackIds).ToList(); - } + public Track GetTrack(int trackId) => Get(trackId); + public List GetTracks(IEnumerable trackIds) => Get(trackIds); + public Track AddTrack(Track newTrack) => Add(newTrack); + public List AddTracks(List newTracks) => AddMany(newTracks); + public void DeleteTrack(int trackId, bool deleteFiles) => Delete(trackId, deleteFiles); + public void DeleteTracks(List trackIds, bool deleteFiles) => DeleteMany(trackIds, deleteFiles); + public List GetAllTracks() => GetAll(); + public Track UpdateTrack(Track track) => Update(track); + public List UpdateTracks(List tracks) => UpdateMany(tracks); - public PagingSpec Paged(PagingSpec pagingSpec) - { - return _trackRepository.GetPaged(pagingSpec); - } - - public Track AddTrack(Track newTrack) - { - newTrack.Added = DateTime.UtcNow; - return _trackRepository.Insert(newTrack); - } - - public List AddTracks(List newTracks) - { - var now = DateTime.UtcNow; - foreach (var track in newTracks) - { - track.Added = now; - } - - _trackRepository.InsertMany(newTracks); - return newTracks; - } - - public Track FindByForeignId(string foreignTrackId) - { - return _trackRepository.FindByForeignId(foreignTrackId); - } - - public List FindByAlbumId(int albumId) - { - return _trackRepository.FindByAlbumId(albumId); - } - - public void DeleteTrack(int trackId, bool deleteFiles) - { - _trackRepository.Delete(trackId); - } - - public void DeleteTracks(List trackIds, bool deleteFiles) - { - _trackRepository.DeleteMany(trackIds); - } - - public List GetAllTracks() - { - return _trackRepository.All().ToList(); - } - - public List GetMonitoredTracks() - { - return _trackRepository.GetMonitored(); - } - - public Track UpdateTrack(Track track) - { - return _trackRepository.Update(track); - } - - public List UpdateTracks(List tracks) - { - _trackRepository.UpdateMany(tracks); - return tracks; - } - - public bool TrackPathExists(string path) - { - return _trackRepository.TrackPathExists(path); - } + public Track FindByForeignId(string foreignTrackId) => _trackRepository.FindByForeignId(foreignTrackId); + public List FindByAlbumId(int albumId) => _trackRepository.FindByAlbumId(albumId); + public List GetMonitoredTracks() => _trackRepository.GetMonitored(); + public bool TrackPathExists(string path) => _trackRepository.TrackPathExists(path); } } diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index 3856280ce8..6f8606be96 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -342,7 +342,7 @@ private void AddMovieFileTokens(Dictionary> tok { tokenHandlers["{Original Title}"] = m => GetOriginalTitle(movieFile, multipleTokens); tokenHandlers["{Original Filename}"] = m => GetOriginalFileName(movieFile, multipleTokens); - tokenHandlers["{Release Group}"] = m => movieFile.ReleaseGroup.IsNullOrWhiteSpace() ? m.DefaultValue("Radarr") : Truncate(movieFile.ReleaseGroup, m.CustomFormat); + tokenHandlers["{Release Group}"] = m => movieFile.ReleaseGroup.IsNullOrWhiteSpace() ? m.DefaultValue("Aletheia") : Truncate(movieFile.ReleaseGroup, m.CustomFormat); } private void AddQualityTokens(Dictionary> tokenHandlers, Movie movie, MovieFile movieFile) diff --git a/src/Radarr.Api.V3/openapi.json b/src/Radarr.Api.V3/openapi.json index ccc891c289..eff8cdc88d 100644 --- a/src/Radarr.Api.V3/openapi.json +++ b/src/Radarr.Api.V3/openapi.json @@ -1,11 +1,11 @@ { "openapi": "3.0.4", "info": { - "title": "Radarr", - "description": "Radarr API docs", + "title": "Aletheia", + "description": "Aletheia API docs", "license": { "name": "GPL-3.0", - "url": "https://github.com/Radarr/Radarr/blob/develop/LICENSE" + "url": "https://github.com/cheir-mneme/aletheia/blob/develop/LICENSE" }, "version": "3.0.0" },