瀏覽代碼

Merge pull request #10463 from jellyfin/dotnet8

Update to .NET 8
Cody Robibero 1 年之前
父節點
當前提交
6d1abf67c3
共有 100 個文件被更改,包括 253 次插入257 次删除
  1. 1 1
      .ci/azure-pipelines-abi.yml
  2. 1 1
      .ci/azure-pipelines-main.yml
  3. 2 2
      .ci/azure-pipelines-package.yml
  4. 2 2
      .ci/azure-pipelines-test.yml
  5. 1 1
      .config/dotnet-tools.json
  6. 1 1
      .github/workflows/ci-codeql-analysis.yml
  7. 4 4
      .github/workflows/ci-openapi.yml
  8. 1 1
      .github/workflows/ci-tests.yml
  9. 2 2
      .vscode/launch.json
  10. 26 26
      Directory.Packages.props
  11. 1 1
      Dockerfile
  12. 1 1
      Dockerfile.arm
  13. 1 1
      Dockerfile.arm64
  14. 1 1
      Emby.Dlna/Emby.Dlna.csproj
  15. 1 1
      Emby.Naming/Emby.Naming.csproj
  16. 1 1
      Emby.Photos/Emby.Photos.csproj
  17. 4 1
      Emby.Server.Implementations/ApplicationHost.cs
  18. 1 1
      Emby.Server.Implementations/Emby.Server.Implementations.csproj
  19. 1 0
      Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
  20. 2 4
      Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
  21. 2 3
      Jellyfin.Api/Auth/CustomAuthenticationHandler.cs
  22. 1 1
      Jellyfin.Api/Controllers/EnvironmentController.cs
  23. 1 1
      Jellyfin.Api/Controllers/HlsSegmentController.cs
  24. 9 9
      Jellyfin.Api/Controllers/ImageController.cs
  25. 1 1
      Jellyfin.Api/Controllers/MusicGenresController.cs
  26. 11 11
      Jellyfin.Api/Extensions/DtoExtensions.cs
  27. 2 2
      Jellyfin.Api/Helpers/DynamicHlsHelper.cs
  28. 1 1
      Jellyfin.Api/Helpers/HlsHelpers.cs
  29. 7 7
      Jellyfin.Api/Helpers/StreamingHelpers.cs
  30. 2 2
      Jellyfin.Api/Helpers/TranscodingJobHelper.cs
  31. 1 1
      Jellyfin.Api/Jellyfin.Api.csproj
  32. 5 5
      Jellyfin.Api/Models/StreamingDtos/StreamState.cs
  33. 1 1
      Jellyfin.Data/Jellyfin.Data.csproj
  34. 3 3
      Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs
  35. 1 1
      Jellyfin.Networking/Jellyfin.Networking.csproj
  36. 3 1
      Jellyfin.Networking/Manager/NetworkManager.cs
  37. 1 1
      Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
  38. 1 1
      Jellyfin.Server.Implementations/Security/AuthorizationContext.cs
  39. 2 2
      Jellyfin.Server.Implementations/Users/UserManager.cs
  40. 2 1
      Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
  41. 1 1
      Jellyfin.Server/Jellyfin.Server.csproj
  42. 1 5
      Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs
  43. 1 1
      Jellyfin.Server/Program.cs
  44. 1 1
      Jellyfin.Server/Startup.cs
  45. 1 1
      MediaBrowser.Common/MediaBrowser.Common.csproj
  46. 1 1
      MediaBrowser.Common/Net/NetworkConstants.cs
  47. 1 1
      MediaBrowser.Common/Net/NetworkUtils.cs
  48. 1 1
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  49. 1 0
      MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
  50. 1 1
      MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
  51. 6 6
      MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
  52. 1 1
      MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
  53. 1 1
      MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
  54. 5 5
      MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
  55. 1 1
      MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs
  56. 1 1
      MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
  57. 3 8
      MediaBrowser.Model/Dlna/StreamBuilder.cs
  58. 1 1
      MediaBrowser.Model/MediaBrowser.Model.csproj
  59. 1 1
      MediaBrowser.Model/Net/IPData.cs
  60. 1 1
      MediaBrowser.Providers/Lyric/LrcLyricParser.cs
  61. 2 2
      MediaBrowser.Providers/Manager/ItemImageProvider.cs
  62. 1 1
      MediaBrowser.Providers/MediaBrowser.Providers.csproj
  63. 2 2
      MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
  64. 1 1
      MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs
  65. 1 1
      MediaBrowser.Providers/Music/AlbumMetadataService.cs
  66. 1 1
      MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs
  67. 1 1
      MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs
  68. 1 1
      MediaBrowser.Providers/TV/SeriesMetadataService.cs
  69. 1 1
      MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj
  70. 2 2
      MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
  71. 1 1
      README.md
  72. 1 1
      RSSDP/RSSDP.csproj
  73. 1 1
      deployment/Dockerfile.centos.amd64
  74. 1 1
      deployment/Dockerfile.fedora.amd64
  75. 1 1
      deployment/Dockerfile.ubuntu.amd64
  76. 1 1
      deployment/Dockerfile.ubuntu.arm64
  77. 1 1
      deployment/Dockerfile.ubuntu.armhf
  78. 1 1
      fedora/jellyfin.spec
  79. 1 1
      fuzz/Emby.Server.Implementations.Fuzz/Emby.Server.Implementations.Fuzz.csproj
  80. 1 1
      fuzz/Emby.Server.Implementations.Fuzz/fuzz.sh
  81. 1 1
      fuzz/Jellyfin.Api.Fuzz/Jellyfin.Api.Fuzz.csproj
  82. 1 1
      fuzz/Jellyfin.Api.Fuzz/fuzz.sh
  83. 1 1
      global.json
  84. 3 0
      jellyfin.ruleset
  85. 3 1
      src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
  86. 1 1
      src/Jellyfin.Drawing/Jellyfin.Drawing.csproj
  87. 1 1
      src/Jellyfin.Extensions/Jellyfin.Extensions.csproj
  88. 3 1
      src/Jellyfin.Extensions/Json/JsonDefaults.cs
  89. 1 1
      src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj
  90. 4 3
      src/Jellyfin.MediaEncoding.Keyframes/FfProbe/FfProbeKeyframeExtractor.cs
  91. 1 1
      src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj
  92. 1 1
      tests/Directory.Build.props
  93. 1 1
      tests/Jellyfin.Api.Tests/Controllers/UserControllerTests.cs
  94. 29 36
      tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs
  95. 21 22
      tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedIReadOnlyListTests.cs
  96. 1 0
      tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
  97. 7 7
      tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs
  98. 5 6
      tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs
  99. 1 1
      tests/Jellyfin.Server.Implementations.Tests/Sorting/IndexNumberComparerTests.cs
  100. 1 1
      tests/Jellyfin.Server.Implementations.Tests/Sorting/ParentIndexNumberComparerTests.cs

+ 1 - 1
.ci/azure-pipelines-abi.yml

@@ -7,7 +7,7 @@ parameters:
   default: "ubuntu-latest"
 - name: DotNetSdkVersion
   type: string
-  default: 7.0.x
+  default: 8.0.x
 
 jobs:
   - job: CompatibilityCheck

+ 1 - 1
.ci/azure-pipelines-main.yml

@@ -1,7 +1,7 @@
 parameters:
   LinuxImage: 'ubuntu-latest'
   RestoreBuildProjects: 'Jellyfin.Server/Jellyfin.Server.csproj'
-  DotNetSdkVersion: 7.0.x
+  DotNetSdkVersion: 8.0.x
 
 jobs:
   - job: Build

+ 2 - 2
.ci/azure-pipelines-package.yml

@@ -208,10 +208,10 @@ jobs:
 
   steps:
   - task: UseDotNet@2
-    displayName: 'Use .NET 7.0 sdk'
+    displayName: 'Use .NET 8.0 sdk'
     inputs:
       packageType: 'sdk'
-      version: '7.0.x'
+      version: '8.0.x'
 
   - task: DotNetCoreCLI@2
     displayName: 'Build Stable Nuget packages'

+ 2 - 2
.ci/azure-pipelines-test.yml

@@ -10,7 +10,7 @@ parameters:
   default: "tests/**/*Tests.csproj"
 - name: DotNetSdkVersion
   type: string
-  default: 7.0.x
+  default: 8.0.x
 
 jobs:
   - job: Test
@@ -94,5 +94,5 @@ jobs:
         displayName: 'Publish OpenAPI Artifact'
         condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
         inputs:
-          targetPath: "tests/Jellyfin.Server.Integration.Tests/bin/Release/net7.0/openapi.json"
+          targetPath: "tests/Jellyfin.Server.Integration.Tests/bin/Release/net8.0/openapi.json"
           artifactName: 'OpenAPI Spec'

+ 1 - 1
.config/dotnet-tools.json

@@ -9,4 +9,4 @@
       ]
     }
   }
-}
+}

+ 1 - 1
.github/workflows/ci-codeql-analysis.yml

@@ -24,7 +24,7 @@ jobs:
     - name: Setup .NET
       uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
       with:
-        dotnet-version: '7.0.x'
+        dotnet-version: '8.0.x'
 
     - name: Initialize CodeQL
       uses: github/codeql-action/init@689fdc5193eeb735ecb2e52e819e3382876f93f4 # v2.22.6

+ 4 - 4
.github/workflows/ci-openapi.yml

@@ -21,7 +21,7 @@ jobs:
       - name: Setup .NET
         uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
         with:
-          dotnet-version: '7.0.x'
+          dotnet-version: '8.0.x'
       - name: Generate openapi.json
         run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
       - name: Upload openapi.json
@@ -30,7 +30,7 @@ jobs:
           name: openapi-head
           retention-days: 14
           if-no-files-found: error
-          path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net7.0/openapi.json
+          path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net8.0/openapi.json
 
   openapi-base:
     name: OpenAPI - BASE
@@ -55,7 +55,7 @@ jobs:
       - name: Setup .NET
         uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
         with:
-          dotnet-version: '7.0.x'
+          dotnet-version: '8.0.x'
       - name: Generate openapi.json
         run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
       - name: Upload openapi.json
@@ -64,7 +64,7 @@ jobs:
           name: openapi-base
           retention-days: 14
           if-no-files-found: error
-          path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net7.0/openapi.json
+          path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net8.0/openapi.json
 
   openapi-diff:
     permissions:

+ 1 - 1
.github/workflows/ci-tests.yml

@@ -9,7 +9,7 @@ on:
   pull_request:
 
 env:
-  SDK_VERSION: "7.0.x"
+  SDK_VERSION: "8.0.x"
 
 jobs:
   run-tests:

+ 2 - 2
.vscode/launch.json

@@ -6,7 +6,7 @@
             "type": "coreclr",
             "request": "launch",
             "preLaunchTask": "build",
-            "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net7.0/jellyfin.dll",
+            "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net8.0/jellyfin.dll",
             "args": [],
             "cwd": "${workspaceFolder}/Jellyfin.Server",
             "console": "internalConsole",
@@ -22,7 +22,7 @@
             "type": "coreclr",
             "request": "launch",
             "preLaunchTask": "build",
-            "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net7.0/jellyfin.dll",
+            "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net8.0/jellyfin.dll",
             "args": ["--nowebclient"],
             "cwd": "${workspaceFolder}/Jellyfin.Server",
             "console": "internalConsole",

+ 26 - 26
Directory.Packages.props

@@ -23,30 +23,30 @@
     <PackageVersion Include="libse" Version="3.6.13" />
     <PackageVersion Include="LrcParser" Version="2023.524.0" />
     <PackageVersion Include="MetaBrainz.MusicBrainz" Version="5.0.1" />
-    <PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="7.0.13" />
+    <PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="8.0.0" />
     <PackageVersion Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" />
-    <PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.13" />
+    <PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.0" />
     <PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" />
-    <PackageVersion Include="Microsoft.Data.Sqlite" Version="7.0.13" />
-    <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.13" />
-    <PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.13" />
-    <PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.13" />
-    <PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.13" />
-    <PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
-    <PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
-    <PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
-    <PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.4" />
-    <PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0" />
-    <PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
-    <PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
-    <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
-    <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="7.0.13" />
-    <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="7.0.13" />
-    <PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="7.0.0" />
-    <PackageVersion Include="Microsoft.Extensions.Http" Version="7.0.0" />
-    <PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
-    <PackageVersion Include="Microsoft.Extensions.Logging" Version="7.0.0" />
-    <PackageVersion Include="Microsoft.Extensions.Options" Version="7.0.1" />
+    <PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.0" />
+    <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0" />
+    <PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.0" />
+    <PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
+    <PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0" />
+    <PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
+    <PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
+    <PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
+    <PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.0" />
+    <PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
+    <PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
+    <PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
+    <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
+    <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.0" />
+    <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.0" />
+    <PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
+    <PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" />
+    <PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
+    <PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
+    <PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.0" />
     <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
     <PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
     <PackageVersion Include="MimeTypes" Version="2.4.0" />
@@ -77,9 +77,9 @@
     <PackageVersion Include="Swashbuckle.AspNetCore" Version="6.2.3" />
     <PackageVersion Include="System.Globalization" Version="4.3.0" />
     <PackageVersion Include="System.Linq.Async" Version="6.0.1" />
-    <PackageVersion Include="System.Text.Encoding.CodePages" Version="7.0.0" />
-    <PackageVersion Include="System.Text.Json" Version="7.0.3" />
-    <PackageVersion Include="System.Threading.Tasks.Dataflow" Version="7.0.0" />
+    <PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" />
+    <PackageVersion Include="System.Text.Json" Version="8.0.0" />
+    <PackageVersion Include="System.Threading.Tasks.Dataflow" Version="8.0.0" />
     <PackageVersion Include="TagLibSharp" Version="2.3.0" />
     <PackageVersion Include="TMDbLib" Version="2.0.0" />
     <PackageVersion Include="UTF.Unknown" Version="2.5.1" />
@@ -88,4 +88,4 @@
     <PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" />
     <PackageVersion Include="xunit" Version="2.6.1" />
   </ItemGroup>
-</Project>
+</Project>

+ 1 - 1
Dockerfile

@@ -2,7 +2,7 @@
 #####################################
 # Requires binfm_misc registration
 # https://github.com/multiarch/qemu-user-static#binfmt_misc-register
-ARG DOTNET_VERSION=7.0
+ARG DOTNET_VERSION=8.0
 
 FROM node:20-alpine as web-builder
 ARG JELLYFIN_WEB_VERSION=master

+ 1 - 1
Dockerfile.arm

@@ -2,7 +2,7 @@
 #####################################
 # Requires binfm_misc registration
 # https://github.com/multiarch/qemu-user-static#binfmt_misc-register
-ARG DOTNET_VERSION=7.0
+ARG DOTNET_VERSION=8.0
 
 
 FROM node:20-alpine as web-builder

+ 1 - 1
Dockerfile.arm64

@@ -2,7 +2,7 @@
 #####################################
 # Requires binfm_misc registration
 # https://github.com/multiarch/qemu-user-static#binfmt_misc-register
-ARG DOTNET_VERSION=7.0
+ARG DOTNET_VERSION=8.0
 
 
 FROM node:20-alpine as web-builder

+ 1 - 1
Emby.Dlna/Emby.Dlna.csproj

@@ -17,7 +17,7 @@
   </ItemGroup>
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
   </PropertyGroup>

+ 1 - 1
Emby.Naming/Emby.Naming.csproj

@@ -6,7 +6,7 @@
   </PropertyGroup>
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <PublishRepositoryUrl>true</PublishRepositoryUrl>

+ 1 - 1
Emby.Photos/Emby.Photos.csproj

@@ -19,7 +19,7 @@
   </ItemGroup>
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
   </PropertyGroup>

+ 4 - 1
Emby.Server.Implementations/ApplicationHost.cs

@@ -99,6 +99,7 @@ using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 using Prometheus.DotNetRuntime;
 using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
+using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager;
 using WebSocketManager = Emby.Server.Implementations.HttpServer.WebSocketManager;
 
 namespace Emby.Server.Implementations
@@ -309,7 +310,9 @@ namespace Emby.Server.Implementations
             {
                 _creatingInstances.Add(type);
                 Logger.LogDebug("Creating instance of {Type}", type);
-                return ActivatorUtilities.CreateInstance(ServiceProvider, type);
+                return ServiceProvider is null
+                    ? Activator.CreateInstance(type)
+                    : ActivatorUtilities.CreateInstance(ServiceProvider, type);
             }
             catch (Exception ex)
             {

+ 1 - 1
Emby.Server.Implementations/Emby.Server.Implementations.csproj

@@ -40,7 +40,7 @@
   </ItemGroup>
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
   </PropertyGroup>

+ 1 - 0
Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs

@@ -12,6 +12,7 @@ using MediaBrowser.Controller;
 using MediaBrowser.Controller.Plugins;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging;
+using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager;
 
 namespace Emby.Server.Implementations.EntryPoints
 {

+ 2 - 4
Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs

@@ -84,15 +84,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
             return Task.CompletedTask;
         }
 
-        public Task Close()
+        public async Task Close()
         {
             EnableStreamSharing = false;
 
             Logger.LogInformation("Closing {Type}", GetType().Name);
 
-            LiveStreamCancellationTokenSource.Cancel();
-
-            return Task.CompletedTask;
+            await LiveStreamCancellationTokenSource.CancelAsync().ConfigureAwait(false);
         }
 
         public Stream GetStream()

+ 2 - 3
Jellyfin.Api/Auth/CustomAuthenticationHandler.cs

@@ -27,13 +27,12 @@ namespace Jellyfin.Api.Auth
         /// <param name="options">Options monitor.</param>
         /// <param name="logger">The logger.</param>
         /// <param name="encoder">The url encoder.</param>
-        /// <param name="clock">The system clock.</param>
         public CustomAuthenticationHandler(
             IAuthService authService,
             IOptionsMonitor<AuthenticationSchemeOptions> options,
             ILoggerFactory logger,
-            UrlEncoder encoder,
-            ISystemClock clock) : base(options, logger, encoder, clock)
+            UrlEncoder encoder)
+            : base(options, logger, encoder)
         {
             _authService = authService;
             _logger = logger.CreateLogger<CustomAuthenticationHandler>();

+ 1 - 1
Jellyfin.Api/Controllers/EnvironmentController.cs

@@ -169,7 +169,7 @@ public class EnvironmentController : BaseJellyfinApiController
             // Check if unc share
             var index = path.LastIndexOf(UncSeparator);
 
-            if (index != -1 && path.IndexOf(UncSeparator, StringComparison.OrdinalIgnoreCase) == 0)
+            if (index != -1 && path[0] == UncSeparator)
             {
                 parent = path.Substring(0, index);
 

+ 1 - 1
Jellyfin.Api/Controllers/HlsSegmentController.cs

@@ -160,7 +160,7 @@ public class HlsSegmentController : BaseJellyfinApiController
             var pathExtension = Path.GetExtension(path);
             if ((string.Equals(pathExtension, segmentContainer, StringComparison.OrdinalIgnoreCase)
                  || string.Equals(pathExtension, ".m3u8", StringComparison.OrdinalIgnoreCase))
-                && path.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1)
+                && path.Contains(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase))
             {
                 playlistPath = path;
                 break;

+ 9 - 9
Jellyfin.Api/Controllers/ImageController.cs

@@ -80,7 +80,7 @@ public class ImageController : BaseJellyfinApiController
         _appPaths = appPaths;
     }
 
-    private static Stream GetFromBase64Stream(Stream inputStream)
+    private static CryptoStream GetFromBase64Stream(Stream inputStream)
         => new CryptoStream(inputStream, new FromBase64Transform(), CryptoStreamMode.Read);
 
     /// <summary>
@@ -2080,30 +2080,30 @@ public class ImageController : BaseJellyfinApiController
 
         foreach (var (key, value) in headers)
         {
-            Response.Headers.Add(key, value);
+            Response.Headers.Append(key, value);
         }
 
         Response.ContentType = imageContentType ?? MediaTypeNames.Text.Plain;
-        Response.Headers.Add(HeaderNames.Age, Convert.ToInt64((DateTime.UtcNow - dateImageModified).TotalSeconds).ToString(CultureInfo.InvariantCulture));
-        Response.Headers.Add(HeaderNames.Vary, HeaderNames.Accept);
+        Response.Headers.Append(HeaderNames.Age, Convert.ToInt64((DateTime.UtcNow - dateImageModified).TotalSeconds).ToString(CultureInfo.InvariantCulture));
+        Response.Headers.Append(HeaderNames.Vary, HeaderNames.Accept);
 
         if (disableCaching)
         {
-            Response.Headers.Add(HeaderNames.CacheControl, "no-cache, no-store, must-revalidate");
-            Response.Headers.Add(HeaderNames.Pragma, "no-cache, no-store, must-revalidate");
+            Response.Headers.Append(HeaderNames.CacheControl, "no-cache, no-store, must-revalidate");
+            Response.Headers.Append(HeaderNames.Pragma, "no-cache, no-store, must-revalidate");
         }
         else
         {
             if (cacheDuration.HasValue)
             {
-                Response.Headers.Add(HeaderNames.CacheControl, "public, max-age=" + cacheDuration.Value.TotalSeconds);
+                Response.Headers.Append(HeaderNames.CacheControl, "public, max-age=" + cacheDuration.Value.TotalSeconds);
             }
             else
             {
-                Response.Headers.Add(HeaderNames.CacheControl, "public");
+                Response.Headers.Append(HeaderNames.CacheControl, "public");
             }
 
-            Response.Headers.Add(HeaderNames.LastModified, dateImageModified.ToUniversalTime().ToString("ddd, dd MMM yyyy HH:mm:ss \"GMT\"", CultureInfo.InvariantCulture));
+            Response.Headers.Append(HeaderNames.LastModified, dateImageModified.ToUniversalTime().ToString("ddd, dd MMM yyyy HH:mm:ss \"GMT\"", CultureInfo.InvariantCulture));
 
             // if the image was not modified since "ifModifiedSinceHeader"-header, return a HTTP status code 304 not modified
             if (!(dateImageModified > ifModifiedSinceHeader) && cacheDuration.HasValue)

+ 1 - 1
Jellyfin.Api/Controllers/MusicGenresController.cs

@@ -150,7 +150,7 @@ public class MusicGenresController : BaseJellyfinApiController
 
         MusicGenre? item;
 
-        if (genreName.IndexOf(BaseItem.SlugChar, StringComparison.OrdinalIgnoreCase) != -1)
+        if (genreName.Contains(BaseItem.SlugChar, StringComparison.OrdinalIgnoreCase))
         {
             item = GetItemFromSlugName<MusicGenre>(_libraryManager, genreName, dtoOptions, BaseItemKind.MusicGenre);
         }

+ 11 - 11
Jellyfin.Api/Extensions/DtoExtensions.cs

@@ -38,10 +38,10 @@ public static class DtoExtensions
 
         if (!dtoOptions.ContainsField(ItemFields.RecursiveItemCount))
         {
-            if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 ||
-                client.IndexOf("wmc", StringComparison.OrdinalIgnoreCase) != -1 ||
-                client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 ||
-                client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1)
+            if (client.Contains("kodi", StringComparison.OrdinalIgnoreCase) ||
+                client.Contains("wmc", StringComparison.OrdinalIgnoreCase) ||
+                client.Contains("media center", StringComparison.OrdinalIgnoreCase) ||
+                client.Contains("classic", StringComparison.OrdinalIgnoreCase))
             {
                 int oldLen = dtoOptions.Fields.Count;
                 var arr = new ItemFields[oldLen + 1];
@@ -53,13 +53,13 @@ public static class DtoExtensions
 
         if (!dtoOptions.ContainsField(ItemFields.ChildCount))
         {
-            if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 ||
-                client.IndexOf("wmc", StringComparison.OrdinalIgnoreCase) != -1 ||
-                client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 ||
-                client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1 ||
-                client.IndexOf("roku", StringComparison.OrdinalIgnoreCase) != -1 ||
-                client.IndexOf("samsung", StringComparison.OrdinalIgnoreCase) != -1 ||
-                client.IndexOf("androidtv", StringComparison.OrdinalIgnoreCase) != -1)
+            if (client.Contains("kodi", StringComparison.OrdinalIgnoreCase) ||
+                client.Contains("wmc", StringComparison.OrdinalIgnoreCase) ||
+                client.Contains("media center", StringComparison.OrdinalIgnoreCase) ||
+                client.Contains("classic", StringComparison.OrdinalIgnoreCase) ||
+                client.Contains("roku", StringComparison.OrdinalIgnoreCase) ||
+                client.Contains("samsung", StringComparison.OrdinalIgnoreCase) ||
+                client.Contains("androidtv", StringComparison.OrdinalIgnoreCase))
             {
                 int oldLen = dtoOptions.Fields.Count;
                 var arr = new ItemFields[oldLen + 1];

+ 2 - 2
Jellyfin.Api/Helpers/DynamicHlsHelper.cs

@@ -147,7 +147,7 @@ public class DynamicHlsHelper
                 cancellationTokenSource.Token)
             .ConfigureAwait(false);
 
-        _httpContextAccessor.HttpContext.Response.Headers.Add(HeaderNames.Expires, "0");
+        _httpContextAccessor.HttpContext.Response.Headers.Append(HeaderNames.Expires, "0");
         if (isHeadRequest)
         {
             return new FileContentResult(Array.Empty<byte>(), MimeTypes.GetMimeType("playlist.m3u8"));
@@ -568,7 +568,7 @@ public class DynamicHlsHelper
             && state.VideoStream is not null
             && state.VideoStream.Level.HasValue)
         {
-            levelString = state.VideoStream.Level.ToString() ?? string.Empty;
+            levelString = state.VideoStream.Level.Value.ToString(CultureInfo.InvariantCulture) ?? string.Empty;
         }
         else
         {

+ 1 - 1
Jellyfin.Api/Helpers/HlsHelpers.cs

@@ -53,7 +53,7 @@ public static class HlsHelpers
                             break;
                         }
 
-                        if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1)
+                        if (line.Contains("#EXTINF:", StringComparison.OrdinalIgnoreCase))
                         {
                             count++;
                             if (count >= segmentCount)

+ 7 - 7
Jellyfin.Api/Helpers/StreamingHelpers.cs

@@ -279,15 +279,15 @@ public static class StreamingHelpers
         var profile = state.DeviceProfile;
 
         StringValues transferMode = request.Headers["transferMode.dlna.org"];
-        responseHeaders.Add("transferMode.dlna.org", string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode.ToString());
-        responseHeaders.Add("realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*");
+        responseHeaders.Append("transferMode.dlna.org", string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode.ToString());
+        responseHeaders.Append("realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*");
 
         if (state.RunTimeTicks.HasValue)
         {
             if (string.Equals(request.Headers["getMediaInfo.sec"], "1", StringComparison.OrdinalIgnoreCase))
             {
                 var ms = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds;
-                responseHeaders.Add("MediaInfo.sec", string.Format(
+                responseHeaders.Append("MediaInfo.sec", string.Format(
                     CultureInfo.InvariantCulture,
                     "SEC_Duration={0};",
                     Convert.ToInt32(ms)));
@@ -305,7 +305,7 @@ public static class StreamingHelpers
 
         if (!state.IsVideoRequest)
         {
-            responseHeaders.Add("contentFeatures.dlna.org", ContentFeatureBuilder.BuildAudioHeader(
+            responseHeaders.Append("contentFeatures.dlna.org", ContentFeatureBuilder.BuildAudioHeader(
                 profile,
                 state.OutputContainer,
                 audioCodec,
@@ -321,7 +321,7 @@ public static class StreamingHelpers
         {
             var videoCodec = state.ActualOutputVideoCodec;
 
-            responseHeaders.Add(
+            responseHeaders.Append(
                 "contentFeatures.dlna.org",
                 ContentFeatureBuilder.BuildVideoHeader(profile, state.OutputContainer, videoCodec, audioCodec, state.OutputWidth, state.OutputHeight, state.TargetVideoBitDepth, state.OutputVideoBitrate, state.TargetTimestamp, isStaticallyStreamed, state.RunTimeTicks, state.TargetVideoProfile, state.TargetVideoRangeType, state.TargetVideoLevel, state.TargetFramerate, state.TargetPacketLength, state.TranscodeSeekInfo, state.IsTargetAnamorphic, state.IsTargetInterlaced, state.TargetRefFrames, state.TargetVideoStreamCount, state.TargetAudioStreamCount, state.TargetVideoCodecTag, state.IsTargetAVC).FirstOrDefault() ?? string.Empty);
         }
@@ -404,12 +404,12 @@ public static class StreamingHelpers
         var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks!.Value).TotalSeconds.ToString(CultureInfo.InvariantCulture);
         var startSeconds = TimeSpan.FromTicks(startTimeTicks ?? 0).TotalSeconds.ToString(CultureInfo.InvariantCulture);
 
-        responseHeaders.Add("TimeSeekRange.dlna.org", string.Format(
+        responseHeaders.Append("TimeSeekRange.dlna.org", string.Format(
             CultureInfo.InvariantCulture,
             "npt={0}-{1}/{1}",
             startSeconds,
             runtimeSeconds));
-        responseHeaders.Add("X-AvailableSeekRange", string.Format(
+        responseHeaders.Append("X-AvailableSeekRange", string.Format(
             CultureInfo.InvariantCulture,
             "1 npt={0}-{1}",
             startSeconds,

+ 2 - 2
Jellyfin.Api/Helpers/TranscodingJobHelper.cs

@@ -280,6 +280,7 @@ public class TranscodingJobHelper : IDisposable
 
             if (job.CancellationTokenSource?.IsCancellationRequested == false)
             {
+#pragma warning disable CA1849 // Can't await in lock block
                 job.CancellationTokenSource.Cancel();
             }
         }
@@ -291,7 +292,6 @@ public class TranscodingJobHelper : IDisposable
 
         lock (job.ProcessLock!)
         {
-#pragma warning disable CA1849 // Can't await in lock block
             job.TranscodingThrottler?.Stop().GetAwaiter().GetResult();
 
             var process = job.Process;
@@ -405,7 +405,7 @@ public class TranscodingJobHelper : IDisposable
         var name = Path.GetFileNameWithoutExtension(outputFilePath);
 
         var filesToDelete = _fileSystem.GetFilePaths(directory)
-            .Where(f => f.IndexOf(name, StringComparison.OrdinalIgnoreCase) != -1);
+            .Where(f => f.Contains(name, StringComparison.OrdinalIgnoreCase));
 
         List<Exception>? exs = null;
         foreach (var file in filesToDelete)

+ 1 - 1
Jellyfin.Api/Jellyfin.Api.csproj

@@ -6,7 +6,7 @@
   </PropertyGroup>
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
   </PropertyGroup>
 

+ 5 - 5
Jellyfin.Api/Models/StreamingDtos/StreamState.cs

@@ -86,11 +86,11 @@ public class StreamState : EncodingJobInfo, IDisposable
             {
                 var userAgent = UserAgent ?? string.Empty;
 
-                if (userAgent.IndexOf("AppleTV", StringComparison.OrdinalIgnoreCase) != -1
-                    || userAgent.IndexOf("cfnetwork", StringComparison.OrdinalIgnoreCase) != -1
-                    || userAgent.IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1
-                    || userAgent.IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1
-                    || userAgent.IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1)
+                if (userAgent.Contains("AppleTV", StringComparison.OrdinalIgnoreCase)
+                    || userAgent.Contains("cfnetwork", StringComparison.OrdinalIgnoreCase)
+                    || userAgent.Contains("ipad", StringComparison.OrdinalIgnoreCase)
+                    || userAgent.Contains("iphone", StringComparison.OrdinalIgnoreCase)
+                    || userAgent.Contains("ipod", StringComparison.OrdinalIgnoreCase))
                 {
                     return 6;
                 }

+ 1 - 1
Jellyfin.Data/Jellyfin.Data.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <PublishRepositoryUrl>true</PublishRepositoryUrl>

+ 3 - 3
Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs

@@ -65,7 +65,7 @@ namespace Jellyfin.Networking.HappyEyeballs
             // See https://github.com/dotnet/corefx/pull/29792/files#r189415885 for more details.
             if (await Task.WhenAny(tryConnectAsyncIPv6, Task.Delay(200, cancelIPv6.Token)).ConfigureAwait(false) == tryConnectAsyncIPv6 && tryConnectAsyncIPv6.IsCompletedSuccessfully)
             {
-                cancelIPv6.Cancel();
+                await cancelIPv6.CancelAsync().ConfigureAwait(false);
                 return tryConnectAsyncIPv6.GetAwaiter().GetResult();
             }
 
@@ -76,7 +76,7 @@ namespace Jellyfin.Networking.HappyEyeballs
             {
                 if (tryConnectAsyncIPv6.IsCompletedSuccessfully)
                 {
-                    cancelIPv4.Cancel();
+                    await cancelIPv4.CancelAsync().ConfigureAwait(false);
                     return tryConnectAsyncIPv6.GetAwaiter().GetResult();
                 }
 
@@ -86,7 +86,7 @@ namespace Jellyfin.Networking.HappyEyeballs
             {
                 if (tryConnectAsyncIPv4.IsCompletedSuccessfully)
                 {
-                    cancelIPv6.Cancel();
+                    await cancelIPv6.CancelAsync().ConfigureAwait(false);
                     return tryConnectAsyncIPv4.GetAwaiter().GetResult();
                 }
 

+ 1 - 1
Jellyfin.Networking/Jellyfin.Networking.csproj

@@ -1,6 +1,6 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
   </PropertyGroup>

+ 3 - 1
Jellyfin.Networking/Manager/NetworkManager.cs

@@ -15,6 +15,8 @@ using Microsoft.AspNetCore.HttpOverrides;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging;
 using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
+using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager;
+using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
 
 namespace Jellyfin.Networking.Manager
 {
@@ -423,7 +425,7 @@ namespace Jellyfin.Networking.Manager
             {
                 // Parse config values into filter collection
                 var remoteIPFilter = config.RemoteIPFilter;
-                if (remoteIPFilter.Any() && !string.IsNullOrWhiteSpace(remoteIPFilter.First()))
+                if (remoteIPFilter.Length != 0 && !string.IsNullOrWhiteSpace(remoteIPFilter[0]))
                 {
                     // Parse all IPs with netmask to a subnet
                     var remoteAddressFilter = new List<IPNetwork>();

+ 1 - 1
Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
   </PropertyGroup>

+ 1 - 1
Jellyfin.Server.Implementations/Security/AuthorizationContext.cs

@@ -60,7 +60,7 @@ namespace Jellyfin.Server.Implementations.Security
         }
 
         private async Task<AuthorizationInfo> GetAuthorizationInfoFromDictionary(
-            IReadOnlyDictionary<string, string>? auth,
+            Dictionary<string, string>? auth,
             IHeaderDictionary headers,
             IQueryCollection queryString)
         {

+ 2 - 2
Jellyfin.Server.Implementations/Users/UserManager.cs

@@ -748,7 +748,7 @@ namespace Jellyfin.Server.Implementations.Users
             return GetPasswordResetProviders(user)[0];
         }
 
-        private IList<IAuthenticationProvider> GetAuthenticationProviders(User? user)
+        private List<IAuthenticationProvider> GetAuthenticationProviders(User? user)
         {
             var authenticationProviderId = user?.AuthenticationProviderId;
 
@@ -775,7 +775,7 @@ namespace Jellyfin.Server.Implementations.Users
             return providers;
         }
 
-        private IList<IPasswordResetProvider> GetPasswordResetProviders(User user)
+        private IPasswordResetProvider[] GetPasswordResetProviders(User user)
         {
             var passwordResetProviderId = user.PasswordResetProviderId;
             var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray();

+ 2 - 1
Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs

@@ -37,6 +37,7 @@ using Microsoft.OpenApi.Interfaces;
 using Microsoft.OpenApi.Models;
 using Swashbuckle.AspNetCore.SwaggerGen;
 using AuthenticationSchemes = Jellyfin.Api.Constants.AuthenticationSchemes;
+using IPNetwork = System.Net.IPNetwork;
 
 namespace Jellyfin.Server.Extensions
 {
@@ -311,7 +312,7 @@ namespace Jellyfin.Server.Extensions
             }
             else
             {
-                options.KnownNetworks.Add(new IPNetwork(addr, prefixLength));
+                options.KnownNetworks.Add(new Microsoft.AspNetCore.HttpOverrides.IPNetwork(addr, prefixLength));
             }
         }
 

+ 1 - 1
Jellyfin.Server/Jellyfin.Server.csproj

@@ -8,7 +8,7 @@
   <PropertyGroup>
     <AssemblyName>jellyfin</AssemblyName>
     <OutputType>Exe</OutputType>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <ServerGarbageCollection>false</ServerGarbageCollection>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>

+ 1 - 5
Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs

@@ -78,11 +78,7 @@ namespace Jellyfin.Server.Migrations.Routines
                     }
                     else
                     {
-                        var ratingValue = _localizationManager.GetRatingLevel(ratingString).ToString();
-                        if (string.IsNullOrEmpty(ratingValue))
-                        {
-                            ratingValue = "NULL";
-                        }
+                        var ratingValue = _localizationManager.GetRatingLevel(ratingString)?.ToString(CultureInfo.InvariantCulture) ?? "NULL";
 
                         using var statement = connection.PrepareStatement("UPDATE TypedBaseItems SET InheritedParentalRatingValue = @Value WHERE OfficialRating = @Rating;");
                         statement.TryBind("@Value", ratingValue);

+ 1 - 1
Jellyfin.Server/Program.cs

@@ -40,7 +40,7 @@ namespace Jellyfin.Server
         /// </summary>
         public const string LoggingConfigFileSystem = "logging.json";
 
-        private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory();
+        private static readonly SerilogLoggerFactory _loggerFactory = new SerilogLoggerFactory();
         private static long _startTimestamp;
         private static ILogger _logger = NullLogger.Instance;
         private static bool _restartOnShutdown;

+ 1 - 1
Jellyfin.Server/Startup.cs

@@ -35,7 +35,7 @@ namespace Jellyfin.Server
     /// </summary>
     public class Startup
     {
-        private readonly IServerApplicationHost _serverApplicationHost;
+        private readonly CoreAppHost _serverApplicationHost;
         private readonly IServerConfigurationManager _serverConfigurationManager;
 
         /// <summary>

+ 1 - 1
MediaBrowser.Common/MediaBrowser.Common.csproj

@@ -29,7 +29,7 @@
   </ItemGroup>
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <PublishRepositoryUrl>true</PublishRepositoryUrl>

+ 1 - 1
MediaBrowser.Common/Net/NetworkConstants.cs

@@ -1,5 +1,5 @@
 using System.Net;
-using Microsoft.AspNetCore.HttpOverrides;
+using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
 
 namespace MediaBrowser.Common.Net;
 

+ 1 - 1
MediaBrowser.Common/Net/NetworkUtils.cs

@@ -5,7 +5,7 @@ using System.Net;
 using System.Net.Sockets;
 using System.Text.RegularExpressions;
 using Jellyfin.Extensions;
-using Microsoft.AspNetCore.HttpOverrides;
+using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
 
 namespace MediaBrowser.Common.Net;
 

+ 1 - 1
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -35,7 +35,7 @@
   </ItemGroup>
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <PublishRepositoryUrl>true</PublishRepositoryUrl>

+ 1 - 0
MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs

@@ -20,6 +20,7 @@ using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.MediaInfo;
 using Microsoft.Extensions.Configuration;
+using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager;
 
 namespace MediaBrowser.Controller.MediaEncoding
 {

+ 1 - 1
MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj

@@ -11,7 +11,7 @@
   </ItemGroup>
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
   </PropertyGroup>

+ 6 - 6
MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs

@@ -121,7 +121,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             "yadif_videotoolbox"
         };
 
-        private static readonly IReadOnlyDictionary<int, string[]> _filterOptionsDict = new Dictionary<int, string[]>
+        private static readonly Dictionary<int, string[]> _filterOptionsDict = new Dictionary<int, string[]>
         {
             { 0, new string[] { "scale_cuda", "Output format (default \"same\")" } },
             { 1, new string[] { "tonemap_cuda", "GPU accelerated HDR to SDR tonemapping" } },
@@ -132,7 +132,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
         };
 
         // These are the library versions that corresponds to our minimum ffmpeg version 4.x according to the version table below
-        private static readonly IReadOnlyDictionary<string, Version> _ffmpegMinimumLibraryVersions = new Dictionary<string, Version>
+        private static readonly Dictionary<string, Version> _ffmpegMinimumLibraryVersions = new Dictionary<string, Version>
         {
             { "libavutil", new Version(56, 14) },
             { "libavcodec", new Version(58, 18) },
@@ -197,7 +197,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
         internal bool ValidateVersionInternal(string versionOutput)
         {
-            if (versionOutput.IndexOf("Libav developers", StringComparison.OrdinalIgnoreCase) != -1)
+            if (versionOutput.Contains("Libav developers", StringComparison.OrdinalIgnoreCase))
             {
                 _logger.LogError("FFmpeg validation: avconv instead of ffmpeg is not supported");
                 return false;
@@ -333,7 +333,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// </summary>
         /// <param name="output">The 'ffmpeg -version' output.</param>
         /// <returns>The library names and major.minor version numbers.</returns>
-        private static IReadOnlyDictionary<string, Version> GetFFmpegLibraryVersions(string output)
+        private static Dictionary<string, Version> GetFFmpegLibraryVersions(string output)
         {
             var map = new Dictionary<string, Version>();
 
@@ -537,9 +537,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
             return found;
         }
 
-        private IDictionary<int, bool> GetFFmpegFiltersWithOption()
+        private Dictionary<int, bool> GetFFmpegFiltersWithOption()
         {
-            IDictionary<int, bool> dict = new Dictionary<int, bool>();
+            Dictionary<int, bool> dict = new Dictionary<int, bool>();
             for (int i = 0; i < _filterOptionsDict.Count; i++)
             {
                 if (_filterOptionsDict.TryGetValue(i, out var val) && val.Length == 2)

+ 1 - 1
MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs

@@ -59,7 +59,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// <returns>System.String.</returns>
         private static string GetFileInputArgument(string path, string inputPrefix)
         {
-            if (path.IndexOf("://", StringComparison.Ordinal) != -1)
+            if (path.Contains("://", StringComparison.Ordinal))
             {
                 return string.Format(CultureInfo.InvariantCulture, "\"{0}\"", path);
             }

+ 1 - 1
MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj

@@ -6,7 +6,7 @@
   </PropertyGroup>
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
   </PropertyGroup>

+ 5 - 5
MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs

@@ -516,7 +516,7 @@ namespace MediaBrowser.MediaEncoding.Probing
 
         private void ProcessPairs(string key, List<NameValuePair> pairs, MediaInfo info)
         {
-            IList<BaseItemPerson> peoples = new List<BaseItemPerson>();
+            List<BaseItemPerson> peoples = new List<BaseItemPerson>();
             if (string.Equals(key, "studio", StringComparison.OrdinalIgnoreCase))
             {
                 info.Studios = pairs.Select(p => p.Value)
@@ -612,11 +612,11 @@ namespace MediaBrowser.MediaEncoding.Probing
             {
                 codec = "dvbsub";
             }
-            else if ((codec ?? string.Empty).IndexOf("PGS", StringComparison.OrdinalIgnoreCase) != -1)
+            else if ((codec ?? string.Empty).Contains("PGS", StringComparison.OrdinalIgnoreCase))
             {
                 codec = "PGSSUB";
             }
-            else if ((codec ?? string.Empty).IndexOf("DVD", StringComparison.OrdinalIgnoreCase) != -1)
+            else if ((codec ?? string.Empty).Contains("DVD", StringComparison.OrdinalIgnoreCase))
             {
                 codec = "DVDSUB";
             }
@@ -1182,7 +1182,7 @@ namespace MediaBrowser.MediaEncoding.Probing
             info.Size = string.IsNullOrEmpty(data.Format.Size) ? null : long.Parse(data.Format.Size, CultureInfo.InvariantCulture);
         }
 
-        private void SetAudioInfoFromTags(MediaInfo audio, IReadOnlyDictionary<string, string> tags)
+        private void SetAudioInfoFromTags(MediaInfo audio, Dictionary<string, string> tags)
         {
             var people = new List<BaseItemPerson>();
             if (tags.TryGetValue("composer", out var composer) && !string.IsNullOrWhiteSpace(composer))
@@ -1339,7 +1339,7 @@ namespace MediaBrowser.MediaEncoding.Probing
         {
             // Only use the comma as a delimiter if there are no slashes or pipes.
             // We want to be careful not to split names that have commas in them
-            var delimiter = !allowCommaDelimiter || _nameDelimiters.Any(i => val.IndexOf(i, StringComparison.Ordinal) != -1) ?
+            var delimiter = !allowCommaDelimiter || _nameDelimiters.Any(i => val.Contains(i, StringComparison.Ordinal)) ?
                 _nameDelimiters :
                 new[] { ',' };
 

+ 1 - 1
MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs

@@ -88,7 +88,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
         public bool SupportsFileExtension(string fileExtension)
             => _subtitleFormats.ContainsKey(fileExtension);
 
-        private IEnumerable<SubtitleFormat> GetSubtitleFormats()
+        private List<SubtitleFormat> GetSubtitleFormats()
         {
             var subtitleFormats = new List<SubtitleFormat>();
             var assembly = typeof(SubtitleFormat).Assembly;

+ 1 - 1
MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs

@@ -63,7 +63,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
 
         private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles");
 
-        private Stream ConvertSubtitles(
+        private MemoryStream ConvertSubtitles(
             Stream stream,
             string inputFormat,
             string outputFormat,

+ 3 - 8
MediaBrowser.Model/Dlna/StreamBuilder.cs

@@ -835,11 +835,6 @@ namespace MediaBrowser.Model.Dlna
                 playlistItem.SetOption(qualifier, "profile", videoStream.Profile.ToLowerInvariant());
             }
 
-            if (videoStream is not null && videoStream.Level != 0)
-            {
-                playlistItem.SetOption(qualifier, "level", videoStream.Level.ToString() ?? string.Empty);
-            }
-
             // Prefer matching audio codecs, could do better here
             var audioCodecs = ContainerProfile.SplitValue(audioCodec);
 
@@ -866,16 +861,16 @@ namespace MediaBrowser.Model.Dlna
 
                 // Copy matching audio codec options
                 playlistItem.AudioSampleRate = audioStream.SampleRate;
-                playlistItem.SetOption(qualifier, "audiochannels", audioStream.Channels.ToString() ?? string.Empty);
+                playlistItem.SetOption(qualifier, "audiochannels", audioStream.Channels?.ToString(CultureInfo.InvariantCulture) ?? string.Empty);
 
                 if (!string.IsNullOrEmpty(audioStream.Profile))
                 {
                     playlistItem.SetOption(audioStream.Codec, "profile", audioStream.Profile.ToLowerInvariant());
                 }
 
-                if (audioStream.Level != 0)
+                if (audioStream.Level.HasValue && audioStream.Level.Value != 0)
                 {
-                    playlistItem.SetOption(audioStream.Codec, "level", audioStream.Level.ToString() ?? string.Empty);
+                    playlistItem.SetOption(audioStream.Codec, "level", audioStream.Level.Value.ToString(CultureInfo.InvariantCulture));
                 }
             }
 

+ 1 - 1
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -14,7 +14,7 @@
   </PropertyGroup>
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <PublishRepositoryUrl>true</PublishRepositoryUrl>

+ 1 - 1
MediaBrowser.Model/Net/IPData.cs

@@ -1,6 +1,6 @@
 using System.Net;
 using System.Net.Sockets;
-using Microsoft.AspNetCore.HttpOverrides;
+using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
 
 namespace MediaBrowser.Model.Net;
 

+ 1 - 1
MediaBrowser.Providers/Lyric/LrcLyricParser.cs

@@ -125,7 +125,7 @@ public class LrcLyricParser : ILyricParser
     /// </summary>
     /// <param name="metaData">The metadata from the LRC file.</param>
     /// <returns>A lyricMetadata object with mapped property data.</returns>
-    private static LyricMetadata MapMetadataValues(IDictionary<string, string> metaData)
+    private static LyricMetadata MapMetadataValues(Dictionary<string, string> metaData)
     {
         LyricMetadata lyricMetadata = new();
 

+ 2 - 2
MediaBrowser.Providers/Manager/ItemImageProvider.cs

@@ -175,7 +175,7 @@ namespace MediaBrowser.Providers.Manager
             IDynamicImageProvider provider,
             ImageRefreshOptions refreshOptions,
             TypeOptions savedOptions,
-            ICollection<ImageType> downloadedImages,
+            List<ImageType> downloadedImages,
             RefreshResult result,
             CancellationToken cancellationToken)
         {
@@ -263,7 +263,7 @@ namespace MediaBrowser.Providers.Manager
             ImageRefreshOptions refreshOptions,
             TypeOptions savedOptions,
             int backdropLimit,
-            ICollection<ImageType> downloadedImages,
+            List<ImageType> downloadedImages,
             RefreshResult result,
             CancellationToken cancellationToken)
         {

+ 1 - 1
MediaBrowser.Providers/MediaBrowser.Providers.csproj

@@ -27,7 +27,7 @@
   </ItemGroup>
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>

+ 2 - 2
MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs

@@ -82,8 +82,8 @@ namespace MediaBrowser.Providers.MediaInfo
             {
                 Directory.CreateDirectory(Path.GetDirectoryName(path));
 
-                var imageStream = imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).IndexOf("front", StringComparison.OrdinalIgnoreCase) != -1) ??
-                    imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).IndexOf("cover", StringComparison.OrdinalIgnoreCase) != -1) ??
+                var imageStream = imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).Contains("front", StringComparison.OrdinalIgnoreCase)) ??
+                    imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).Contains("cover", StringComparison.OrdinalIgnoreCase)) ??
                     imageStreams.FirstOrDefault();
 
                 var imageStreamIndex = imageStream?.Index;

+ 1 - 1
MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs

@@ -183,7 +183,7 @@ namespace MediaBrowser.Providers.MediaInfo
                 files.AddRange(directoryService.GetFilePaths(internalMetadataPath, clearCache, true));
             }
 
-            if (!files.Any())
+            if (files.Count == 0)
             {
                 return Array.Empty<ExternalPathParserResult>();
             }

+ 1 - 1
MediaBrowser.Providers/Music/AlbumMetadataService.cs

@@ -148,7 +148,7 @@ namespace MediaBrowser.Providers.Music
                 .ToArray();
 
             var id = item.GetProviderId(provider);
-            if (ids.Any())
+            if (ids.Length != 0)
             {
                 var firstId = ids[0];
                 if (!string.IsNullOrEmpty(firstId)

+ 1 - 1
MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs

@@ -73,7 +73,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
             return Enumerable.Empty<RemoteImageInfo>();
         }
 
-        private IEnumerable<RemoteImageInfo> GetImages(AudioDbAlbumProvider.Album item)
+        private List<RemoteImageInfo> GetImages(AudioDbAlbumProvider.Album item)
         {
             var list = new List<RemoteImageInfo>();
 

+ 1 - 1
MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs

@@ -78,7 +78,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
             return Enumerable.Empty<RemoteImageInfo>();
         }
 
-        private IEnumerable<RemoteImageInfo> GetImages(AudioDbArtistProvider.Artist item)
+        private List<RemoteImageInfo> GetImages(AudioDbArtistProvider.Artist item)
         {
             var list = new List<RemoteImageInfo>();
 

+ 1 - 1
MediaBrowser.Providers/TV/SeriesMetadataService.cs

@@ -121,7 +121,7 @@ namespace MediaBrowser.Providers.TV
                 var seasonNumber = virtualSeason.IndexNumber;
                 // If there's a physical season with the same number or no episodes in the season, delete it
                 if ((seasonNumber.HasValue && physicalSeasonNumbers.Contains(seasonNumber.Value))
-                    || !virtualSeason.GetEpisodes().Any())
+                    || virtualSeason.GetEpisodes().Count == 0)
                 {
                     Logger.LogInformation("Removing virtual season {SeasonNumber} in series {SeriesName}", virtualSeason.IndexNumber, series.Name);
 

+ 1 - 1
MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj

@@ -15,7 +15,7 @@
   </ItemGroup>
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
   </PropertyGroup>

+ 2 - 2
MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs

@@ -314,11 +314,11 @@ namespace MediaBrowser.XbmcMetadata.Savers
                 {
                     var codec = stream.Codec;
 
-                    if ((stream.CodecTag ?? string.Empty).IndexOf("xvid", StringComparison.OrdinalIgnoreCase) != -1)
+                    if ((stream.CodecTag ?? string.Empty).Contains("xvid", StringComparison.OrdinalIgnoreCase))
                     {
                         codec = "xvid";
                     }
-                    else if ((stream.CodecTag ?? string.Empty).IndexOf("divx", StringComparison.OrdinalIgnoreCase) != -1)
+                    else if ((stream.CodecTag ?? string.Empty).Contains("divx", StringComparison.OrdinalIgnoreCase))
                     {
                         codec = "divx";
                     }

+ 1 - 1
README.md

@@ -137,7 +137,7 @@ A second option is to build the project and then run the resulting executable fi
 
 ```bash
 dotnet build                       # Build the project
-cd Jellyfin.Server/bin/Debug/net7.0 # Change into the build output directory
+cd Jellyfin.Server/bin/Debug/net8.0 # Change into the build output directory
 ```
 
 2. Execute the build output. On Linux, Mac, etc. use `./jellyfin` and on Windows use `jellyfin.exe`.

+ 1 - 1
RSSDP/RSSDP.csproj

@@ -11,7 +11,7 @@
   </ItemGroup>
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <AnalysisMode>AllDisabledByDefault</AnalysisMode>
     <Nullable>disable</Nullable>

+ 1 - 1
deployment/Dockerfile.centos.amd64

@@ -13,7 +13,7 @@ RUN yum update -yq \
   && yum install -yq @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git wget
 
 # Install DotNET SDK
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/ff8c660f-ffa9-4814-ac2d-4089e6ec4eb5/dc806d344844f1d58d8015d105e85c65/dotnet-sdk-7.0.403-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5226a5fa-8c0b-474f-b79a-8984ad7c5beb/3113ccbf789c9fd29972835f0f334b7a/dotnet-sdk-8.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
   && mkdir -p dotnet-sdk \
   && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
   && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 1 - 1
deployment/Dockerfile.fedora.amd64

@@ -12,7 +12,7 @@ RUN dnf update -yq \
   && dnf install -yq @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd wget make
 
 # Install DotNET SDK
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/ff8c660f-ffa9-4814-ac2d-4089e6ec4eb5/dc806d344844f1d58d8015d105e85c65/dotnet-sdk-7.0.403-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5226a5fa-8c0b-474f-b79a-8984ad7c5beb/3113ccbf789c9fd29972835f0f334b7a/dotnet-sdk-8.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
   && mkdir -p dotnet-sdk \
   && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
   && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 1 - 1
deployment/Dockerfile.ubuntu.amd64

@@ -17,7 +17,7 @@ RUN apt-get update -yqq \
     libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
 
 # Install dotnet repository
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/ff8c660f-ffa9-4814-ac2d-4089e6ec4eb5/dc806d344844f1d58d8015d105e85c65/dotnet-sdk-7.0.403-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5226a5fa-8c0b-474f-b79a-8984ad7c5beb/3113ccbf789c9fd29972835f0f334b7a/dotnet-sdk-8.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
   && mkdir -p dotnet-sdk \
   && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
   && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 1 - 1
deployment/Dockerfile.ubuntu.arm64

@@ -16,7 +16,7 @@ RUN apt-get update -yqq \
     mmv build-essential lsb-release
 
 # Install dotnet repository
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/ff8c660f-ffa9-4814-ac2d-4089e6ec4eb5/dc806d344844f1d58d8015d105e85c65/dotnet-sdk-7.0.403-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5226a5fa-8c0b-474f-b79a-8984ad7c5beb/3113ccbf789c9fd29972835f0f334b7a/dotnet-sdk-8.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
   && mkdir -p dotnet-sdk \
   && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
   && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 1 - 1
deployment/Dockerfile.ubuntu.armhf

@@ -16,7 +16,7 @@ RUN apt-get update -yqq \
     mmv build-essential lsb-release
 
 # Install dotnet repository
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/ff8c660f-ffa9-4814-ac2d-4089e6ec4eb5/dc806d344844f1d58d8015d105e85c65/dotnet-sdk-7.0.403-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5226a5fa-8c0b-474f-b79a-8984ad7c5beb/3113ccbf789c9fd29972835f0f334b7a/dotnet-sdk-8.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
   && mkdir -p dotnet-sdk \
   && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
   && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 1 - 1
fedora/jellyfin.spec

@@ -73,7 +73,7 @@ dotnet publish --configuration Release --self-contained --runtime %{dotnet_runti
 %install
 # Jellyfin files
 %{__mkdir} -p %{buildroot}%{_libdir}/jellyfin %{buildroot}%{_bindir}
-%{__cp} -r Jellyfin.Server/bin/Release/net7.0/%{dotnet_runtime}/publish/* %{buildroot}%{_libdir}/jellyfin
+%{__cp} -r Jellyfin.Server/bin/Release/net8.0/%{dotnet_runtime}/publish/* %{buildroot}%{_libdir}/jellyfin
 %{__install} -D %{SOURCE10} %{buildroot}%{_bindir}/jellyfin
 sed -i -e 's|/usr/lib64|%{_libdir}|g' %{buildroot}%{_bindir}/jellyfin
 

+ 1 - 1
fuzz/Emby.Server.Implementations.Fuzz/Emby.Server.Implementations.Fuzz.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <OutputType>Exe</OutputType>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
   </PropertyGroup>
 
   <ItemGroup>

+ 1 - 1
fuzz/Emby.Server.Implementations.Fuzz/fuzz.sh

@@ -8,4 +8,4 @@ cp bin/Emby.Server.Implementations.dll .
 
 dotnet build
 mkdir -p Findings
-AFL_SKIP_BIN_CHECK=1 afl-fuzz -i "Testcases/$1" -o "Findings/$1" -t 5000 ./bin/Debug/net7.0/Emby.Server.Implementations.Fuzz "$1"
+AFL_SKIP_BIN_CHECK=1 afl-fuzz -i "Testcases/$1" -o "Findings/$1" -t 5000 ./bin/Debug/net8.0/Emby.Server.Implementations.Fuzz "$1"

+ 1 - 1
fuzz/Jellyfin.Api.Fuzz/Jellyfin.Api.Fuzz.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <OutputType>Exe</OutputType>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
   </PropertyGroup>
 
   <ItemGroup>

+ 1 - 1
fuzz/Jellyfin.Api.Fuzz/fuzz.sh

@@ -8,4 +8,4 @@ cp bin/Jellyfin.Api.dll .
 
 dotnet build
 mkdir -p Findings
-AFL_SKIP_BIN_CHECK=1 afl-fuzz -i "Testcases/$1" -o "Findings/$1" -t 5000 ./bin/Debug/net7.0/Jellyfin.Api.Fuzz "$1"
+AFL_SKIP_BIN_CHECK=1 afl-fuzz -i "Testcases/$1" -o "Findings/$1" -t 5000 ./bin/Debug/net8.0/Jellyfin.Api.Fuzz "$1"

+ 1 - 1
global.json

@@ -1,6 +1,6 @@
 {
     "sdk": {
-        "version": "7.0.0",
+        "version": "8.0.0",
         "rollForward": "latestMinor"
     }
 }

+ 3 - 0
jellyfin.ruleset

@@ -140,6 +140,9 @@
     <Rule Id="CA1812" Action="Info" />
     <!-- disable warning CA1822: Member does not access instance data and can be marked as static -->
     <Rule Id="CA1822" Action="Info" />
+    <!-- TODO: Enable -->
+    <!-- CA1861: Prefer 'static readonly' fields over constant array arguments if the called method is called repeatedly and is not mutating the passed array -->
+    <Rule Id="CA1861" Action="Info" />
     <!-- disable warning CA2000: Dispose objects before losing scope -->
     <Rule Id="CA2000" Action="Info" />
     <!-- disable warning CA2253: Named placeholders should not be numeric values -->

+ 3 - 1
src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj

@@ -6,9 +6,11 @@
   </PropertyGroup>
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <!-- TODO: Remove once we update SkiaSharp > 2.88.5 -->
+    <NoWarn>NU1903</NoWarn>
   </PropertyGroup>
 
   <ItemGroup>

+ 1 - 1
src/Jellyfin.Drawing/Jellyfin.Drawing.csproj

@@ -6,7 +6,7 @@
   </PropertyGroup>
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
   </PropertyGroup>

+ 1 - 1
src/Jellyfin.Extensions/Jellyfin.Extensions.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <PublishRepositoryUrl>true</PublishRepositoryUrl>

+ 3 - 1
src/Jellyfin.Extensions/Json/JsonDefaults.cs

@@ -1,5 +1,6 @@
 using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
 using Jellyfin.Extensions.Json.Converters;
 
 namespace Jellyfin.Extensions.Json
@@ -41,7 +42,8 @@ namespace Jellyfin.Extensions.Json
                 new JsonNullableStructConverterFactory(),
                 new JsonDateTimeConverter(),
                 new JsonStringConverter()
-            }
+            },
+            TypeInfoResolver = new DefaultJsonTypeInfoResolver()
         };
 
         private static readonly JsonSerializerOptions _pascalCaseJsonSerializerOptions = new(_jsonSerializerOptions)

+ 1 - 1
src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
   </PropertyGroup>
 

+ 4 - 3
src/Jellyfin.MediaEncoding.Keyframes/FfProbe/FfProbeKeyframeExtractor.cs

@@ -11,8 +11,6 @@ namespace Jellyfin.MediaEncoding.Keyframes.FfProbe;
 /// </summary>
 public static class FfProbeKeyframeExtractor
 {
-    private const string DefaultArguments = "-fflags +genpts -v error -skip_frame nokey -show_entries format=duration -show_entries stream=duration -show_entries packet=pts_time,flags -select_streams v -of csv \"{0}\"";
-
     /// <summary>
     /// Extracts the keyframes using the ffprobe executable at the specified path.
     /// </summary>
@@ -26,7 +24,10 @@ public static class FfProbeKeyframeExtractor
             StartInfo = new ProcessStartInfo
             {
                 FileName = ffProbePath,
-                Arguments = string.Format(CultureInfo.InvariantCulture, DefaultArguments, filePath),
+                Arguments = string.Format(
+                    CultureInfo.InvariantCulture,
+                    "-fflags +genpts -v error -skip_frame nokey -show_entries format=duration -show_entries stream=duration -show_entries packet=pts_time,flags -select_streams v -of csv \"{0}\"",
+                    filePath),
 
                 CreateNoWindow = true,
                 UseShellExecute = false,

+ 1 - 1
src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
   </PropertyGroup>
 

+ 1 - 1
tests/Directory.Build.props

@@ -4,7 +4,7 @@
   <Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <IsPackable>false</IsPackable>
     <CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)/jellyfin-tests.ruleset</CodeAnalysisRuleSet>
   </PropertyGroup>

+ 1 - 1
tests/Jellyfin.Api.Tests/Controllers/UserControllerTests.cs

@@ -109,7 +109,7 @@ public class UserControllerTests
             v.ErrorMessage.Contains("required", StringComparison.CurrentCultureIgnoreCase));
     }
 
-    private IList<ValidationResult> Validate(object model)
+    private List<ValidationResult> Validate(object model)
     {
         var result = new List<ValidationResult>();
         var context = new ValidationContext(model, null, null);

+ 29 - 36
tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs

@@ -7,135 +7,128 @@ using Xunit;
 
 namespace Jellyfin.Extensions.Tests.Json.Converters
 {
-    public static class JsonCommaDelimitedArrayTests
+    public class JsonCommaDelimitedArrayTests
     {
+        private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions()
+        {
+            Converters =
+            {
+                new JsonStringEnumConverter()
+            }
+        };
+
         [Fact]
-        public static void Deserialize_String_Null_Success()
+        public void Deserialize_String_Null_Success()
         {
-            var options = new JsonSerializerOptions();
-            var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": null }", options);
+            var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": null }", _jsonOptions);
             Assert.Null(value?.Value);
         }
 
         [Fact]
-        public static void Deserialize_Empty_Success()
+        public void Deserialize_Empty_Success()
         {
             var desiredValue = new GenericBodyArrayModel<string>
             {
                 Value = Array.Empty<string>()
             };
 
-            var options = new JsonSerializerOptions();
-            var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": """" }", options);
+            var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": """" }", _jsonOptions);
             Assert.Equal(desiredValue.Value, value?.Value);
         }
 
         [Fact]
-        public static void Deserialize_String_Valid_Success()
+        public void Deserialize_String_Valid_Success()
         {
             var desiredValue = new GenericBodyArrayModel<string>
             {
                 Value = new[] { "a", "b", "c" }
             };
 
-            var options = new JsonSerializerOptions();
-            var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": ""a,b,c"" }", options);
+            var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": ""a,b,c"" }", _jsonOptions);
             Assert.Equal(desiredValue.Value, value?.Value);
         }
 
         [Fact]
-        public static void Deserialize_String_Space_Valid_Success()
+        public void Deserialize_String_Space_Valid_Success()
         {
             var desiredValue = new GenericBodyArrayModel<string>
             {
                 Value = new[] { "a", "b", "c" }
             };
 
-            var options = new JsonSerializerOptions();
-            var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": ""a, b, c"" }", options);
+            var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": ""a, b, c"" }", _jsonOptions);
             Assert.Equal(desiredValue.Value, value?.Value);
         }
 
         [Fact]
-        public static void Deserialize_GenericCommandType_Valid_Success()
+        public void Deserialize_GenericCommandType_Valid_Success()
         {
             var desiredValue = new GenericBodyArrayModel<GeneralCommandType>
             {
                 Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
             };
 
-            var options = new JsonSerializerOptions();
-            options.Converters.Add(new JsonStringEnumConverter());
-            var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,MoveDown"" }", options);
+            var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,MoveDown"" }", _jsonOptions);
             Assert.Equal(desiredValue.Value, value?.Value);
         }
 
         [Fact]
-        public static void Deserialize_GenericCommandType_EmptyEntry_Success()
+        public void Deserialize_GenericCommandType_EmptyEntry_Success()
         {
             var desiredValue = new GenericBodyArrayModel<GeneralCommandType>
             {
                 Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
             };
 
-            var options = new JsonSerializerOptions();
-            options.Converters.Add(new JsonStringEnumConverter());
-            var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,,MoveDown"" }", options);
+            var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,,MoveDown"" }", _jsonOptions);
             Assert.Equal(desiredValue.Value, value?.Value);
         }
 
         [Fact]
-        public static void Deserialize_GenericCommandType_Invalid_Success()
+        public void Deserialize_GenericCommandType_Invalid_Success()
         {
             var desiredValue = new GenericBodyArrayModel<GeneralCommandType>
             {
                 Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
             };
 
-            var options = new JsonSerializerOptions();
-            options.Converters.Add(new JsonStringEnumConverter());
-            var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,TotallyNotAVallidCommand,MoveDown"" }", options);
+            var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,TotallyNotAVallidCommand,MoveDown"" }", _jsonOptions);
             Assert.Equal(desiredValue.Value, value?.Value);
         }
 
         [Fact]
-        public static void Deserialize_GenericCommandType_Space_Valid_Success()
+        public void Deserialize_GenericCommandType_Space_Valid_Success()
         {
             var desiredValue = new GenericBodyArrayModel<GeneralCommandType>
             {
                 Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
             };
 
-            var options = new JsonSerializerOptions();
-            options.Converters.Add(new JsonStringEnumConverter());
-            var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp, MoveDown"" }", options);
+            var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp, MoveDown"" }", _jsonOptions);
             Assert.Equal(desiredValue.Value, value?.Value);
         }
 
         [Fact]
-        public static void Deserialize_String_Array_Valid_Success()
+        public void Deserialize_String_Array_Valid_Success()
         {
             var desiredValue = new GenericBodyArrayModel<string>
             {
                 Value = new[] { "a", "b", "c" }
             };
 
-            var options = new JsonSerializerOptions();
-            var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": [""a"",""b"",""c""] }", options);
+            var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": [""a"",""b"",""c""] }", _jsonOptions);
             Assert.Equal(desiredValue.Value, value?.Value);
         }
 
         [Fact]
-        public static void Deserialize_GenericCommandType_Array_Valid_Success()
+        public void Deserialize_GenericCommandType_Array_Valid_Success()
         {
             var desiredValue = new GenericBodyArrayModel<GeneralCommandType>
             {
                 Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
             };
 
-            var options = new JsonSerializerOptions();
-            options.Converters.Add(new JsonStringEnumConverter());
-            var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", options);
+            var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", _jsonOptions);
             Assert.Equal(desiredValue.Value, value?.Value);
         }
     }

+ 21 - 22
tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedIReadOnlyListTests.cs

@@ -6,86 +6,85 @@ using Xunit;
 
 namespace Jellyfin.Extensions.Tests.Json.Converters
 {
-    public static class JsonCommaDelimitedIReadOnlyListTests
+    public class JsonCommaDelimitedIReadOnlyListTests
     {
+        private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions()
+        {
+            Converters =
+            {
+                new JsonStringEnumConverter()
+            }
+        };
+
         [Fact]
-        public static void Deserialize_String_Valid_Success()
+        public void Deserialize_String_Valid_Success()
         {
             var desiredValue = new GenericBodyIReadOnlyListModel<string>
             {
                 Value = new[] { "a", "b", "c" }
             };
 
-            var options = new JsonSerializerOptions();
-            var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<string>>(@"{ ""Value"": ""a,b,c"" }", options);
+            var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<string>>(@"{ ""Value"": ""a,b,c"" }", _jsonOptions);
             Assert.Equal(desiredValue.Value, value?.Value);
         }
 
         [Fact]
-        public static void Deserialize_String_Space_Valid_Success()
+        public void Deserialize_String_Space_Valid_Success()
         {
             var desiredValue = new GenericBodyIReadOnlyListModel<string>
             {
                 Value = new[] { "a", "b", "c" }
             };
 
-            var options = new JsonSerializerOptions();
-            var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<string>>(@"{ ""Value"": ""a, b, c"" }", options);
+            var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<string>>(@"{ ""Value"": ""a, b, c"" }", _jsonOptions);
             Assert.Equal(desiredValue.Value, value?.Value);
         }
 
         [Fact]
-        public static void Deserialize_GenericCommandType_Valid_Success()
+        public void Deserialize_GenericCommandType_Valid_Success()
         {
             var desiredValue = new GenericBodyIReadOnlyListModel<GeneralCommandType>
             {
                 Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
             };
 
-            var options = new JsonSerializerOptions();
-            options.Converters.Add(new JsonStringEnumConverter());
-            var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,MoveDown"" }", options);
+            var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,MoveDown"" }", _jsonOptions);
             Assert.Equal(desiredValue.Value, value?.Value);
         }
 
         [Fact]
-        public static void Deserialize_GenericCommandType_Space_Valid_Success()
+        public void Deserialize_GenericCommandType_Space_Valid_Success()
         {
             var desiredValue = new GenericBodyIReadOnlyListModel<GeneralCommandType>
             {
                 Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
             };
 
-            var options = new JsonSerializerOptions();
-            options.Converters.Add(new JsonStringEnumConverter());
-            var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp, MoveDown"" }", options);
+            var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp, MoveDown"" }", _jsonOptions);
             Assert.Equal(desiredValue.Value, value?.Value);
         }
 
         [Fact]
-        public static void Deserialize_String_Array_Valid_Success()
+        public void Deserialize_String_Array_Valid_Success()
         {
             var desiredValue = new GenericBodyIReadOnlyListModel<string>
             {
                 Value = new[] { "a", "b", "c" }
             };
 
-            var options = new JsonSerializerOptions();
-            var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<string>>(@"{ ""Value"": [""a"",""b"",""c""] }", options);
+            var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<string>>(@"{ ""Value"": [""a"",""b"",""c""] }", _jsonOptions);
             Assert.Equal(desiredValue.Value, value?.Value);
         }
 
         [Fact]
-        public static void Deserialize_GenericCommandType_Array_Valid_Success()
+        public void Deserialize_GenericCommandType_Array_Valid_Success()
         {
             var desiredValue = new GenericBodyIReadOnlyListModel<GeneralCommandType>
             {
                 Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
             };
 
-            var options = new JsonSerializerOptions();
-            options.Converters.Add(new JsonStringEnumConverter());
-            var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", options);
+            var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", _jsonOptions);
             Assert.Equal(desiredValue.Value, value?.Value);
         }
     }

+ 1 - 0
tests/Jellyfin.Networking.Tests/NetworkParseTests.cs

@@ -10,6 +10,7 @@ using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging.Abstractions;
 using Moq;
 using Xunit;
+using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager;
 
 namespace Jellyfin.Networking.Tests
 {

+ 7 - 7
tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs

@@ -27,7 +27,7 @@ namespace Jellyfin.Providers.Tests.Manager
 {
     public partial class ItemImageProviderTests
     {
-        private const string TestDataImagePath = "Test Data/Images/blank{0}.jpg";
+        private static readonly CompositeFormat _testDataImagePath = CompositeFormat.Parse("Test Data/Images/blank{0}.jpg");
 
         [GeneratedRegex("[0-9]+")]
         private static partial Regex NumbersRegex();
@@ -275,7 +275,7 @@ namespace Jellyfin.Providers.Tests.Manager
             {
                 HasImage = true,
                 Format = ImageFormat.Jpg,
-                Path = responseHasPath ? string.Format(CultureInfo.InvariantCulture, TestDataImagePath, 0) : null,
+                Path = responseHasPath ? string.Format(CultureInfo.InvariantCulture, _testDataImagePath, 0) : null,
                 Protocol = protocol
             };
 
@@ -563,21 +563,21 @@ namespace Jellyfin.Providers.Tests.Manager
             mockFileSystem.Setup(fs => fs.GetFilePaths(It.IsAny<string>(), It.IsAny<bool>()))
                 .Returns(new[]
                 {
-                    string.Format(CultureInfo.InvariantCulture, TestDataImagePath, 0),
-                    string.Format(CultureInfo.InvariantCulture, TestDataImagePath, 1)
+                    string.Format(CultureInfo.InvariantCulture, _testDataImagePath, 0),
+                    string.Format(CultureInfo.InvariantCulture, _testDataImagePath, 1)
                 });
 
             return new ItemImageProvider(new NullLogger<ItemImageProvider>(), providerManager, mockFileSystem.Object);
         }
 
-        private static BaseItem GetItemWithImages(ImageType type, int count, bool validPaths)
+        private static Video GetItemWithImages(ImageType type, int count, bool validPaths)
         {
             // Has to exist for querying DateModified time on file, results stored but not checked so not populating
             BaseItem.FileSystem ??= Mock.Of<IFileSystem>();
 
             var item = new Video();
 
-            var path = validPaths ? TestDataImagePath : "invalid path {0}";
+            var path = validPaths ? _testDataImagePath.Format : "invalid path {0}";
             for (int i = 0; i < count; i++)
             {
                 item.SetImagePath(type, i, new FileSystemMetadata
@@ -604,7 +604,7 @@ namespace Jellyfin.Providers.Tests.Manager
         /// </summary>
         private static LocalImageInfo[] GetImages(ImageType type, int count, bool validPaths)
         {
-            var path = validPaths ? TestDataImagePath : "invalid path {0}";
+            var path = validPaths ? _testDataImagePath.Format : "invalid path {0}";
             var images = new LocalImageInfo[count];
             for (int i = 0; i < count; i++)
             {

+ 5 - 6
tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs

@@ -9,22 +9,21 @@ namespace Jellyfin.Server.Implementations.Tests.Sorting
 {
     public class AiredEpisodeOrderComparerTests
     {
+        private readonly AiredEpisodeOrderComparer _cmp = new AiredEpisodeOrderComparer();
+
         [Theory]
         [ClassData(typeof(EpisodeBadData))]
         public void Compare_GivenNull_ThrowsArgumentNullException(BaseItem? x, BaseItem? y)
         {
-            var cmp = new AiredEpisodeOrderComparer();
-            Assert.Throws<ArgumentNullException>(() => cmp.Compare(x, y));
+            Assert.Throws<ArgumentNullException>(() => _cmp.Compare(x, y));
         }
 
         [Theory]
         [ClassData(typeof(EpisodeTestData))]
         public void AiredEpisodeOrderCompareTest(BaseItem x, BaseItem y, int expected)
         {
-            var cmp = new AiredEpisodeOrderComparer();
-
-            Assert.Equal(expected, cmp.Compare(x, y));
-            Assert.Equal(-expected, cmp.Compare(y, x));
+            Assert.Equal(expected, _cmp.Compare(x, y));
+            Assert.Equal(-expected, _cmp.Compare(y, x));
         }
 
         private sealed class EpisodeBadData : TheoryData<BaseItem?, BaseItem?>

+ 1 - 1
tests/Jellyfin.Server.Implementations.Tests/Sorting/IndexNumberComparerTests.cs

@@ -9,7 +9,7 @@ namespace Jellyfin.Server.Implementations.Tests.Sorting;
 
 public class IndexNumberComparerTests
 {
-    private readonly IBaseItemComparer _cmp = new IndexNumberComparer();
+    private readonly IndexNumberComparer _cmp = new IndexNumberComparer();
 
     public static TheoryData<BaseItem?, BaseItem?> Compare_GivenNull_ThrowsArgumentNullException_TestData()
         => new()

+ 1 - 1
tests/Jellyfin.Server.Implementations.Tests/Sorting/ParentIndexNumberComparerTests.cs

@@ -9,7 +9,7 @@ namespace Jellyfin.Server.Implementations.Tests.Sorting;
 
 public class ParentIndexNumberComparerTests
 {
-    private readonly IBaseItemComparer _cmp = new ParentIndexNumberComparer();
+    private readonly ParentIndexNumberComparer _cmp = new ParentIndexNumberComparer();
 
     public static TheoryData<BaseItem?, BaseItem?> Compare_GivenNull_ThrowsArgumentNullException_TestData()
         => new()

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