2
0
Эх сурвалжийг харах

Merge branch 'jellyfin:master' into master

Abdulmohsen 1 жил өмнө
parent
commit
9d5dc4d71b
100 өөрчлөгдсөн 1170 нэмэгдсэн , 1264 устгасан
  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. 2 2
      .config/dotnet-tools.json
  6. 4 4
      .github/workflows/ci-codeql-analysis.yml
  7. 4 4
      .github/workflows/ci-openapi.yml
  8. 2 2
      .github/workflows/ci-tests.yml
  9. 2 2
      .vscode/launch.json
  10. 28 29
      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. 3 0
      Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs
  16. 0 363
      Emby.Dlna/Main/DlnaEntryPoint.cs
  17. 387 0
      Emby.Dlna/Main/DlnaHost.cs
  18. 1 5
      Emby.Naming/Emby.Naming.csproj
  19. 1 1
      Emby.Photos/Emby.Photos.csproj
  20. 5 3
      Emby.Server.Implementations/ApplicationHost.cs
  21. 1 1
      Emby.Server.Implementations/Emby.Server.Implementations.csproj
  22. 1 1
      Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
  23. 285 383
      Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
  24. 2 3
      Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
  25. 2 4
      Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
  26. 3 1
      Emby.Server.Implementations/Localization/Core/af.json
  27. 2 1
      Emby.Server.Implementations/Localization/Core/ar.json
  28. 3 1
      Emby.Server.Implementations/Localization/Core/lt-LT.json
  29. 3 1
      Emby.Server.Implementations/Localization/Core/mr.json
  30. 3 1
      Emby.Server.Implementations/Localization/Core/pt-BR.json
  31. 3 1
      Emby.Server.Implementations/Localization/Core/ro.json
  32. 6 5
      Emby.Server.Implementations/Plugins/PluginManager.cs
  33. 1 2
      Emby.Server.Implementations/Updates/InstallationManager.cs
  34. 2 3
      Jellyfin.Api/Auth/CustomAuthenticationHandler.cs
  35. 1 0
      Jellyfin.Api/Controllers/ActivityLogController.cs
  36. 1 0
      Jellyfin.Api/Controllers/ApiKeyController.cs
  37. 1 0
      Jellyfin.Api/Controllers/CollectionController.cs
  38. 1 0
      Jellyfin.Api/Controllers/ConfigurationController.cs
  39. 1 0
      Jellyfin.Api/Controllers/DevicesController.cs
  40. 1 0
      Jellyfin.Api/Controllers/DlnaController.cs
  41. 1 0
      Jellyfin.Api/Controllers/DlnaServerController.cs
  42. 2 1
      Jellyfin.Api/Controllers/EnvironmentController.cs
  43. 1 1
      Jellyfin.Api/Controllers/HlsSegmentController.cs
  44. 10 9
      Jellyfin.Api/Controllers/ImageController.cs
  45. 1 0
      Jellyfin.Api/Controllers/ItemLookupController.cs
  46. 1 0
      Jellyfin.Api/Controllers/ItemRefreshController.cs
  47. 1 0
      Jellyfin.Api/Controllers/ItemUpdateController.cs
  48. 1 0
      Jellyfin.Api/Controllers/LibraryController.cs
  49. 1 0
      Jellyfin.Api/Controllers/LibraryStructureController.cs
  50. 1 0
      Jellyfin.Api/Controllers/LiveTvController.cs
  51. 1 0
      Jellyfin.Api/Controllers/LocalizationController.cs
  52. 1 1
      Jellyfin.Api/Controllers/MusicGenresController.cs
  53. 1 0
      Jellyfin.Api/Controllers/PackageController.cs
  54. 1 0
      Jellyfin.Api/Controllers/PluginsController.cs
  55. 1 0
      Jellyfin.Api/Controllers/RemoteImageController.cs
  56. 1 0
      Jellyfin.Api/Controllers/ScheduledTasksController.cs
  57. 1 0
      Jellyfin.Api/Controllers/SessionController.cs
  58. 2 1
      Jellyfin.Api/Controllers/StartupController.cs
  59. 1 0
      Jellyfin.Api/Controllers/SubtitleController.cs
  60. 1 0
      Jellyfin.Api/Controllers/SyncPlayController.cs
  61. 1 0
      Jellyfin.Api/Controllers/SystemController.cs
  62. 1 0
      Jellyfin.Api/Controllers/UserController.cs
  63. 1 0
      Jellyfin.Api/Controllers/VideosController.cs
  64. 11 11
      Jellyfin.Api/Extensions/DtoExtensions.cs
  65. 2 2
      Jellyfin.Api/Helpers/DynamicHlsHelper.cs
  66. 1 1
      Jellyfin.Api/Helpers/HlsHelpers.cs
  67. 7 7
      Jellyfin.Api/Helpers/StreamingHelpers.cs
  68. 2 2
      Jellyfin.Api/Helpers/TranscodingJobHelper.cs
  69. 1 1
      Jellyfin.Api/Jellyfin.Api.csproj
  70. 1 1
      Jellyfin.Api/Middleware/BaseUrlRedirectionMiddleware.cs
  71. 0 1
      Jellyfin.Api/Middleware/LanFilteringMiddleware.cs
  72. 5 5
      Jellyfin.Api/Models/StreamingDtos/StreamState.cs
  73. 1 5
      Jellyfin.Data/Jellyfin.Data.csproj
  74. 0 176
      Jellyfin.Networking/Configuration/NetworkConfiguration.cs
  75. 0 20
      Jellyfin.Networking/Configuration/NetworkConfigurationExtensions.cs
  76. 0 23
      Jellyfin.Networking/Configuration/NetworkConfigurationFactory.cs
  77. 0 24
      Jellyfin.Networking/Configuration/NetworkConfigurationStore.cs
  78. 3 3
      Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs
  79. 1 1
      Jellyfin.Networking/Jellyfin.Networking.csproj
  80. 41 42
      Jellyfin.Networking/Manager/NetworkManager.cs
  81. 1 1
      Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
  82. 1 1
      Jellyfin.Server.Implementations/Security/AuthorizationContext.cs
  83. 2 2
      Jellyfin.Server.Implementations/Users/UserManager.cs
  84. 1 1
      Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs
  85. 9 9
      Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
  86. 1 1
      Jellyfin.Server/Jellyfin.Server.csproj
  87. 1 1
      Jellyfin.Server/Migrations/PreStartupRoutines/MigrateNetworkConfiguration.cs
  88. 1 5
      Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs
  89. 1 1
      Jellyfin.Server/Program.cs
  90. 1 2
      Jellyfin.Server/Startup.cs
  91. 1 1
      MediaBrowser.Common/Api/Policies.cs
  92. 1 2
      MediaBrowser.Common/MediaBrowser.Common.csproj
  93. 175 0
      MediaBrowser.Common/Net/NetworkConfiguration.cs
  94. 19 0
      MediaBrowser.Common/Net/NetworkConfigurationExtensions.cs
  95. 22 0
      MediaBrowser.Common/Net/NetworkConfigurationFactory.cs
  96. 23 0
      MediaBrowser.Common/Net/NetworkConfigurationStore.cs
  97. 3 3
      MediaBrowser.Common/Net/NetworkConstants.cs
  98. 24 42
      MediaBrowser.Common/Net/NetworkUtils.cs
  99. 0 19
      MediaBrowser.Common/Plugins/IPluginServiceRegistrator.cs
  100. 1 2
      MediaBrowser.Controller/MediaBrowser.Controller.csproj

+ 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'

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

@@ -3,10 +3,10 @@
   "isRoot": true,
   "tools": {
     "dotnet-ef": {
-      "version": "7.0.13",
+      "version": "8.0.0",
       "commands": [
         "dotnet-ef"
       ]
     }
   }
-}
+}

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

@@ -24,14 +24,14 @@ 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@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
+      uses: github/codeql-action/init@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8
       with:
         languages: ${{ matrix.language }}
         queries: +security-extended
     - name: Autobuild
-      uses: github/codeql-action/autobuild@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
+      uses: github/codeql-action/autobuild@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
+      uses: github/codeql-action/analyze@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8

+ 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:

+ 2 - 2
.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:
@@ -34,7 +34,7 @@ jobs:
           --verbosity minimal
 
       - name: Merge code coverage results
-        uses: danielpalme/ReportGenerator-GitHub-Action@873ee34c88a6234bdab7fd264d3666fd1ab417f7 # 5
+        uses: danielpalme/ReportGenerator-GitHub-Action@4d510cbed8a05af5aefea46c7fd6e05b95844c89 # 5
         with:
           reports: "**/coverage.cobertura.xml"
           targetdir: "merged/"

+ 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",

+ 28 - 29
Directory.Packages.props

@@ -23,32 +23,31 @@
     <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" />
     <PackageVersion Include="Mono.Nat" Version="3.0.4" />
     <PackageVersion Include="Moq" Version="4.18.4" />
@@ -58,9 +57,9 @@
     <PackageVersion Include="prometheus-net.AspNetCore" Version="8.1.0" />
     <PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
     <PackageVersion Include="prometheus-net" Version="8.1.0" />
-    <PackageVersion Include="Serilog.AspNetCore" Version="7.0.0" />
+    <PackageVersion Include="Serilog.AspNetCore" Version="8.0.0" />
     <PackageVersion Include="Serilog.Enrichers.Thread" Version="3.1.0" />
-    <PackageVersion Include="Serilog.Settings.Configuration" Version="7.0.1" />
+    <PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.0" />
     <PackageVersion Include="Serilog.Sinks.Async" Version="1.5.0" />
     <PackageVersion Include="Serilog.Sinks.Console" Version="5.0.0" />
     <PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
@@ -77,9 +76,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 +87,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>

+ 3 - 0
Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs

@@ -5,6 +5,7 @@ using System.Net.Http;
 using System.Text;
 using Emby.Dlna.ConnectionManager;
 using Emby.Dlna.ContentDirectory;
+using Emby.Dlna.Main;
 using Emby.Dlna.MediaReceiverRegistrar;
 using Emby.Dlna.Ssdp;
 using MediaBrowser.Common.Net;
@@ -65,5 +66,7 @@ public static class DlnaServiceCollectionExtensions
         {
             IsShared = true
         });
+
+        services.AddHostedService<DlnaHost>();
     }
 }

+ 0 - 363
Emby.Dlna/Main/DlnaEntryPoint.cs

@@ -1,363 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-using System;
-using System.Globalization;
-using System.Linq;
-using System.Net.Http;
-using System.Net.Sockets;
-using System.Threading.Tasks;
-using Emby.Dlna.PlayTo;
-using Emby.Dlna.Ssdp;
-using Jellyfin.Networking.Configuration;
-using Jellyfin.Networking.Extensions;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Drawing;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Plugins;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Globalization;
-using Microsoft.Extensions.Logging;
-using Rssdp;
-using Rssdp.Infrastructure;
-
-namespace Emby.Dlna.Main
-{
-    public sealed class DlnaEntryPoint : IServerEntryPoint, IRunBeforeStartup
-    {
-        private readonly IServerConfigurationManager _config;
-        private readonly ILogger<DlnaEntryPoint> _logger;
-        private readonly IServerApplicationHost _appHost;
-        private readonly ISessionManager _sessionManager;
-        private readonly IHttpClientFactory _httpClientFactory;
-        private readonly ILibraryManager _libraryManager;
-        private readonly IUserManager _userManager;
-        private readonly IDlnaManager _dlnaManager;
-        private readonly IImageProcessor _imageProcessor;
-        private readonly IUserDataManager _userDataManager;
-        private readonly ILocalizationManager _localization;
-        private readonly IMediaSourceManager _mediaSourceManager;
-        private readonly IMediaEncoder _mediaEncoder;
-        private readonly IDeviceDiscovery _deviceDiscovery;
-        private readonly ISsdpCommunicationsServer _communicationsServer;
-        private readonly INetworkManager _networkManager;
-        private readonly object _syncLock = new();
-        private readonly bool _disabled;
-
-        private PlayToManager _manager;
-        private SsdpDevicePublisher _publisher;
-
-        private bool _disposed;
-
-        public DlnaEntryPoint(
-            IServerConfigurationManager config,
-            ILoggerFactory loggerFactory,
-            IServerApplicationHost appHost,
-            ISessionManager sessionManager,
-            IHttpClientFactory httpClientFactory,
-            ILibraryManager libraryManager,
-            IUserManager userManager,
-            IDlnaManager dlnaManager,
-            IImageProcessor imageProcessor,
-            IUserDataManager userDataManager,
-            ILocalizationManager localizationManager,
-            IMediaSourceManager mediaSourceManager,
-            IDeviceDiscovery deviceDiscovery,
-            IMediaEncoder mediaEncoder,
-            ISsdpCommunicationsServer communicationsServer,
-            INetworkManager networkManager)
-        {
-            _config = config;
-            _appHost = appHost;
-            _sessionManager = sessionManager;
-            _httpClientFactory = httpClientFactory;
-            _libraryManager = libraryManager;
-            _userManager = userManager;
-            _dlnaManager = dlnaManager;
-            _imageProcessor = imageProcessor;
-            _userDataManager = userDataManager;
-            _localization = localizationManager;
-            _mediaSourceManager = mediaSourceManager;
-            _deviceDiscovery = deviceDiscovery;
-            _mediaEncoder = mediaEncoder;
-            _communicationsServer = communicationsServer;
-            _networkManager = networkManager;
-            _logger = loggerFactory.CreateLogger<DlnaEntryPoint>();
-
-            var netConfig = config.GetConfiguration<NetworkConfiguration>(NetworkConfigurationStore.StoreKey);
-            _disabled = appHost.ListenWithHttps && netConfig.RequireHttps;
-
-            if (_disabled && _config.GetDlnaConfiguration().EnableServer)
-            {
-                _logger.LogError("The DLNA specification does not support HTTPS.");
-            }
-        }
-
-        public async Task RunAsync()
-        {
-            await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false);
-
-            if (_disabled)
-            {
-                // No use starting as dlna won't work, as we're running purely on HTTPS.
-                return;
-            }
-
-            ReloadComponents();
-
-            _config.NamedConfigurationUpdated += OnNamedConfigurationUpdated;
-        }
-
-        private void OnNamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
-        {
-            if (string.Equals(e.Key, "dlna", StringComparison.OrdinalIgnoreCase))
-            {
-                ReloadComponents();
-            }
-        }
-
-        private void ReloadComponents()
-        {
-            var options = _config.GetDlnaConfiguration();
-            StartDeviceDiscovery();
-
-            if (options.EnableServer)
-            {
-                StartDevicePublisher(options);
-            }
-            else
-            {
-                DisposeDevicePublisher();
-            }
-
-            if (options.EnablePlayTo)
-            {
-                StartPlayToManager();
-            }
-            else
-            {
-                DisposePlayToManager();
-            }
-        }
-
-        private void StartDeviceDiscovery()
-        {
-            try
-            {
-                ((DeviceDiscovery)_deviceDiscovery).Start(_communicationsServer);
-            }
-            catch (Exception ex)
-            {
-                _logger.LogError(ex, "Error starting device discovery");
-            }
-        }
-
-        public void StartDevicePublisher(Configuration.DlnaOptions options)
-        {
-            if (_publisher is not null)
-            {
-                return;
-            }
-
-            try
-            {
-                _publisher = new SsdpDevicePublisher(
-                    _communicationsServer,
-                    Environment.OSVersion.Platform.ToString(),
-                    // Can not use VersionString here since that includes OS and version
-                    Environment.OSVersion.Version.ToString(),
-                    _config.GetDlnaConfiguration().SendOnlyMatchedHost)
-                {
-                    LogFunction = (msg) => _logger.LogDebug("{Msg}", msg),
-                    SupportPnpRootDevice = false
-                };
-
-                RegisterServerEndpoints();
-
-                if (options.BlastAliveMessages)
-                {
-                    _publisher.StartSendingAliveNotifications(TimeSpan.FromSeconds(options.BlastAliveMessageIntervalSeconds));
-                }
-            }
-            catch (Exception ex)
-            {
-                _logger.LogError(ex, "Error registering endpoint");
-            }
-        }
-
-        private void RegisterServerEndpoints()
-        {
-            var udn = CreateUuid(_appHost.SystemId);
-            var descriptorUri = "/dlna/" + udn + "/description.xml";
-
-            // Only get bind addresses in LAN
-            // IPv6 is currently unsupported
-            var validInterfaces = _networkManager.GetInternalBindAddresses()
-                .Where(x => x.Address is not null)
-                .Where(x => x.AddressFamily != AddressFamily.InterNetworkV6)
-                .ToList();
-
-            if (validInterfaces.Count == 0)
-            {
-                // No interfaces returned, fall back to loopback
-                validInterfaces = _networkManager.GetLoopbacks().ToList();
-            }
-
-            foreach (var intf in validInterfaces)
-            {
-                var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
-
-                _logger.LogInformation("Registering publisher for {ResourceName} on {DeviceAddress}", fullService, intf.Address);
-
-                var uri = new UriBuilder(_appHost.GetApiUrlForLocalAccess(intf.Address, false) + descriptorUri);
-
-                var device = new SsdpRootDevice
-                {
-                    CacheLifetime = TimeSpan.FromSeconds(1800), // How long SSDP clients can cache this info.
-                    Location = uri.Uri, // Must point to the URL that serves your devices UPnP description document.
-                    Address = intf.Address,
-                    PrefixLength = NetworkExtensions.MaskToCidr(intf.Subnet.Prefix),
-                    FriendlyName = "Jellyfin",
-                    Manufacturer = "Jellyfin",
-                    ModelName = "Jellyfin Server",
-                    Uuid = udn
-                    // This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc.
-                };
-
-                SetProperties(device, fullService);
-                _publisher.AddDevice(device);
-
-                var embeddedDevices = new[]
-                {
-                    "urn:schemas-upnp-org:service:ContentDirectory:1",
-                    "urn:schemas-upnp-org:service:ConnectionManager:1",
-                    // "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"
-                };
-
-                foreach (var subDevice in embeddedDevices)
-                {
-                    var embeddedDevice = new SsdpEmbeddedDevice
-                    {
-                        FriendlyName = device.FriendlyName,
-                        Manufacturer = device.Manufacturer,
-                        ModelName = device.ModelName,
-                        Uuid = udn
-                        // This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc.
-                    };
-
-                    SetProperties(embeddedDevice, subDevice);
-                    device.AddDevice(embeddedDevice);
-                }
-            }
-        }
-
-        private static string CreateUuid(string text)
-        {
-            if (!Guid.TryParse(text, out var guid))
-            {
-                guid = text.GetMD5();
-            }
-
-            return guid.ToString("D", CultureInfo.InvariantCulture);
-        }
-
-        private static void SetProperties(SsdpDevice device, string fullDeviceType)
-        {
-            var serviceParts = fullDeviceType
-                .Replace("urn:", string.Empty, StringComparison.OrdinalIgnoreCase)
-                .Replace(":1", string.Empty, StringComparison.OrdinalIgnoreCase)
-                .Split(':');
-
-            device.DeviceTypeNamespace = serviceParts[0].Replace('.', '-');
-            device.DeviceClass = serviceParts[1];
-            device.DeviceType = serviceParts[2];
-        }
-
-        private void StartPlayToManager()
-        {
-            lock (_syncLock)
-            {
-                if (_manager is not null)
-                {
-                    return;
-                }
-
-                try
-                {
-                    _manager = new PlayToManager(
-                        _logger,
-                        _sessionManager,
-                        _libraryManager,
-                        _userManager,
-                        _dlnaManager,
-                        _appHost,
-                        _imageProcessor,
-                        _deviceDiscovery,
-                        _httpClientFactory,
-                        _userDataManager,
-                        _localization,
-                        _mediaSourceManager,
-                        _mediaEncoder);
-
-                    _manager.Start();
-                }
-                catch (Exception ex)
-                {
-                    _logger.LogError(ex, "Error starting PlayTo manager");
-                }
-            }
-        }
-
-        private void DisposePlayToManager()
-        {
-            lock (_syncLock)
-            {
-                if (_manager is not null)
-                {
-                    try
-                    {
-                        _logger.LogInformation("Disposing PlayToManager");
-                        _manager.Dispose();
-                    }
-                    catch (Exception ex)
-                    {
-                        _logger.LogError(ex, "Error disposing PlayTo manager");
-                    }
-
-                    _manager = null;
-                }
-            }
-        }
-
-        public void DisposeDevicePublisher()
-        {
-            if (_publisher is not null)
-            {
-                _logger.LogInformation("Disposing SsdpDevicePublisher");
-                _publisher.Dispose();
-                _publisher = null;
-            }
-        }
-
-        /// <inheritdoc />
-        public void Dispose()
-        {
-            if (_disposed)
-            {
-                return;
-            }
-
-            DisposeDevicePublisher();
-            DisposePlayToManager();
-            _disposed = true;
-        }
-    }
-}

+ 387 - 0
Emby.Dlna/Main/DlnaHost.cs

@@ -0,0 +1,387 @@
+#pragma warning disable CA1031 // Do not catch general exception types.
+
+using System;
+using System.Globalization;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+using Emby.Dlna.PlayTo;
+using Emby.Dlna.Ssdp;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Globalization;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Rssdp;
+using Rssdp.Infrastructure;
+
+namespace Emby.Dlna.Main;
+
+/// <summary>
+/// A <see cref="IHostedService"/> that manages a DLNA server.
+/// </summary>
+public sealed class DlnaHost : IHostedService, IDisposable
+{
+    private readonly ILogger<DlnaHost> _logger;
+    private readonly IServerConfigurationManager _config;
+    private readonly IServerApplicationHost _appHost;
+    private readonly ISessionManager _sessionManager;
+    private readonly IHttpClientFactory _httpClientFactory;
+    private readonly ILibraryManager _libraryManager;
+    private readonly IUserManager _userManager;
+    private readonly IDlnaManager _dlnaManager;
+    private readonly IImageProcessor _imageProcessor;
+    private readonly IUserDataManager _userDataManager;
+    private readonly ILocalizationManager _localization;
+    private readonly IMediaSourceManager _mediaSourceManager;
+    private readonly IMediaEncoder _mediaEncoder;
+    private readonly IDeviceDiscovery _deviceDiscovery;
+    private readonly ISsdpCommunicationsServer _communicationsServer;
+    private readonly INetworkManager _networkManager;
+    private readonly object _syncLock = new();
+
+    private SsdpDevicePublisher? _publisher;
+    private PlayToManager? _manager;
+    private bool _disposed;
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="DlnaHost"/> class.
+    /// </summary>
+    /// <param name="config">The <see cref="IServerConfigurationManager"/>.</param>
+    /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
+    /// <param name="appHost">The <see cref="IServerApplicationHost"/>.</param>
+    /// <param name="sessionManager">The <see cref="ISessionManager"/>.</param>
+    /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param>
+    /// <param name="libraryManager">The <see cref="ILibraryManager"/>.</param>
+    /// <param name="userManager">The <see cref="IUserManager"/>.</param>
+    /// <param name="dlnaManager">The <see cref="IDlnaManager"/>.</param>
+    /// <param name="imageProcessor">The <see cref="IImageProcessor"/>.</param>
+    /// <param name="userDataManager">The <see cref="IUserDataManager"/>.</param>
+    /// <param name="localizationManager">The <see cref="ILocalizationManager"/>.</param>
+    /// <param name="mediaSourceManager">The <see cref="IMediaSourceManager"/>.</param>
+    /// <param name="deviceDiscovery">The <see cref="IDeviceDiscovery"/>.</param>
+    /// <param name="mediaEncoder">The <see cref="IMediaEncoder"/>.</param>
+    /// <param name="communicationsServer">The <see cref="ISsdpCommunicationsServer"/>.</param>
+    /// <param name="networkManager">The <see cref="INetworkManager"/>.</param>
+    public DlnaHost(
+        IServerConfigurationManager config,
+        ILoggerFactory loggerFactory,
+        IServerApplicationHost appHost,
+        ISessionManager sessionManager,
+        IHttpClientFactory httpClientFactory,
+        ILibraryManager libraryManager,
+        IUserManager userManager,
+        IDlnaManager dlnaManager,
+        IImageProcessor imageProcessor,
+        IUserDataManager userDataManager,
+        ILocalizationManager localizationManager,
+        IMediaSourceManager mediaSourceManager,
+        IDeviceDiscovery deviceDiscovery,
+        IMediaEncoder mediaEncoder,
+        ISsdpCommunicationsServer communicationsServer,
+        INetworkManager networkManager)
+    {
+        _config = config;
+        _appHost = appHost;
+        _sessionManager = sessionManager;
+        _httpClientFactory = httpClientFactory;
+        _libraryManager = libraryManager;
+        _userManager = userManager;
+        _dlnaManager = dlnaManager;
+        _imageProcessor = imageProcessor;
+        _userDataManager = userDataManager;
+        _localization = localizationManager;
+        _mediaSourceManager = mediaSourceManager;
+        _deviceDiscovery = deviceDiscovery;
+        _mediaEncoder = mediaEncoder;
+        _communicationsServer = communicationsServer;
+        _networkManager = networkManager;
+        _logger = loggerFactory.CreateLogger<DlnaHost>();
+    }
+
+    /// <inheritdoc />
+    public async Task StartAsync(CancellationToken cancellationToken)
+    {
+        var netConfig = _config.GetConfiguration<NetworkConfiguration>(NetworkConfigurationStore.StoreKey);
+        if (_appHost.ListenWithHttps && netConfig.RequireHttps)
+        {
+            if (_config.GetDlnaConfiguration().EnableServer)
+            {
+                _logger.LogError("The DLNA specification does not support HTTPS.");
+            }
+
+            // No use starting as dlna won't work, as we're running purely on HTTPS.
+            return;
+        }
+
+        await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false);
+        ReloadComponents();
+
+        _config.NamedConfigurationUpdated += OnNamedConfigurationUpdated;
+    }
+
+    /// <inheritdoc />
+    public Task StopAsync(CancellationToken cancellationToken)
+    {
+        Stop();
+
+        return Task.CompletedTask;
+    }
+
+    /// <inheritdoc />
+    public void Dispose()
+    {
+        if (!_disposed)
+        {
+            Stop();
+            _disposed = true;
+        }
+    }
+
+    private void OnNamedConfigurationUpdated(object? sender, ConfigurationUpdateEventArgs e)
+    {
+        if (string.Equals(e.Key, "dlna", StringComparison.OrdinalIgnoreCase))
+        {
+            ReloadComponents();
+        }
+    }
+
+    private void ReloadComponents()
+    {
+        var options = _config.GetDlnaConfiguration();
+        StartDeviceDiscovery();
+
+        if (options.EnableServer)
+        {
+            StartDevicePublisher(options);
+        }
+        else
+        {
+            DisposeDevicePublisher();
+        }
+
+        if (options.EnablePlayTo)
+        {
+            StartPlayToManager();
+        }
+        else
+        {
+            DisposePlayToManager();
+        }
+    }
+
+    private static string CreateUuid(string text)
+    {
+        if (!Guid.TryParse(text, out var guid))
+        {
+            guid = text.GetMD5();
+        }
+
+        return guid.ToString("D", CultureInfo.InvariantCulture);
+    }
+
+    private static void SetProperties(SsdpDevice device, string fullDeviceType)
+    {
+        var serviceParts = fullDeviceType
+            .Replace("urn:", string.Empty, StringComparison.OrdinalIgnoreCase)
+            .Replace(":1", string.Empty, StringComparison.OrdinalIgnoreCase)
+            .Split(':');
+
+        device.DeviceTypeNamespace = serviceParts[0].Replace('.', '-');
+        device.DeviceClass = serviceParts[1];
+        device.DeviceType = serviceParts[2];
+    }
+
+    private void StartDeviceDiscovery()
+    {
+        try
+        {
+            ((DeviceDiscovery)_deviceDiscovery).Start(_communicationsServer);
+        }
+        catch (Exception ex)
+        {
+            _logger.LogError(ex, "Error starting device discovery");
+        }
+    }
+
+    private void StartDevicePublisher(Configuration.DlnaOptions options)
+    {
+        if (_publisher is not null)
+        {
+            return;
+        }
+
+        try
+        {
+            _publisher = new SsdpDevicePublisher(
+                _communicationsServer,
+                Environment.OSVersion.Platform.ToString(),
+                // Can not use VersionString here since that includes OS and version
+                Environment.OSVersion.Version.ToString(),
+                _config.GetDlnaConfiguration().SendOnlyMatchedHost)
+            {
+                LogFunction = msg => _logger.LogDebug("{Msg}", msg),
+                SupportPnpRootDevice = false
+            };
+
+            RegisterServerEndpoints();
+
+            if (options.BlastAliveMessages)
+            {
+                _publisher.StartSendingAliveNotifications(TimeSpan.FromSeconds(options.BlastAliveMessageIntervalSeconds));
+            }
+        }
+        catch (Exception ex)
+        {
+            _logger.LogError(ex, "Error registering endpoint");
+        }
+    }
+
+    private void RegisterServerEndpoints()
+    {
+        var udn = CreateUuid(_appHost.SystemId);
+        var descriptorUri = "/dlna/" + udn + "/description.xml";
+
+        // Only get bind addresses in LAN
+        // IPv6 is currently unsupported
+        var validInterfaces = _networkManager.GetInternalBindAddresses()
+            .Where(x => x.AddressFamily != AddressFamily.InterNetworkV6)
+            .ToList();
+
+        if (validInterfaces.Count == 0)
+        {
+            // No interfaces returned, fall back to loopback
+            validInterfaces = _networkManager.GetLoopbacks().ToList();
+        }
+
+        foreach (var intf in validInterfaces)
+        {
+            var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
+
+            _logger.LogInformation("Registering publisher for {ResourceName} on {DeviceAddress}", fullService, intf.Address);
+
+            var uri = new UriBuilder(_appHost.GetApiUrlForLocalAccess(intf.Address, false) + descriptorUri);
+
+            var device = new SsdpRootDevice
+            {
+                CacheLifetime = TimeSpan.FromSeconds(1800), // How long SSDP clients can cache this info.
+                Location = uri.Uri, // Must point to the URL that serves your devices UPnP description document.
+                Address = intf.Address,
+                PrefixLength = NetworkUtils.MaskToCidr(intf.Subnet.Prefix),
+                FriendlyName = "Jellyfin",
+                Manufacturer = "Jellyfin",
+                ModelName = "Jellyfin Server",
+                Uuid = udn
+                // This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc.
+            };
+
+            SetProperties(device, fullService);
+            _publisher!.AddDevice(device);
+
+            var embeddedDevices = new[]
+            {
+                "urn:schemas-upnp-org:service:ContentDirectory:1",
+                "urn:schemas-upnp-org:service:ConnectionManager:1",
+                // "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"
+            };
+
+            foreach (var subDevice in embeddedDevices)
+            {
+                var embeddedDevice = new SsdpEmbeddedDevice
+                {
+                    FriendlyName = device.FriendlyName,
+                    Manufacturer = device.Manufacturer,
+                    ModelName = device.ModelName,
+                    Uuid = udn
+                    // This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc.
+                };
+
+                SetProperties(embeddedDevice, subDevice);
+                device.AddDevice(embeddedDevice);
+            }
+        }
+    }
+
+    private void StartPlayToManager()
+    {
+        lock (_syncLock)
+        {
+            if (_manager is not null)
+            {
+                return;
+            }
+
+            try
+            {
+                _manager = new PlayToManager(
+                    _logger,
+                    _sessionManager,
+                    _libraryManager,
+                    _userManager,
+                    _dlnaManager,
+                    _appHost,
+                    _imageProcessor,
+                    _deviceDiscovery,
+                    _httpClientFactory,
+                    _userDataManager,
+                    _localization,
+                    _mediaSourceManager,
+                    _mediaEncoder);
+
+                _manager.Start();
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "Error starting PlayTo manager");
+            }
+        }
+    }
+
+    private void DisposePlayToManager()
+    {
+        lock (_syncLock)
+        {
+            if (_manager is not null)
+            {
+                try
+                {
+                    _logger.LogInformation("Disposing PlayToManager");
+                    _manager.Dispose();
+                }
+                catch (Exception ex)
+                {
+                    _logger.LogError(ex, "Error disposing PlayTo manager");
+                }
+
+                _manager = null;
+            }
+        }
+    }
+
+    private void DisposeDevicePublisher()
+    {
+        if (_publisher is not null)
+        {
+            _logger.LogInformation("Disposing SsdpDevicePublisher");
+            _publisher.Dispose();
+            _publisher = null;
+        }
+    }
+
+    private void Stop()
+    {
+        DisposeDevicePublisher();
+        DisposePlayToManager();
+    }
+}

+ 1 - 5
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>
@@ -41,10 +41,6 @@
     <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
   </PropertyGroup>
 
-  <ItemGroup>
-    <PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
-  </ItemGroup>
-
   <!-- Code Analyzers -->
   <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
     <PackageReference Include="IDisposableAnalyzers">

+ 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>

+ 5 - 3
Emby.Server.Implementations/ApplicationHost.cs

@@ -41,7 +41,6 @@ using Emby.Server.Implementations.Updates;
 using Jellyfin.Api.Helpers;
 using Jellyfin.Drawing;
 using Jellyfin.MediaEncoding.Hls.Playlist;
-using Jellyfin.Networking.Configuration;
 using Jellyfin.Networking.Manager;
 using Jellyfin.Server.Implementations;
 using MediaBrowser.Common;
@@ -100,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
@@ -310,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)
             {
@@ -866,7 +868,7 @@ namespace Emby.Server.Implementations
             yield return typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder).Assembly;
 
             // Dlna
-            yield return typeof(DlnaEntryPoint).Assembly;
+            yield return typeof(DlnaHost).Assembly;
 
             // Local metadata
             yield return typeof(BoxSetXmlSaver).Assembly;

+ 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 - 1
Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs

@@ -9,7 +9,7 @@ using System.Net;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
-using Jellyfin.Networking.Configuration;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Plugins;

+ 285 - 383
Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs

@@ -1,7 +1,3 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
@@ -23,476 +19,382 @@ using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Session;
 using Microsoft.Extensions.Logging;
 
-namespace Emby.Server.Implementations.EntryPoints
+namespace Emby.Server.Implementations.EntryPoints;
+
+/// <summary>
+/// A <see cref="IServerEntryPoint"/> that notifies users when libraries are updated.
+/// </summary>
+public sealed class LibraryChangedNotifier : IServerEntryPoint
 {
-    public class LibraryChangedNotifier : IServerEntryPoint
+    private readonly ILibraryManager _libraryManager;
+    private readonly IServerConfigurationManager _configurationManager;
+    private readonly IProviderManager _providerManager;
+    private readonly ISessionManager _sessionManager;
+    private readonly IUserManager _userManager;
+    private readonly ILogger<LibraryChangedNotifier> _logger;
+
+    private readonly object _libraryChangedSyncLock = new();
+    private readonly List<Folder> _foldersAddedTo = new();
+    private readonly List<Folder> _foldersRemovedFrom = new();
+    private readonly List<BaseItem> _itemsAdded = new();
+    private readonly List<BaseItem> _itemsRemoved = new();
+    private readonly List<BaseItem> _itemsUpdated = new();
+    private readonly ConcurrentDictionary<Guid, DateTime> _lastProgressMessageTimes = new();
+
+    private Timer? _libraryUpdateTimer;
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="LibraryChangedNotifier"/> class.
+    /// </summary>
+    /// <param name="libraryManager">The <see cref="ILibraryManager"/>.</param>
+    /// <param name="configurationManager">The <see cref="IServerConfigurationManager"/>.</param>
+    /// <param name="sessionManager">The <see cref="ISessionManager"/>.</param>
+    /// <param name="userManager">The <see cref="IUserManager"/>.</param>
+    /// <param name="logger">The <see cref="ILogger"/>.</param>
+    /// <param name="providerManager">The <see cref="IProviderManager"/>.</param>
+    public LibraryChangedNotifier(
+        ILibraryManager libraryManager,
+        IServerConfigurationManager configurationManager,
+        ISessionManager sessionManager,
+        IUserManager userManager,
+        ILogger<LibraryChangedNotifier> logger,
+        IProviderManager providerManager)
     {
-        private readonly ILibraryManager _libraryManager;
-        private readonly IServerConfigurationManager _configurationManager;
-        private readonly IProviderManager _providerManager;
-        private readonly ISessionManager _sessionManager;
-        private readonly IUserManager _userManager;
-        private readonly ILogger<LibraryChangedNotifier> _logger;
-
-        /// <summary>
-        /// The library changed sync lock.
-        /// </summary>
-        private readonly object _libraryChangedSyncLock = new object();
-
-        private readonly List<Folder> _foldersAddedTo = new List<Folder>();
-        private readonly List<Folder> _foldersRemovedFrom = new List<Folder>();
-        private readonly List<BaseItem> _itemsAdded = new List<BaseItem>();
-        private readonly List<BaseItem> _itemsRemoved = new List<BaseItem>();
-        private readonly List<BaseItem> _itemsUpdated = new List<BaseItem>();
-        private readonly ConcurrentDictionary<Guid, DateTime> _lastProgressMessageTimes = new ConcurrentDictionary<Guid, DateTime>();
-
-        public LibraryChangedNotifier(
-            ILibraryManager libraryManager,
-            IServerConfigurationManager configurationManager,
-            ISessionManager sessionManager,
-            IUserManager userManager,
-            ILogger<LibraryChangedNotifier> logger,
-            IProviderManager providerManager)
-        {
-            _libraryManager = libraryManager;
-            _configurationManager = configurationManager;
-            _sessionManager = sessionManager;
-            _userManager = userManager;
-            _logger = logger;
-            _providerManager = providerManager;
-        }
+        _libraryManager = libraryManager;
+        _configurationManager = configurationManager;
+        _sessionManager = sessionManager;
+        _userManager = userManager;
+        _logger = logger;
+        _providerManager = providerManager;
+    }
 
-        /// <summary>
-        /// Gets or sets the library update timer.
-        /// </summary>
-        /// <value>The library update timer.</value>
-        private Timer LibraryUpdateTimer { get; set; }
+    /// <inheritdoc />
+    public Task RunAsync()
+    {
+        _libraryManager.ItemAdded += OnLibraryItemAdded;
+        _libraryManager.ItemUpdated += OnLibraryItemUpdated;
+        _libraryManager.ItemRemoved += OnLibraryItemRemoved;
 
-        public Task RunAsync()
-        {
-            _libraryManager.ItemAdded += OnLibraryItemAdded;
-            _libraryManager.ItemUpdated += OnLibraryItemUpdated;
-            _libraryManager.ItemRemoved += OnLibraryItemRemoved;
+        _providerManager.RefreshCompleted += OnProviderRefreshCompleted;
+        _providerManager.RefreshStarted += OnProviderRefreshStarted;
+        _providerManager.RefreshProgress += OnProviderRefreshProgress;
 
-            _providerManager.RefreshCompleted += OnProviderRefreshCompleted;
-            _providerManager.RefreshStarted += OnProviderRefreshStarted;
-            _providerManager.RefreshProgress += OnProviderRefreshProgress;
+        return Task.CompletedTask;
+    }
 
-            return Task.CompletedTask;
-        }
+    private void OnProviderRefreshProgress(object? sender, GenericEventArgs<Tuple<BaseItem, double>> e)
+    {
+        var item = e.Argument.Item1;
 
-        private void OnProviderRefreshProgress(object sender, GenericEventArgs<Tuple<BaseItem, double>> e)
+        if (!EnableRefreshMessage(item))
         {
-            var item = e.Argument.Item1;
+            return;
+        }
 
-            if (!EnableRefreshMessage(item))
+        var progress = e.Argument.Item2;
+
+        if (_lastProgressMessageTimes.TryGetValue(item.Id, out var lastMessageSendTime))
+        {
+            if (progress > 0 && progress < 100 && (DateTime.UtcNow - lastMessageSendTime).TotalMilliseconds < 1000)
             {
                 return;
             }
+        }
 
-            var progress = e.Argument.Item2;
+        _lastProgressMessageTimes.AddOrUpdate(item.Id, _ => DateTime.UtcNow, (_, _) => DateTime.UtcNow);
 
-            if (_lastProgressMessageTimes.TryGetValue(item.Id, out var lastMessageSendTime))
-            {
-                if (progress > 0 && progress < 100 && (DateTime.UtcNow - lastMessageSendTime).TotalMilliseconds < 1000)
-                {
-                    return;
-                }
-            }
+        var dict = new Dictionary<string, string>();
+        dict["ItemId"] = item.Id.ToString("N", CultureInfo.InvariantCulture);
+        dict["Progress"] = progress.ToString(CultureInfo.InvariantCulture);
 
-            _lastProgressMessageTimes.AddOrUpdate(item.Id, _ => DateTime.UtcNow, (_, _) => DateTime.UtcNow);
+        try
+        {
+            _sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, dict, CancellationToken.None);
+        }
+        catch
+        {
+        }
 
-            var dict = new Dictionary<string, string>();
-            dict["ItemId"] = item.Id.ToString("N", CultureInfo.InvariantCulture);
-            dict["Progress"] = progress.ToString(CultureInfo.InvariantCulture);
+        var collectionFolders = _libraryManager.GetCollectionFolders(item);
+
+        foreach (var collectionFolder in collectionFolders)
+        {
+            var collectionFolderDict = new Dictionary<string, string>
+            {
+                ["ItemId"] = collectionFolder.Id.ToString("N", CultureInfo.InvariantCulture),
+                ["Progress"] = (collectionFolder.GetRefreshProgress() ?? 0).ToString(CultureInfo.InvariantCulture)
+            };
 
             try
             {
-                _sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, dict, CancellationToken.None);
+                _sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, collectionFolderDict, CancellationToken.None);
             }
             catch
             {
             }
+        }
+    }
 
-            var collectionFolders = _libraryManager.GetCollectionFolders(item);
+    private void OnProviderRefreshStarted(object? sender, GenericEventArgs<BaseItem> e)
+    {
+        OnProviderRefreshProgress(sender, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(e.Argument, 0)));
+    }
 
-            foreach (var collectionFolder in collectionFolders)
-            {
-                var collectionFolderDict = new Dictionary<string, string>
-                {
-                    ["ItemId"] = collectionFolder.Id.ToString("N", CultureInfo.InvariantCulture),
-                    ["Progress"] = (collectionFolder.GetRefreshProgress() ?? 0).ToString(CultureInfo.InvariantCulture)
-                };
-
-                try
-                {
-                    _sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, collectionFolderDict, CancellationToken.None);
-                }
-                catch
-                {
-                }
-            }
-        }
+    private void OnProviderRefreshCompleted(object? sender, GenericEventArgs<BaseItem> e)
+    {
+        OnProviderRefreshProgress(sender, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(e.Argument, 100)));
 
-        private void OnProviderRefreshStarted(object sender, GenericEventArgs<BaseItem> e)
-        {
-            OnProviderRefreshProgress(sender, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(e.Argument, 0)));
-        }
+        _lastProgressMessageTimes.TryRemove(e.Argument.Id, out _);
+    }
 
-        private void OnProviderRefreshCompleted(object sender, GenericEventArgs<BaseItem> e)
-        {
-            OnProviderRefreshProgress(sender, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(e.Argument, 100)));
+    private static bool EnableRefreshMessage(BaseItem item)
+        => item is Folder { IsRoot: false, IsTopParent: true }
+            and not (AggregateFolder or UserRootFolder or UserView or Channel);
 
-            _lastProgressMessageTimes.TryRemove(e.Argument.Id, out _);
-        }
+    private void OnLibraryItemAdded(object? sender, ItemChangeEventArgs e)
+        => OnLibraryChange(e.Item, e.Parent, _itemsAdded, _foldersAddedTo);
 
-        private static bool EnableRefreshMessage(BaseItem item)
+    private void OnLibraryItemUpdated(object? sender, ItemChangeEventArgs e)
+        => OnLibraryChange(e.Item, e.Parent, _itemsUpdated, null);
+
+    private void OnLibraryItemRemoved(object? sender, ItemChangeEventArgs e)
+        => OnLibraryChange(e.Item, e.Parent, _itemsRemoved, _foldersRemovedFrom);
+
+    private void OnLibraryChange(BaseItem item, BaseItem parent, List<BaseItem> itemsList, List<Folder>? foldersList)
+    {
+        if (!FilterItem(item))
         {
-            if (item is not Folder folder)
-            {
-                return false;
-            }
+            return;
+        }
 
-            if (folder.IsRoot)
-            {
-                return false;
-            }
+        lock (_libraryChangedSyncLock)
+        {
+            var updateDuration = TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration);
 
-            if (folder is AggregateFolder || folder is UserRootFolder)
+            if (_libraryUpdateTimer is null)
             {
-                return false;
+                _libraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, updateDuration, Timeout.InfiniteTimeSpan);
             }
-
-            if (folder is UserView || folder is Channel)
+            else
             {
-                return false;
+                _libraryUpdateTimer.Change(updateDuration, Timeout.InfiniteTimeSpan);
             }
 
-            if (!folder.IsTopParent)
+            if (foldersList is not null && parent is Folder folder)
             {
-                return false;
+                foldersList.Add(folder);
             }
 
-            return true;
+            itemsList.Add(item);
         }
+    }
 
-        /// <summary>
-        /// Handles the ItemAdded event of the libraryManager control.
-        /// </summary>
-        /// <param name="sender">The source of the event.</param>
-        /// <param name="e">The <see cref="ItemChangeEventArgs"/> instance containing the event data.</param>
-        private void OnLibraryItemAdded(object sender, ItemChangeEventArgs e)
+    private async void LibraryUpdateTimerCallback(object? state)
+    {
+        List<Folder> foldersAddedTo;
+        List<Folder> foldersRemovedFrom;
+        List<BaseItem> itemsUpdated;
+        List<BaseItem> itemsAdded;
+        List<BaseItem> itemsRemoved;
+        lock (_libraryChangedSyncLock)
         {
-            if (!FilterItem(e.Item))
-            {
-                return;
-            }
+            // Remove dupes in case some were saved multiple times
+            foldersAddedTo = _foldersAddedTo
+                .DistinctBy(x => x.Id)
+                .ToList();
 
-            lock (_libraryChangedSyncLock)
+            foldersRemovedFrom = _foldersRemovedFrom
+                .DistinctBy(x => x.Id)
+                .ToList();
+
+            itemsUpdated = _itemsUpdated
+                .Where(i => !_itemsAdded.Contains(i))
+                .DistinctBy(x => x.Id)
+                .ToList();
+
+            itemsAdded = _itemsAdded.ToList();
+            itemsRemoved = _itemsRemoved.ToList();
+
+            if (_libraryUpdateTimer is not null)
             {
-                if (LibraryUpdateTimer is null)
-                {
-                    LibraryUpdateTimer = new Timer(
-                        LibraryUpdateTimerCallback,
-                        null,
-                        TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration),
-                        Timeout.InfiniteTimeSpan);
-                }
-                else
-                {
-                    LibraryUpdateTimer.Change(TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration), Timeout.InfiniteTimeSpan);
-                }
-
-                if (e.Item.GetParent() is Folder parent)
-                {
-                    _foldersAddedTo.Add(parent);
-                }
-
-                _itemsAdded.Add(e.Item);
+                _libraryUpdateTimer.Dispose();
+                _libraryUpdateTimer = null;
             }
+
+            _itemsAdded.Clear();
+            _itemsRemoved.Clear();
+            _itemsUpdated.Clear();
+            _foldersAddedTo.Clear();
+            _foldersRemovedFrom.Clear();
         }
 
-        /// <summary>
-        /// Handles the ItemUpdated event of the libraryManager control.
-        /// </summary>
-        /// <param name="sender">The source of the event.</param>
-        /// <param name="e">The <see cref="ItemChangeEventArgs"/> instance containing the event data.</param>
-        private void OnLibraryItemUpdated(object sender, ItemChangeEventArgs e)
+        await SendChangeNotifications(itemsAdded, itemsUpdated, itemsRemoved, foldersAddedTo, foldersRemovedFrom, CancellationToken.None).ConfigureAwait(false);
+    }
+
+    private async Task SendChangeNotifications(
+        List<BaseItem> itemsAdded,
+        List<BaseItem> itemsUpdated,
+        List<BaseItem> itemsRemoved,
+        List<Folder> foldersAddedTo,
+        List<Folder> foldersRemovedFrom,
+        CancellationToken cancellationToken)
+    {
+        var userIds = _sessionManager.Sessions
+            .Select(i => i.UserId)
+            .Where(i => !i.Equals(default))
+            .Distinct()
+            .ToArray();
+
+        foreach (var userId in userIds)
         {
-            if (!FilterItem(e.Item))
-            {
-                return;
-            }
+            LibraryUpdateInfo info;
 
-            lock (_libraryChangedSyncLock)
+            try
             {
-                if (LibraryUpdateTimer is null)
-                {
-                    LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration), Timeout.InfiniteTimeSpan);
-                }
-                else
-                {
-                    LibraryUpdateTimer.Change(TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration), Timeout.InfiniteTimeSpan);
-                }
-
-                _itemsUpdated.Add(e.Item);
+                info = GetLibraryUpdateInfo(itemsAdded, itemsUpdated, itemsRemoved, foldersAddedTo, foldersRemovedFrom, userId);
             }
-        }
-
-        /// <summary>
-        /// Handles the ItemRemoved event of the libraryManager control.
-        /// </summary>
-        /// <param name="sender">The source of the event.</param>
-        /// <param name="e">The <see cref="ItemChangeEventArgs"/> instance containing the event data.</param>
-        private void OnLibraryItemRemoved(object sender, ItemChangeEventArgs e)
-        {
-            if (!FilterItem(e.Item))
+            catch (Exception ex)
             {
+                _logger.LogError(ex, "Error in GetLibraryUpdateInfo");
                 return;
             }
 
-            lock (_libraryChangedSyncLock)
+            if (info.IsEmpty)
             {
-                if (LibraryUpdateTimer is null)
-                {
-                    LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration), Timeout.InfiniteTimeSpan);
-                }
-                else
-                {
-                    LibraryUpdateTimer.Change(TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration), Timeout.InfiniteTimeSpan);
-                }
-
-                if (e.Parent is Folder parent)
-                {
-                    _foldersRemovedFrom.Add(parent);
-                }
-
-                _itemsRemoved.Add(e.Item);
+                continue;
             }
-        }
 
-        /// <summary>
-        /// Libraries the update timer callback.
-        /// </summary>
-        /// <param name="state">The state.</param>
-        private async void LibraryUpdateTimerCallback(object state)
-        {
-            List<Folder> foldersAddedTo;
-            List<Folder> foldersRemovedFrom;
-            List<BaseItem> itemsUpdated;
-            List<BaseItem> itemsAdded;
-            List<BaseItem> itemsRemoved;
-            lock (_libraryChangedSyncLock)
+            try
             {
-                // Remove dupes in case some were saved multiple times
-                foldersAddedTo = _foldersAddedTo
-                                        .DistinctBy(x => x.Id)
-                                        .ToList();
-
-                foldersRemovedFrom = _foldersRemovedFrom
-                                            .DistinctBy(x => x.Id)
-                                            .ToList();
-
-                itemsUpdated = _itemsUpdated
-                                    .Where(i => !_itemsAdded.Contains(i))
-                                    .DistinctBy(x => x.Id)
-                                    .ToList();
-
-                itemsAdded = _itemsAdded.ToList();
-                itemsRemoved = _itemsRemoved.ToList();
-
-                if (LibraryUpdateTimer is not null)
-                {
-                    LibraryUpdateTimer.Dispose();
-                    LibraryUpdateTimer = null;
-                }
-
-                _itemsAdded.Clear();
-                _itemsRemoved.Clear();
-                _itemsUpdated.Clear();
-                _foldersAddedTo.Clear();
-                _foldersRemovedFrom.Clear();
+                await _sessionManager.SendMessageToUserSessions(
+                        new List<Guid> { userId },
+                        SessionMessageType.LibraryChanged,
+                        info,
+                        cancellationToken)
+                    .ConfigureAwait(false);
             }
-
-            await SendChangeNotifications(itemsAdded, itemsUpdated, itemsRemoved, foldersAddedTo, foldersRemovedFrom, CancellationToken.None).ConfigureAwait(false);
-        }
-
-        /// <summary>
-        /// Sends the change notifications.
-        /// </summary>
-        /// <param name="itemsAdded">The items added.</param>
-        /// <param name="itemsUpdated">The items updated.</param>
-        /// <param name="itemsRemoved">The items removed.</param>
-        /// <param name="foldersAddedTo">The folders added to.</param>
-        /// <param name="foldersRemovedFrom">The folders removed from.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        private async Task SendChangeNotifications(List<BaseItem> itemsAdded, List<BaseItem> itemsUpdated, List<BaseItem> itemsRemoved, List<Folder> foldersAddedTo, List<Folder> foldersRemovedFrom, CancellationToken cancellationToken)
-        {
-            var userIds = _sessionManager.Sessions
-                .Select(i => i.UserId)
-                .Where(i => !i.Equals(default))
-                .Distinct()
-                .ToArray();
-
-            foreach (var userId in userIds)
+            catch (Exception ex)
             {
-                LibraryUpdateInfo info;
-
-                try
-                {
-                    info = GetLibraryUpdateInfo(itemsAdded, itemsUpdated, itemsRemoved, foldersAddedTo, foldersRemovedFrom, userId);
-                }
-                catch (Exception ex)
-                {
-                    _logger.LogError(ex, "Error in GetLibraryUpdateInfo");
-                    return;
-                }
-
-                if (info.IsEmpty)
-                {
-                    continue;
-                }
-
-                try
-                {
-                    await _sessionManager.SendMessageToUserSessions(new List<Guid> { userId }, SessionMessageType.LibraryChanged, info, cancellationToken).ConfigureAwait(false);
-                }
-                catch (Exception ex)
-                {
-                    _logger.LogError(ex, "Error sending LibraryChanged message");
-                }
+                _logger.LogError(ex, "Error sending LibraryChanged message");
             }
         }
+    }
 
-        /// <summary>
-        /// Gets the library update info.
-        /// </summary>
-        /// <param name="itemsAdded">The items added.</param>
-        /// <param name="itemsUpdated">The items updated.</param>
-        /// <param name="itemsRemoved">The items removed.</param>
-        /// <param name="foldersAddedTo">The folders added to.</param>
-        /// <param name="foldersRemovedFrom">The folders removed from.</param>
-        /// <param name="userId">The user id.</param>
-        /// <returns>LibraryUpdateInfo.</returns>
-        private LibraryUpdateInfo GetLibraryUpdateInfo(List<BaseItem> itemsAdded, List<BaseItem> itemsUpdated, List<BaseItem> itemsRemoved, List<Folder> foldersAddedTo, List<Folder> foldersRemovedFrom, Guid userId)
-        {
-            var user = _userManager.GetUserById(userId);
-
-            var newAndRemoved = new List<BaseItem>();
-            newAndRemoved.AddRange(foldersAddedTo);
-            newAndRemoved.AddRange(foldersRemovedFrom);
-
-            var allUserRootChildren = _libraryManager.GetUserRootFolder().GetChildren(user, true).OfType<Folder>().ToList();
-
-            return new LibraryUpdateInfo
-            {
-                ItemsAdded = itemsAdded.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(),
-
-                ItemsUpdated = itemsUpdated.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(),
-
-                ItemsRemoved = itemsRemoved.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user, true)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(),
+    private LibraryUpdateInfo GetLibraryUpdateInfo(
+        List<BaseItem> itemsAdded,
+        List<BaseItem> itemsUpdated,
+        List<BaseItem> itemsRemoved,
+        List<Folder> foldersAddedTo,
+        List<Folder> foldersRemovedFrom,
+        Guid userId)
+    {
+        var user = _userManager.GetUserById(userId);
+        ArgumentNullException.ThrowIfNull(user);
 
-                FoldersAddedTo = foldersAddedTo.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(),
+        var newAndRemoved = new List<BaseItem>();
+        newAndRemoved.AddRange(foldersAddedTo);
+        newAndRemoved.AddRange(foldersRemovedFrom);
 
-                FoldersRemovedFrom = foldersRemovedFrom.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(),
+        var allUserRootChildren = _libraryManager.GetUserRootFolder()
+            .GetChildren(user, true)
+            .OfType<Folder>()
+            .ToList();
 
-                CollectionFolders = GetTopParentIds(newAndRemoved, allUserRootChildren).ToArray()
-            };
-        }
-
-        private static bool FilterItem(BaseItem item)
+        return new LibraryUpdateInfo
         {
-            if (!item.IsFolder && !item.HasPathProtocol)
-            {
-                return false;
-            }
-
-            if (item is IItemByName && item is not MusicArtist)
-            {
-                return false;
-            }
+            ItemsAdded = itemsAdded.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user))
+                .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture))
+                .Distinct()
+                .ToArray(),
+            ItemsUpdated = itemsUpdated.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user))
+                .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture))
+                .Distinct()
+                .ToArray(),
+            ItemsRemoved = itemsRemoved.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user, true))
+                .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture))
+                .Distinct()
+                .ToArray(),
+            FoldersAddedTo = foldersAddedTo.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user))
+                .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture))
+                .Distinct()
+                .ToArray(),
+            FoldersRemovedFrom = foldersRemovedFrom.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user))
+                .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture))
+                .Distinct()
+                .ToArray(),
+            CollectionFolders = GetTopParentIds(newAndRemoved, allUserRootChildren).ToArray()
+        };
+    }
 
-            return item.SourceType == SourceType.Library;
+    private static bool FilterItem(BaseItem item)
+    {
+        if (!item.IsFolder && !item.HasPathProtocol)
+        {
+            return false;
         }
 
-        private IEnumerable<string> GetTopParentIds(List<BaseItem> items, List<Folder> allUserRootChildren)
+        if (item is IItemByName && item is not MusicArtist)
         {
-            var list = new List<string>();
+            return false;
+        }
 
-            foreach (var item in items)
-            {
-                // If the physical root changed, return the user root
-                if (item is AggregateFolder)
-                {
-                    continue;
-                }
-
-                foreach (var folder in allUserRootChildren)
-                {
-                    list.Add(folder.Id.ToString("N", CultureInfo.InvariantCulture));
-                }
-            }
+        return item.SourceType == SourceType.Library;
+    }
 
-            return list.Distinct(StringComparer.Ordinal);
-        }
+    private IEnumerable<string> GetTopParentIds(List<BaseItem> items, List<Folder> allUserRootChildren)
+    {
+        var list = new List<string>();
 
-        /// <summary>
-        /// Translates the physical item to user library.
-        /// </summary>
-        /// <typeparam name="T">The type of item.</typeparam>
-        /// <param name="item">The item.</param>
-        /// <param name="user">The user.</param>
-        /// <param name="includeIfNotFound">if set to <c>true</c> [include if not found].</param>
-        /// <returns>IEnumerable{``0}.</returns>
-        private IEnumerable<T> TranslatePhysicalItemToUserLibrary<T>(T item, User user, bool includeIfNotFound = false)
-            where T : BaseItem
+        foreach (var item in items)
         {
             // If the physical root changed, return the user root
             if (item is AggregateFolder)
             {
-                return new[] { _libraryManager.GetUserRootFolder() as T };
+                continue;
             }
 
-            // Return it only if it's in the user's library
-            if (includeIfNotFound || item.IsVisibleStandalone(user))
+            foreach (var folder in allUserRootChildren)
             {
-                return new[] { item };
+                list.Add(folder.Id.ToString("N", CultureInfo.InvariantCulture));
             }
+        }
 
-            return Array.Empty<T>();
+        return list.Distinct(StringComparer.Ordinal);
+    }
+
+    private IEnumerable<T> TranslatePhysicalItemToUserLibrary<T>(T item, User user, bool includeIfNotFound = false)
+        where T : BaseItem
+    {
+        // If the physical root changed, return the user root
+        if (item is AggregateFolder)
+        {
+            return _libraryManager.GetUserRootFolder() is T t ? new[] { t } : Array.Empty<T>();
         }
 
-        /// <summary>
-        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
-        /// </summary>
-        public void Dispose()
+        // Return it only if it's in the user's library
+        if (includeIfNotFound || item.IsVisibleStandalone(user))
         {
-            Dispose(true);
-            GC.SuppressFinalize(this);
+            return new[] { item };
         }
 
-        /// <summary>
-        /// Releases unmanaged and - optionally - managed resources.
-        /// </summary>
-        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
-        protected virtual void Dispose(bool dispose)
+        return Array.Empty<T>();
+    }
+
+    /// <inheritdoc />
+    public void Dispose()
+    {
+        _libraryManager.ItemAdded -= OnLibraryItemAdded;
+        _libraryManager.ItemUpdated -= OnLibraryItemUpdated;
+        _libraryManager.ItemRemoved -= OnLibraryItemRemoved;
+
+        _providerManager.RefreshCompleted -= OnProviderRefreshCompleted;
+        _providerManager.RefreshStarted -= OnProviderRefreshStarted;
+        _providerManager.RefreshProgress -= OnProviderRefreshProgress;
+
+        if (_libraryUpdateTimer is not null)
         {
-            if (dispose)
-            {
-                if (LibraryUpdateTimer is not null)
-                {
-                    LibraryUpdateTimer.Dispose();
-                    LibraryUpdateTimer = null;
-                }
-
-                _libraryManager.ItemAdded -= OnLibraryItemAdded;
-                _libraryManager.ItemUpdated -= OnLibraryItemUpdated;
-                _libraryManager.ItemRemoved -= OnLibraryItemRemoved;
-
-                _providerManager.RefreshCompleted -= OnProviderRefreshCompleted;
-                _providerManager.RefreshStarted -= OnProviderRefreshStarted;
-                _providerManager.RefreshProgress -= OnProviderRefreshProgress;
-            }
+            _libraryUpdateTimer.Dispose();
+            _libraryUpdateTimer = null;
         }
     }
 }

+ 2 - 3
Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs

@@ -6,14 +6,13 @@ using System.Net.Sockets;
 using System.Threading;
 using System.Threading.Tasks;
 using Emby.Server.Implementations.Udp;
-using Jellyfin.Networking.Configuration;
-using Jellyfin.Networking.Extensions;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Net;
 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
 {
@@ -92,7 +91,7 @@ namespace Emby.Server.Implementations.EntryPoints
                     var validInterfaces = _networkManager.GetInternalBindAddresses().Where(i => i.AddressFamily == AddressFamily.InterNetwork);
                     foreach (var intf in validInterfaces)
                     {
-                        var broadcastAddress = NetworkExtensions.GetBroadcastAddress(intf.Subnet);
+                        var broadcastAddress = NetworkUtils.GetBroadcastAddress(intf.Subnet);
                         _logger.LogDebug("Binding UDP server to {Address} on port {PortNumber}", broadcastAddress, PortNumber);
 
                         server = new UdpServer(_logger, _appHost, _config, broadcastAddress, PortNumber);

+ 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()

+ 3 - 1
Emby.Server.Implementations/Localization/Core/af.json

@@ -123,5 +123,7 @@
     "TaskKeyframeExtractorDescription": "Haal keyframes vanuit video lêers om meer presiese HLS afspeellyste te maak. Dit kan lank duur.",
     "TaskKeyframeExtractor": "Keyframe Ekstraktor",
     "External": "Ekstern",
-    "HearingImpaired": "gehoorgestremd"
+    "HearingImpaired": "gehoorgestremd",
+    "TaskRefreshTrickplayImages": "Genereer Fopspeel Beelde",
+    "TaskRefreshTrickplayImagesDescription": "Skep fopspeel voorskou vir videos in aangeskakelde media versameling."
 }

+ 2 - 1
Emby.Server.Implementations/Localization/Core/ar.json

@@ -124,5 +124,6 @@
     "TaskKeyframeExtractorDescription": "يستخرج الإطارات الرئيسية من ملفات الفيديو لكي ينشئ قوائم تشغيل بث HTTP المباشر. قد تستمر هذه العملية لوقت طويل.",
     "TaskKeyframeExtractor": "مستخرج الإطار الرئيسي",
     "External": "خارجي",
-    "HearingImpaired": "ضعاف السمع"
+    "HearingImpaired": "ضعاف السمع",
+    "TaskRefreshTrickplayImages": "توليد صور Trickplay"
 }

+ 3 - 1
Emby.Server.Implementations/Localization/Core/lt-LT.json

@@ -124,5 +124,7 @@
     "TaskKeyframeExtractor": "Pagrindinių kadrų ištraukėjas",
     "TaskOptimizeDatabaseDescription": "Suspaudžia duomenų bazę ir atlaisvina vietą. Paleidžiant šią užduotį, po bibliotekos skenavimo arba kitų veiksmų kurie galimai modifikuoja duomenų bazė, gali pagerinti greitaveiką.",
     "External": "Išorinis",
-    "HearingImpaired": "Su klausos sutrikimais"
+    "HearingImpaired": "Su klausos sutrikimais",
+    "TaskRefreshTrickplayImages": "Generuoti Trickplay atvaizdus",
+    "TaskRefreshTrickplayImagesDescription": "Sukuria trickplay peržiūras vaizdo įrašams įgalintose bibliotekose."
 }

+ 3 - 1
Emby.Server.Implementations/Localization/Core/mr.json

@@ -123,5 +123,7 @@
     "DeviceOnlineWithName": "{0} कनेक्ट झाले",
     "DeviceOfflineWithName": "{0} डिस्कनेक्ट झाला आहे",
     "AuthenticationSucceededWithUserName": "{0} यशस्वीरित्या प्रमाणीकृत",
-    "HearingImpaired": "कर्णबधीर"
+    "HearingImpaired": "कर्णबधीर",
+    "TaskRefreshTrickplayImages": "ट्रिकप्ले प्रतिमा तयार करा",
+    "TaskRefreshTrickplayImagesDescription": "सक्षम लायब्ररीमधील व्हिडिओंसाठी ट्रिकप्ले पूर्वावलोकन तयार करते."
 }

+ 3 - 1
Emby.Server.Implementations/Localization/Core/pt-BR.json

@@ -124,5 +124,7 @@
     "TaskKeyframeExtractor": "Extrator de quadro-chave",
     "TaskKeyframeExtractorDescription": "Extrai quadros-chave de arquivos de vídeo para criar listas de reprodução HLS mais precisas. Esta tarefa pode ser executada por um longo tempo.",
     "External": "Externo",
-    "HearingImpaired": "Deficiência Auditiva"
+    "HearingImpaired": "Deficiência Auditiva",
+    "TaskRefreshTrickplayImages": "Gerar imagens Trickplay",
+    "TaskRefreshTrickplayImagesDescription": "Cria prévias Trickplay para vídeos em bibliotecas em que o recurso está habilitado."
 }

+ 3 - 1
Emby.Server.Implementations/Localization/Core/ro.json

@@ -123,5 +123,7 @@
     "TaskKeyframeExtractorDescription": "Extrage cadrele cheie din fișierele video pentru a crea liste de redare HLS mai precise. Această sarcină poate rula o perioadă lungă de timp.",
     "External": "Extern",
     "TaskKeyframeExtractor": "Extractor de cadre cheie",
-    "HearingImpaired": "Ascultare Impară"
+    "HearingImpaired": "Ascultare Impară",
+    "TaskRefreshTrickplayImages": "Generează imagini Trickplay",
+    "TaskRefreshTrickplayImagesDescription": "Generează previzualizările trickplay pentru videourile din librăriile selectate."
 }

+ 6 - 5
Emby.Server.Implementations/Plugins/PluginManager.cs

@@ -12,10 +12,11 @@ using System.Threading.Tasks;
 using Emby.Server.Implementations.Library;
 using Jellyfin.Extensions.Json;
 using Jellyfin.Extensions.Json.Converters;
-using MediaBrowser.Common;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Plugins;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Plugins;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Plugins;
@@ -37,7 +38,7 @@ namespace Emby.Server.Implementations.Plugins
         private readonly List<AssemblyLoadContext> _assemblyLoadContexts;
         private readonly JsonSerializerOptions _jsonOptions;
         private readonly ILogger<PluginManager> _logger;
-        private readonly IApplicationHost _appHost;
+        private readonly IServerApplicationHost _appHost;
         private readonly ServerConfiguration _config;
         private readonly List<LocalPlugin> _plugins;
         private readonly Version _minimumVersion;
@@ -48,13 +49,13 @@ namespace Emby.Server.Implementations.Plugins
         /// Initializes a new instance of the <see cref="PluginManager"/> class.
         /// </summary>
         /// <param name="logger">The <see cref="ILogger{PluginManager}"/>.</param>
-        /// <param name="appHost">The <see cref="IApplicationHost"/>.</param>
+        /// <param name="appHost">The <see cref="IServerApplicationHost"/>.</param>
         /// <param name="config">The <see cref="ServerConfiguration"/>.</param>
         /// <param name="pluginsPath">The plugin path.</param>
         /// <param name="appVersion">The application version.</param>
         public PluginManager(
             ILogger<PluginManager> logger,
-            IApplicationHost appHost,
+            IServerApplicationHost appHost,
             ServerConfiguration config,
             string pluginsPath,
             Version appVersion)
@@ -222,7 +223,7 @@ namespace Emby.Server.Implementations.Plugins
                 try
                 {
                     var instance = (IPluginServiceRegistrator?)Activator.CreateInstance(pluginServiceRegistrator);
-                    instance?.RegisterServices(serviceCollection);
+                    instance?.RegisterServices(serviceCollection, _appHost);
                 }
 #pragma warning disable CA1031 // Do not catch general exception types
                 catch (Exception ex)

+ 1 - 2
Emby.Server.Implementations/Updates/InstallationManager.cs

@@ -551,8 +551,7 @@ namespace Emby.Server.Implementations.Updates
             }
 
             stream.Position = 0;
-            using var reader = new ZipArchive(stream);
-            reader.ExtractToDirectory(targetDir, true);
+            ZipFile.ExtractToDirectory(stream, targetDir, true);
 
             // Ensure we create one or populate existing ones with missing data.
             await _pluginManager.PopulateManifest(package.PackageInfo, package.Version, targetDir, status).ConfigureAwait(false);

+ 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 - 0
Jellyfin.Api/Controllers/ActivityLogController.cs

@@ -2,6 +2,7 @@ using System;
 using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
 using Jellyfin.Data.Queries;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Model.Activity;
 using MediaBrowser.Model.Querying;
 using Microsoft.AspNetCore.Authorization;

+ 1 - 0
Jellyfin.Api/Controllers/ApiKeyController.cs

@@ -1,6 +1,7 @@
 using System.ComponentModel.DataAnnotations;
 using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Controller.Security;
 using MediaBrowser.Model.Querying;
 using Microsoft.AspNetCore.Authorization;

+ 1 - 0
Jellyfin.Api/Controllers/CollectionController.cs

@@ -4,6 +4,7 @@ using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Extensions;
 using Jellyfin.Api.ModelBinders;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Controller.Collections;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Model.Collections;

+ 1 - 0
Jellyfin.Api/Controllers/ConfigurationController.cs

@@ -6,6 +6,7 @@ using Jellyfin.Api.Attributes;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Models.ConfigurationDtos;
 using Jellyfin.Extensions.Json;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Model.Configuration;

+ 1 - 0
Jellyfin.Api/Controllers/DevicesController.cs

@@ -6,6 +6,7 @@ using Jellyfin.Api.Helpers;
 using Jellyfin.Data.Dtos;
 using Jellyfin.Data.Entities.Security;
 using Jellyfin.Data.Queries;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Devices;

+ 1 - 0
Jellyfin.Api/Controllers/DlnaController.cs

@@ -1,6 +1,7 @@
 using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
 using Jellyfin.Api.Constants;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Model.Dlna;
 using Microsoft.AspNetCore.Authorization;

+ 1 - 0
Jellyfin.Api/Controllers/DlnaServerController.cs

@@ -7,6 +7,7 @@ using System.Threading.Tasks;
 using Emby.Dlna;
 using Jellyfin.Api.Attributes;
 using Jellyfin.Api.Constants;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Model.Net;
 using Microsoft.AspNetCore.Authorization;

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

@@ -5,6 +5,7 @@ using System.IO;
 using System.Linq;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Models.EnvironmentDtos;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Model.IO;
 using Microsoft.AspNetCore.Authorization;
@@ -168,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;

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

@@ -13,6 +13,7 @@ using System.Threading.Tasks;
 using Jellyfin.Api.Attributes;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Helpers;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Drawing;
@@ -79,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>
@@ -2079,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 - 0
Jellyfin.Api/Controllers/ItemLookupController.cs

@@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations;
 using System.Threading;
 using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Movies;

+ 1 - 0
Jellyfin.Api/Controllers/ItemRefreshController.cs

@@ -2,6 +2,7 @@ using System;
 using System.ComponentModel;
 using System.ComponentModel.DataAnnotations;
 using Jellyfin.Api.Constants;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.IO;

+ 1 - 0
Jellyfin.Api/Controllers/ItemUpdateController.cs

@@ -6,6 +6,7 @@ using System.Threading;
 using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
 using Jellyfin.Data.Enums;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;

+ 1 - 0
Jellyfin.Api/Controllers/LibraryController.cs

@@ -15,6 +15,7 @@ using Jellyfin.Api.Models.LibraryDtos;
 using Jellyfin.Data.Entities;
 using Jellyfin.Data.Enums;
 using Jellyfin.Extensions;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Progress;
 using MediaBrowser.Controller.Configuration;

+ 1 - 0
Jellyfin.Api/Controllers/LibraryStructureController.cs

@@ -9,6 +9,7 @@ using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.ModelBinders;
 using Jellyfin.Api.Models.LibraryStructureDto;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Common.Progress;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Configuration;

+ 1 - 0
Jellyfin.Api/Controllers/LiveTvController.cs

@@ -16,6 +16,7 @@ using Jellyfin.Api.Helpers;
 using Jellyfin.Api.ModelBinders;
 using Jellyfin.Api.Models.LiveTvDtos;
 using Jellyfin.Data.Enums;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Dto;

+ 1 - 0
Jellyfin.Api/Controllers/LocalizationController.cs

@@ -1,5 +1,6 @@
 using System.Collections.Generic;
 using Jellyfin.Api.Constants;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Globalization;
 using Microsoft.AspNetCore.Authorization;

+ 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);
         }

+ 1 - 0
Jellyfin.Api/Controllers/PackageController.cs

@@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations;
 using System.Linq;
 using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Common.Updates;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Model.Updates;

+ 1 - 0
Jellyfin.Api/Controllers/PluginsController.cs

@@ -8,6 +8,7 @@ using System.Threading.Tasks;
 using Jellyfin.Api.Attributes;
 using Jellyfin.Api.Constants;
 using Jellyfin.Extensions.Json;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Common.Plugins;
 using MediaBrowser.Common.Updates;
 using MediaBrowser.Model.Net;

+ 1 - 0
Jellyfin.Api/Controllers/RemoteImageController.cs

@@ -6,6 +6,7 @@ using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;

+ 1 - 0
Jellyfin.Api/Controllers/ScheduledTasksController.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
 using System.Linq;
 using Jellyfin.Api.Constants;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Model.Tasks;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Http;

+ 1 - 0
Jellyfin.Api/Controllers/SessionController.cs

@@ -10,6 +10,7 @@ using Jellyfin.Api.Helpers;
 using Jellyfin.Api.ModelBinders;
 using Jellyfin.Api.Models.SessionDtos;
 using Jellyfin.Data.Enums;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Session;

+ 2 - 1
Jellyfin.Api/Controllers/StartupController.cs

@@ -3,7 +3,8 @@ using System.Linq;
 using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Models.StartupDtos;
-using Jellyfin.Networking.Configuration;
+using MediaBrowser.Common.Api;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Library;
 using Microsoft.AspNetCore.Authorization;

+ 1 - 0
Jellyfin.Api/Controllers/SubtitleController.cs

@@ -14,6 +14,7 @@ using Jellyfin.Api.Attributes;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Extensions;
 using Jellyfin.Api.Models.SubtitleDtos;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;

+ 1 - 0
Jellyfin.Api/Controllers/SyncPlayController.cs

@@ -6,6 +6,7 @@ using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Helpers;
 using Jellyfin.Api.Models.SyncPlayDtos;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.SyncPlay;

+ 1 - 0
Jellyfin.Api/Controllers/SystemController.cs

@@ -6,6 +6,7 @@ using System.Linq;
 using System.Net.Mime;
 using Jellyfin.Api.Attributes;
 using Jellyfin.Api.Constants;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;

+ 1 - 0
Jellyfin.Api/Controllers/UserController.cs

@@ -8,6 +8,7 @@ using Jellyfin.Api.Extensions;
 using Jellyfin.Api.Helpers;
 using Jellyfin.Api.Models.UserDtos;
 using Jellyfin.Data.Enums;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Authentication;

+ 1 - 0
Jellyfin.Api/Controllers/VideosController.cs

@@ -12,6 +12,7 @@ using Jellyfin.Api.Extensions;
 using Jellyfin.Api.Helpers;
 using Jellyfin.Api.ModelBinders;
 using Jellyfin.Api.Models.StreamingDtos;
+using MediaBrowser.Common.Api;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;

+ 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>
 

+ 1 - 1
Jellyfin.Api/Middleware/BaseUrlRedirectionMiddleware.cs

@@ -1,6 +1,6 @@
 using System;
 using System.Threading.Tasks;
-using Jellyfin.Networking.Configuration;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.Configuration;

+ 0 - 1
Jellyfin.Api/Middleware/LanFilteringMiddleware.cs

@@ -1,5 +1,4 @@
 using System.Threading.Tasks;
-using Jellyfin.Networking.Configuration;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;

+ 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 - 5
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>
@@ -23,10 +23,6 @@
     <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
   </PropertyGroup>
 
-  <ItemGroup>
-    <PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
-  </ItemGroup>
-
   <!-- Code Analyzers -->
   <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
     <PackageReference Include="IDisposableAnalyzers">

+ 0 - 176
Jellyfin.Networking/Configuration/NetworkConfiguration.cs

@@ -1,176 +0,0 @@
-#pragma warning disable CA1819 // Properties should not return arrays
-
-using System;
-
-namespace Jellyfin.Networking.Configuration
-{
-    /// <summary>
-    /// Defines the <see cref="NetworkConfiguration" />.
-    /// </summary>
-    public class NetworkConfiguration
-    {
-        /// <summary>
-        /// The default value for <see cref="InternalHttpPort"/>.
-        /// </summary>
-        public const int DefaultHttpPort = 8096;
-
-        /// <summary>
-        /// The default value for <see cref="PublicHttpsPort"/> and <see cref="InternalHttpsPort"/>.
-        /// </summary>
-        public const int DefaultHttpsPort = 8920;
-
-        private string _baseUrl = string.Empty;
-
-        /// <summary>
-        /// Gets or sets a value used to specify the URL prefix that your Jellyfin instance can be accessed at.
-        /// </summary>
-        public string BaseUrl
-        {
-            get => _baseUrl;
-            set
-            {
-                // Normalize the start of the string
-                if (string.IsNullOrWhiteSpace(value))
-                {
-                    // If baseUrl is empty, set an empty prefix string
-                    _baseUrl = string.Empty;
-                    return;
-                }
-
-                if (value[0] != '/')
-                {
-                    // If baseUrl was not configured with a leading slash, append one for consistency
-                    value = "/" + value;
-                }
-
-                // Normalize the end of the string
-                if (value[^1] == '/')
-                {
-                    // If baseUrl was configured with a trailing slash, remove it for consistency
-                    value = value.Remove(value.Length - 1);
-                }
-
-                _baseUrl = value;
-            }
-        }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether to use HTTPS.
-        /// </summary>
-        /// <remarks>
-        /// In order for HTTPS to be used, in addition to setting this to true, valid values must also be
-        /// provided for <see cref="CertificatePath"/> and <see cref="CertificatePassword"/>.
-        /// </remarks>
-        public bool EnableHttps { get; set; }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether the server should force connections over HTTPS.
-        /// </summary>
-        public bool RequireHttps { get; set; }
-
-        /// <summary>
-        /// Gets or sets the filesystem path of an X.509 certificate to use for SSL.
-        /// </summary>
-        public string CertificatePath { get; set; } = string.Empty;
-
-        /// <summary>
-        /// Gets or sets the password required to access the X.509 certificate data in the file specified by <see cref="CertificatePath"/>.
-        /// </summary>
-        public string CertificatePassword { get; set; } = string.Empty;
-
-        /// <summary>
-        /// Gets or sets the internal HTTP server port.
-        /// </summary>
-        /// <value>The HTTP server port.</value>
-        public int InternalHttpPort { get; set; } = DefaultHttpPort;
-
-        /// <summary>
-        /// Gets or sets the internal HTTPS server port.
-        /// </summary>
-        /// <value>The HTTPS server port.</value>
-        public int InternalHttpsPort { get; set; } = DefaultHttpsPort;
-
-        /// <summary>
-        /// Gets or sets the public HTTP port.
-        /// </summary>
-        /// <value>The public HTTP port.</value>
-        public int PublicHttpPort { get; set; } = DefaultHttpPort;
-
-        /// <summary>
-        /// Gets or sets the public HTTPS port.
-        /// </summary>
-        /// <value>The public HTTPS port.</value>
-        public int PublicHttpsPort { get; set; } = DefaultHttpsPort;
-
-        /// <summary>
-        /// Gets or sets a value indicating whether Autodiscovery is enabled.
-        /// </summary>
-        public bool AutoDiscovery { get; set; } = true;
-
-        /// <summary>
-        /// Gets or sets a value indicating whether to enable automatic port forwarding.
-        /// </summary>
-        public bool EnableUPnP { get; set; }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether IPv6 is enabled.
-        /// </summary>
-        public bool EnableIPv4 { get; set; } = true;
-
-        /// <summary>
-        /// Gets or sets a value indicating whether IPv6 is enabled.
-        /// </summary>
-        public bool EnableIPv6 { get; set; }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether access from outside of the LAN is permitted.
-        /// </summary>
-        public bool EnableRemoteAccess { get; set; } = true;
-
-        /// <summary>
-        /// Gets or sets the subnets that are deemed to make up the LAN.
-        /// </summary>
-        public string[] LocalNetworkSubnets { get; set; } = Array.Empty<string>();
-
-        /// <summary>
-        /// Gets or sets the interface addresses which Jellyfin will bind to. If empty, all interfaces will be used.
-        /// </summary>
-        public string[] LocalNetworkAddresses { get; set; } = Array.Empty<string>();
-
-        /// <summary>
-        /// Gets or sets the known proxies.
-        /// </summary>
-        public string[] KnownProxies { get; set; } = Array.Empty<string>();
-
-        /// <summary>
-        /// Gets or sets a value indicating whether address names that match <see cref="VirtualInterfaceNames"/> should be ignored for the purposes of binding.
-        /// </summary>
-        public bool IgnoreVirtualInterfaces { get; set; } = true;
-
-        /// <summary>
-        /// Gets or sets a value indicating the interface name prefixes that should be ignored. The list can be comma separated and values are case-insensitive. <seealso cref="IgnoreVirtualInterfaces"/>.
-        /// </summary>
-        public string[] VirtualInterfaceNames { get; set; } = new string[] { "veth" };
-
-        /// <summary>
-        /// Gets or sets a value indicating whether the published server uri is based on information in HTTP requests.
-        /// </summary>
-        public bool EnablePublishedServerUriByRequest { get; set; } = false;
-
-        /// <summary>
-        /// Gets or sets the PublishedServerUriBySubnet
-        /// Gets or sets PublishedServerUri to advertise for specific subnets.
-        /// </summary>
-        public string[] PublishedServerUriBySubnet { get; set; } = Array.Empty<string>();
-
-        /// <summary>
-        /// Gets or sets the filter for remote IP connectivity. Used in conjunction with <seealso cref="IsRemoteIPFilterBlacklist"/>.
-        /// </summary>
-        public string[] RemoteIPFilter { get; set; } = Array.Empty<string>();
-
-        /// <summary>
-        /// Gets or sets a value indicating whether <seealso cref="RemoteIPFilter"/> contains a blacklist or a whitelist. Default is a whitelist.
-        /// </summary>
-        public bool IsRemoteIPFilterBlacklist { get; set; }
-    }
-}

+ 0 - 20
Jellyfin.Networking/Configuration/NetworkConfigurationExtensions.cs

@@ -1,20 +0,0 @@
-using MediaBrowser.Common.Configuration;
-
-namespace Jellyfin.Networking.Configuration
-{
-    /// <summary>
-    /// Defines the <see cref="NetworkConfigurationExtensions" />.
-    /// </summary>
-    public static class NetworkConfigurationExtensions
-    {
-        /// <summary>
-        /// Retrieves the network configuration.
-        /// </summary>
-        /// <param name="config">The <see cref="IConfigurationManager"/>.</param>
-        /// <returns>The <see cref="NetworkConfiguration"/>.</returns>
-        public static NetworkConfiguration GetNetworkConfiguration(this IConfigurationManager config)
-        {
-            return config.GetConfiguration<NetworkConfiguration>(NetworkConfigurationStore.StoreKey);
-        }
-    }
-}

+ 0 - 23
Jellyfin.Networking/Configuration/NetworkConfigurationFactory.cs

@@ -1,23 +0,0 @@
-using System.Collections.Generic;
-using MediaBrowser.Common.Configuration;
-
-namespace Jellyfin.Networking.Configuration
-{
-    /// <summary>
-    /// Defines the <see cref="NetworkConfigurationFactory" />.
-    /// </summary>
-    public class NetworkConfigurationFactory : IConfigurationFactory
-    {
-        /// <summary>
-        /// The GetConfigurations.
-        /// </summary>
-        /// <returns>The <see cref="IEnumerable{ConfigurationStore}"/>.</returns>
-        public IEnumerable<ConfigurationStore> GetConfigurations()
-        {
-            return new[]
-            {
-                new NetworkConfigurationStore()
-            };
-        }
-    }
-}

+ 0 - 24
Jellyfin.Networking/Configuration/NetworkConfigurationStore.cs

@@ -1,24 +0,0 @@
-using MediaBrowser.Common.Configuration;
-
-namespace Jellyfin.Networking.Configuration
-{
-    /// <summary>
-    /// A configuration that stores network related settings.
-    /// </summary>
-    public class NetworkConfigurationStore : ConfigurationStore
-    {
-        /// <summary>
-        /// The name of the configuration in the storage.
-        /// </summary>
-        public const string StoreKey = "network";
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="NetworkConfigurationStore"/> class.
-        /// </summary>
-        public NetworkConfigurationStore()
-        {
-            ConfigurationType = typeof(NetworkConfiguration);
-            Key = StoreKey;
-        }
-    }
-}

+ 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>

+ 41 - 42
Jellyfin.Networking/Manager/NetworkManager.cs

@@ -7,9 +7,6 @@ using System.Net;
 using System.Net.NetworkInformation;
 using System.Net.Sockets;
 using System.Threading;
-using Jellyfin.Networking.Configuration;
-using Jellyfin.Networking.Constants;
-using Jellyfin.Networking.Extensions;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Model.Net;
@@ -18,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
 {
@@ -289,12 +288,12 @@ namespace Jellyfin.Networking.Manager
 
                     if (IsIPv4Enabled)
                     {
-                        interfaces.Add(new IPData(IPAddress.Loopback, Network.IPv4RFC5735Loopback, "lo"));
+                        interfaces.Add(new IPData(IPAddress.Loopback, NetworkConstants.IPv4RFC5735Loopback, "lo"));
                     }
 
                     if (IsIPv6Enabled)
                     {
-                        interfaces.Add(new IPData(IPAddress.IPv6Loopback, Network.IPv6RFC4291Loopback, "lo"));
+                        interfaces.Add(new IPData(IPAddress.IPv6Loopback, NetworkConstants.IPv6RFC4291Loopback, "lo"));
                     }
                 }
 
@@ -319,24 +318,24 @@ namespace Jellyfin.Networking.Manager
                 var subnets = config.LocalNetworkSubnets;
 
                 // If no LAN addresses are specified, all private subnets and Loopback are deemed to be the LAN
-                if (!NetworkExtensions.TryParseToSubnets(subnets, out var lanSubnets, false) || lanSubnets.Count == 0)
+                if (!NetworkUtils.TryParseToSubnets(subnets, out var lanSubnets, false) || lanSubnets.Count == 0)
                 {
                     _logger.LogDebug("Using LAN interface addresses as user provided no LAN details.");
 
                     var fallbackLanSubnets = new List<IPNetwork>();
                     if (IsIPv6Enabled)
                     {
-                        fallbackLanSubnets.Add(Network.IPv6RFC4291Loopback); // RFC 4291 (Loopback)
-                        fallbackLanSubnets.Add(Network.IPv6RFC4291SiteLocal); // RFC 4291 (Site local)
-                        fallbackLanSubnets.Add(Network.IPv6RFC4193UniqueLocal); // RFC 4193 (Unique local)
+                        fallbackLanSubnets.Add(NetworkConstants.IPv6RFC4291Loopback); // RFC 4291 (Loopback)
+                        fallbackLanSubnets.Add(NetworkConstants.IPv6RFC4291SiteLocal); // RFC 4291 (Site local)
+                        fallbackLanSubnets.Add(NetworkConstants.IPv6RFC4193UniqueLocal); // RFC 4193 (Unique local)
                     }
 
                     if (IsIPv4Enabled)
                     {
-                        fallbackLanSubnets.Add(Network.IPv4RFC5735Loopback); // RFC 5735 (Loopback)
-                        fallbackLanSubnets.Add(Network.IPv4RFC1918PrivateClassA); // RFC 1918 (private Class A)
-                        fallbackLanSubnets.Add(Network.IPv4RFC1918PrivateClassB); // RFC 1918 (private Class B)
-                        fallbackLanSubnets.Add(Network.IPv4RFC1918PrivateClassC); // RFC 1918 (private Class C)
+                        fallbackLanSubnets.Add(NetworkConstants.IPv4RFC5735Loopback); // RFC 5735 (Loopback)
+                        fallbackLanSubnets.Add(NetworkConstants.IPv4RFC1918PrivateClassA); // RFC 1918 (private Class A)
+                        fallbackLanSubnets.Add(NetworkConstants.IPv4RFC1918PrivateClassB); // RFC 1918 (private Class B)
+                        fallbackLanSubnets.Add(NetworkConstants.IPv4RFC1918PrivateClassC); // RFC 1918 (private Class C)
                     }
 
                     _lanSubnets = fallbackLanSubnets;
@@ -346,7 +345,7 @@ namespace Jellyfin.Networking.Manager
                     _lanSubnets = lanSubnets;
                 }
 
-                _excludedSubnets = NetworkExtensions.TryParseToSubnets(subnets, out var excludedSubnets, true)
+                _excludedSubnets = NetworkUtils.TryParseToSubnets(subnets, out var excludedSubnets, true)
                     ? excludedSubnets
                     : new List<IPNetwork>();
             }
@@ -364,7 +363,7 @@ namespace Jellyfin.Networking.Manager
                 var localNetworkAddresses = config.LocalNetworkAddresses;
                 if (localNetworkAddresses.Length > 0 && !string.IsNullOrWhiteSpace(localNetworkAddresses[0]))
                 {
-                    var bindAddresses = localNetworkAddresses.Select(p => NetworkExtensions.TryParseToSubnet(p, out var network)
+                    var bindAddresses = localNetworkAddresses.Select(p => NetworkUtils.TryParseToSubnet(p, out var network)
                         ? network.Prefix
                         : (interfaces.Where(x => x.Name.Equals(p, StringComparison.OrdinalIgnoreCase))
                             .Select(x => x.Address)
@@ -375,12 +374,12 @@ namespace Jellyfin.Networking.Manager
 
                     if (bindAddresses.Contains(IPAddress.Loopback) && !interfaces.Any(i => i.Address.Equals(IPAddress.Loopback)))
                     {
-                        interfaces.Add(new IPData(IPAddress.Loopback, Network.IPv4RFC5735Loopback, "lo"));
+                        interfaces.Add(new IPData(IPAddress.Loopback, NetworkConstants.IPv4RFC5735Loopback, "lo"));
                     }
 
                     if (bindAddresses.Contains(IPAddress.IPv6Loopback) && !interfaces.Any(i => i.Address.Equals(IPAddress.IPv6Loopback)))
                     {
-                        interfaces.Add(new IPData(IPAddress.IPv6Loopback, Network.IPv6RFC4291Loopback, "lo"));
+                        interfaces.Add(new IPData(IPAddress.IPv6Loopback, NetworkConstants.IPv6RFC4291Loopback, "lo"));
                     }
                 }
 
@@ -426,12 +425,12 @@ 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>();
                     var remoteFilteredSubnets = remoteIPFilter.Where(x => x.Contains('/', StringComparison.OrdinalIgnoreCase)).ToArray();
-                    if (NetworkExtensions.TryParseToSubnets(remoteFilteredSubnets, out var remoteAddressFilterResult, false))
+                    if (NetworkUtils.TryParseToSubnets(remoteFilteredSubnets, out var remoteAddressFilterResult, false))
                     {
                         remoteAddressFilter = remoteAddressFilterResult.ToList();
                     }
@@ -442,7 +441,7 @@ namespace Jellyfin.Networking.Manager
                     {
                         if (IPAddress.TryParse(ip, out var ipp))
                         {
-                            remoteAddressFilter.Add(new IPNetwork(ipp, ipp.AddressFamily == AddressFamily.InterNetwork ? Network.MinimumIPv4PrefixSize : Network.MinimumIPv6PrefixSize));
+                            remoteAddressFilter.Add(new IPNetwork(ipp, ipp.AddressFamily == AddressFamily.InterNetwork ? NetworkConstants.MinimumIPv4PrefixSize : NetworkConstants.MinimumIPv6PrefixSize));
                         }
                     }
 
@@ -470,13 +469,13 @@ namespace Jellyfin.Networking.Manager
                 {
                     publishedServerUrls.Add(
                         new PublishedServerUriOverride(
-                            new IPData(IPAddress.Any, Network.IPv4Any),
+                            new IPData(IPAddress.Any, NetworkConstants.IPv4Any),
                             startupOverrideKey,
                             true,
                             true));
                     publishedServerUrls.Add(
                         new PublishedServerUriOverride(
-                            new IPData(IPAddress.IPv6Any, Network.IPv6Any),
+                            new IPData(IPAddress.IPv6Any, NetworkConstants.IPv6Any),
                             startupOverrideKey,
                             true,
                             true));
@@ -502,13 +501,13 @@ namespace Jellyfin.Networking.Manager
                         publishedServerUrls.Clear();
                         publishedServerUrls.Add(
                             new PublishedServerUriOverride(
-                                new IPData(IPAddress.Any, Network.IPv4Any),
+                                new IPData(IPAddress.Any, NetworkConstants.IPv4Any),
                                 replacement,
                                 true,
                                 true));
                         publishedServerUrls.Add(
                             new PublishedServerUriOverride(
-                                new IPData(IPAddress.IPv6Any, Network.IPv6Any),
+                                new IPData(IPAddress.IPv6Any, NetworkConstants.IPv6Any),
                                 replacement,
                                 true,
                                 true));
@@ -518,13 +517,13 @@ namespace Jellyfin.Networking.Manager
                     {
                         publishedServerUrls.Add(
                             new PublishedServerUriOverride(
-                                new IPData(IPAddress.Any, Network.IPv4Any),
+                                new IPData(IPAddress.Any, NetworkConstants.IPv4Any),
                                 replacement,
                                 false,
                                 true));
                         publishedServerUrls.Add(
                             new PublishedServerUriOverride(
-                                new IPData(IPAddress.IPv6Any, Network.IPv6Any),
+                                new IPData(IPAddress.IPv6Any, NetworkConstants.IPv6Any),
                                 replacement,
                                 false,
                                 true));
@@ -542,7 +541,7 @@ namespace Jellyfin.Networking.Manager
                                     false));
                         }
                     }
-                    else if (NetworkExtensions.TryParseToSubnet(identifier, out var result) && result is not null)
+                    else if (NetworkUtils.TryParseToSubnet(identifier, out var result) && result is not null)
                     {
                         var data = new IPData(result.Prefix, result);
                         publishedServerUrls.Add(
@@ -608,7 +607,7 @@ namespace Jellyfin.Networking.Manager
                 foreach (var details in interfaceList)
                 {
                     var parts = details.Split(',');
-                    if (NetworkExtensions.TryParseToSubnet(parts[0], out var subnet))
+                    if (NetworkUtils.TryParseToSubnet(parts[0], out var subnet))
                     {
                         var address = subnet.Prefix;
                         var index = int.Parse(parts[1], CultureInfo.InvariantCulture);
@@ -724,12 +723,12 @@ namespace Jellyfin.Networking.Manager
             var loopbackNetworks = new List<IPData>();
             if (IsIPv4Enabled)
             {
-                loopbackNetworks.Add(new IPData(IPAddress.Loopback, Network.IPv4RFC5735Loopback, "lo"));
+                loopbackNetworks.Add(new IPData(IPAddress.Loopback, NetworkConstants.IPv4RFC5735Loopback, "lo"));
             }
 
             if (IsIPv6Enabled)
             {
-                loopbackNetworks.Add(new IPData(IPAddress.IPv6Loopback, Network.IPv6RFC4291Loopback, "lo"));
+                loopbackNetworks.Add(new IPData(IPAddress.IPv6Loopback, NetworkConstants.IPv6RFC4291Loopback, "lo"));
             }
 
             return loopbackNetworks;
@@ -748,11 +747,11 @@ namespace Jellyfin.Networking.Manager
             if (IsIPv4Enabled && IsIPv6Enabled)
             {
                 // Kestrel source code shows it uses Sockets.DualMode - so this also covers IPAddress.Any by default
-                result.Add(new IPData(IPAddress.IPv6Any, Network.IPv6Any));
+                result.Add(new IPData(IPAddress.IPv6Any, NetworkConstants.IPv6Any));
             }
             else if (IsIPv4Enabled)
             {
-                result.Add(new IPData(IPAddress.Any, Network.IPv4Any));
+                result.Add(new IPData(IPAddress.Any, NetworkConstants.IPv4Any));
             }
             else if (IsIPv6Enabled)
             {
@@ -772,7 +771,7 @@ namespace Jellyfin.Networking.Manager
         /// <inheritdoc/>
         public string GetBindAddress(string source, out int? port)
         {
-            if (!NetworkExtensions.TryParseHost(source, out var addresses, IsIPv4Enabled, IsIPv6Enabled))
+            if (!NetworkUtils.TryParseHost(source, out var addresses, IsIPv4Enabled, IsIPv6Enabled))
             {
                 addresses = Array.Empty<IPAddress>();
             }
@@ -847,7 +846,7 @@ namespace Jellyfin.Networking.Manager
             // If no source address is given, use the preferred (first) interface
             if (source is null)
             {
-                result = NetworkExtensions.FormatIPString(availableInterfaces.First().Address);
+                result = NetworkUtils.FormatIPString(availableInterfaces.First().Address);
                 _logger.LogDebug("{Source}: Using first internal interface as bind address: {Result}", source, result);
                 return result;
             }
@@ -858,14 +857,14 @@ namespace Jellyfin.Networking.Manager
             {
                 if (intf.Subnet.Contains(source))
                 {
-                    result = NetworkExtensions.FormatIPString(intf.Address);
+                    result = NetworkUtils.FormatIPString(intf.Address);
                     _logger.LogDebug("{Source}: Found interface with matching subnet, using it as bind address: {Result}", source, result);
                     return result;
                 }
             }
 
             // Fallback to first available interface
-            result = NetworkExtensions.FormatIPString(availableInterfaces[0].Address);
+            result = NetworkUtils.FormatIPString(availableInterfaces[0].Address);
             _logger.LogDebug("{Source}: No matching interfaces found, using preferred interface as bind address: {Result}", source, result);
             return result;
         }
@@ -882,12 +881,12 @@ namespace Jellyfin.Networking.Manager
         /// <inheritdoc/>
         public bool IsInLocalNetwork(string address)
         {
-            if (NetworkExtensions.TryParseToSubnet(address, out var subnet))
+            if (NetworkUtils.TryParseToSubnet(address, out var subnet))
             {
                 return IPAddress.IsLoopback(subnet.Prefix) || (_lanSubnets.Any(x => x.Contains(subnet.Prefix)) && !_excludedSubnets.Any(x => x.Contains(subnet.Prefix)));
             }
 
-            if (NetworkExtensions.TryParseHost(address, out var addresses, IsIPv4Enabled, IsIPv6Enabled))
+            if (NetworkUtils.TryParseHost(address, out var addresses, IsIPv4Enabled, IsIPv6Enabled))
             {
                 foreach (var ept in addresses)
                 {
@@ -1045,7 +1044,7 @@ namespace Jellyfin.Networking.Manager
                         .Select(x => x.Address)
                         .First();
 
-                    result = NetworkExtensions.FormatIPString(bindAddress);
+                    result = NetworkUtils.FormatIPString(bindAddress);
                     _logger.LogDebug("{Source}: External request received, matching external bind address found: {Result}", source, result);
                     return true;
                 }
@@ -1064,7 +1063,7 @@ namespace Jellyfin.Networking.Manager
 
                 if (bindAddress is not null)
                 {
-                    result = NetworkExtensions.FormatIPString(bindAddress);
+                    result = NetworkUtils.FormatIPString(bindAddress);
                     _logger.LogDebug("{Source}: Internal request received, matching internal bind address found: {Result}", source, result);
                     return true;
                 }
@@ -1098,14 +1097,14 @@ namespace Jellyfin.Networking.Manager
             {
                 if (intf.Subnet.Contains(source))
                 {
-                    result = NetworkExtensions.FormatIPString(intf.Address);
+                    result = NetworkUtils.FormatIPString(intf.Address);
                     _logger.LogDebug("{Source}: Found external interface with matching subnet, using it as bind address: {Result}", source, result);
                     return true;
                 }
             }
 
             // Fallback to first external interface.
-            result = NetworkExtensions.FormatIPString(extResult[0].Address);
+            result = NetworkUtils.FormatIPString(extResult[0].Address);
             _logger.LogDebug("{Source}: Using first external interface as bind address: {Result}", source, result);
             return true;
         }

+ 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();

+ 1 - 1
Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs

@@ -1,6 +1,6 @@
 using System.Collections.Generic;
 using Jellyfin.Api.Middleware;
-using Jellyfin.Networking.Configuration;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using Microsoft.AspNetCore.Builder;
 using Microsoft.OpenApi.Models;

+ 9 - 9
Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs

@@ -20,11 +20,10 @@ using Jellyfin.Api.Formatters;
 using Jellyfin.Api.ModelBinders;
 using Jellyfin.Data.Enums;
 using Jellyfin.Extensions.Json;
-using Jellyfin.Networking.Configuration;
-using Jellyfin.Networking.Constants;
-using Jellyfin.Networking.Extensions;
 using Jellyfin.Server.Configuration;
 using Jellyfin.Server.Filters;
+using MediaBrowser.Common.Api;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Session;
 using Microsoft.AspNetCore.Authentication;
@@ -38,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
 {
@@ -275,20 +275,20 @@ namespace Jellyfin.Server.Extensions
             {
                 if (IPAddress.TryParse(allowedProxies[i], out var addr))
                 {
-                    AddIPAddress(config, options, addr, addr.AddressFamily == AddressFamily.InterNetwork ? Network.MinimumIPv4PrefixSize : Network.MinimumIPv6PrefixSize);
+                    AddIPAddress(config, options, addr, addr.AddressFamily == AddressFamily.InterNetwork ? NetworkConstants.MinimumIPv4PrefixSize : NetworkConstants.MinimumIPv6PrefixSize);
                 }
-                else if (NetworkExtensions.TryParseToSubnet(allowedProxies[i], out var subnet))
+                else if (NetworkUtils.TryParseToSubnet(allowedProxies[i], out var subnet))
                 {
                     if (subnet is not null)
                     {
                         AddIPAddress(config, options, subnet.Prefix, subnet.PrefixLength);
                     }
                 }
-                else if (NetworkExtensions.TryParseHost(allowedProxies[i], out var addresses, config.EnableIPv4, config.EnableIPv6))
+                else if (NetworkUtils.TryParseHost(allowedProxies[i], out var addresses, config.EnableIPv4, config.EnableIPv6))
                 {
                     foreach (var address in addresses)
                     {
-                        AddIPAddress(config, options, address, address.AddressFamily == AddressFamily.InterNetwork ? Network.MinimumIPv4PrefixSize : Network.MinimumIPv6PrefixSize);
+                        AddIPAddress(config, options, address, address.AddressFamily == AddressFamily.InterNetwork ? NetworkConstants.MinimumIPv4PrefixSize : NetworkConstants.MinimumIPv6PrefixSize);
                     }
                 }
             }
@@ -306,13 +306,13 @@ namespace Jellyfin.Server.Extensions
                 return;
             }
 
-            if (prefixLength == Network.MinimumIPv4PrefixSize)
+            if (prefixLength == NetworkConstants.MinimumIPv4PrefixSize)
             {
                 options.KnownProxies.Add(addr);
             }
             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 - 1
Jellyfin.Server/Migrations/PreStartupRoutines/MigrateNetworkConfiguration.cs

@@ -3,7 +3,7 @@ using System.IO;
 using System.Xml;
 using System.Xml.Serialization;
 using Emby.Server.Implementations;
-using Jellyfin.Networking.Configuration;
+using MediaBrowser.Common.Net;
 using Microsoft.Extensions.Logging;
 
 namespace Jellyfin.Server.Migrations.PreStartupRoutines;

+ 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 - 2
Jellyfin.Server/Startup.cs

@@ -7,7 +7,6 @@ using System.Text;
 using Emby.Dlna.Extensions;
 using Jellyfin.Api.Middleware;
 using Jellyfin.MediaEncoding.Hls.Extensions;
-using Jellyfin.Networking.Configuration;
 using Jellyfin.Networking.HappyEyeballs;
 using Jellyfin.Server.Extensions;
 using Jellyfin.Server.HealthChecks;
@@ -36,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
Jellyfin.Api/Constants/Policies.cs → MediaBrowser.Common/Api/Policies.cs

@@ -1,4 +1,4 @@
-namespace Jellyfin.Api.Constants;
+namespace MediaBrowser.Common.Api;
 
 /// <summary>
 /// Policies for the API authorization.

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

@@ -21,7 +21,6 @@
   <ItemGroup>
     <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
     <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
-    <PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
   </ItemGroup>
 
   <ItemGroup>
@@ -29,7 +28,7 @@
   </ItemGroup>
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <PublishRepositoryUrl>true</PublishRepositoryUrl>

+ 175 - 0
MediaBrowser.Common/Net/NetworkConfiguration.cs

@@ -0,0 +1,175 @@
+#pragma warning disable CA1819 // Properties should not return arrays
+
+using System;
+
+namespace MediaBrowser.Common.Net;
+
+/// <summary>
+/// Defines the <see cref="NetworkConfiguration" />.
+/// </summary>
+public class NetworkConfiguration
+{
+    /// <summary>
+    /// The default value for <see cref="InternalHttpPort"/>.
+    /// </summary>
+    public const int DefaultHttpPort = 8096;
+
+    /// <summary>
+    /// The default value for <see cref="PublicHttpsPort"/> and <see cref="InternalHttpsPort"/>.
+    /// </summary>
+    public const int DefaultHttpsPort = 8920;
+
+    private string _baseUrl = string.Empty;
+
+    /// <summary>
+    /// Gets or sets a value used to specify the URL prefix that your Jellyfin instance can be accessed at.
+    /// </summary>
+    public string BaseUrl
+    {
+        get => _baseUrl;
+        set
+        {
+            // Normalize the start of the string
+            if (string.IsNullOrWhiteSpace(value))
+            {
+                // If baseUrl is empty, set an empty prefix string
+                _baseUrl = string.Empty;
+                return;
+            }
+
+            if (value[0] != '/')
+            {
+                // If baseUrl was not configured with a leading slash, append one for consistency
+                value = "/" + value;
+            }
+
+            // Normalize the end of the string
+            if (value[^1] == '/')
+            {
+                // If baseUrl was configured with a trailing slash, remove it for consistency
+                value = value.Remove(value.Length - 1);
+            }
+
+            _baseUrl = value;
+        }
+    }
+
+    /// <summary>
+    /// Gets or sets a value indicating whether to use HTTPS.
+    /// </summary>
+    /// <remarks>
+    /// In order for HTTPS to be used, in addition to setting this to true, valid values must also be
+    /// provided for <see cref="CertificatePath"/> and <see cref="CertificatePassword"/>.
+    /// </remarks>
+    public bool EnableHttps { get; set; }
+
+    /// <summary>
+    /// Gets or sets a value indicating whether the server should force connections over HTTPS.
+    /// </summary>
+    public bool RequireHttps { get; set; }
+
+    /// <summary>
+    /// Gets or sets the filesystem path of an X.509 certificate to use for SSL.
+    /// </summary>
+    public string CertificatePath { get; set; } = string.Empty;
+
+    /// <summary>
+    /// Gets or sets the password required to access the X.509 certificate data in the file specified by <see cref="CertificatePath"/>.
+    /// </summary>
+    public string CertificatePassword { get; set; } = string.Empty;
+
+    /// <summary>
+    /// Gets or sets the internal HTTP server port.
+    /// </summary>
+    /// <value>The HTTP server port.</value>
+    public int InternalHttpPort { get; set; } = DefaultHttpPort;
+
+    /// <summary>
+    /// Gets or sets the internal HTTPS server port.
+    /// </summary>
+    /// <value>The HTTPS server port.</value>
+    public int InternalHttpsPort { get; set; } = DefaultHttpsPort;
+
+    /// <summary>
+    /// Gets or sets the public HTTP port.
+    /// </summary>
+    /// <value>The public HTTP port.</value>
+    public int PublicHttpPort { get; set; } = DefaultHttpPort;
+
+    /// <summary>
+    /// Gets or sets the public HTTPS port.
+    /// </summary>
+    /// <value>The public HTTPS port.</value>
+    public int PublicHttpsPort { get; set; } = DefaultHttpsPort;
+
+    /// <summary>
+    /// Gets or sets a value indicating whether Autodiscovery is enabled.
+    /// </summary>
+    public bool AutoDiscovery { get; set; } = true;
+
+    /// <summary>
+    /// Gets or sets a value indicating whether to enable automatic port forwarding.
+    /// </summary>
+    public bool EnableUPnP { get; set; }
+
+    /// <summary>
+    /// Gets or sets a value indicating whether IPv6 is enabled.
+    /// </summary>
+    public bool EnableIPv4 { get; set; } = true;
+
+    /// <summary>
+    /// Gets or sets a value indicating whether IPv6 is enabled.
+    /// </summary>
+    public bool EnableIPv6 { get; set; }
+
+    /// <summary>
+    /// Gets or sets a value indicating whether access from outside of the LAN is permitted.
+    /// </summary>
+    public bool EnableRemoteAccess { get; set; } = true;
+
+    /// <summary>
+    /// Gets or sets the subnets that are deemed to make up the LAN.
+    /// </summary>
+    public string[] LocalNetworkSubnets { get; set; } = Array.Empty<string>();
+
+    /// <summary>
+    /// Gets or sets the interface addresses which Jellyfin will bind to. If empty, all interfaces will be used.
+    /// </summary>
+    public string[] LocalNetworkAddresses { get; set; } = Array.Empty<string>();
+
+    /// <summary>
+    /// Gets or sets the known proxies.
+    /// </summary>
+    public string[] KnownProxies { get; set; } = Array.Empty<string>();
+
+    /// <summary>
+    /// Gets or sets a value indicating whether address names that match <see cref="VirtualInterfaceNames"/> should be ignored for the purposes of binding.
+    /// </summary>
+    public bool IgnoreVirtualInterfaces { get; set; } = true;
+
+    /// <summary>
+    /// Gets or sets a value indicating the interface name prefixes that should be ignored. The list can be comma separated and values are case-insensitive. <seealso cref="IgnoreVirtualInterfaces"/>.
+    /// </summary>
+    public string[] VirtualInterfaceNames { get; set; } = new string[] { "veth" };
+
+    /// <summary>
+    /// Gets or sets a value indicating whether the published server uri is based on information in HTTP requests.
+    /// </summary>
+    public bool EnablePublishedServerUriByRequest { get; set; } = false;
+
+    /// <summary>
+    /// Gets or sets the PublishedServerUriBySubnet
+    /// Gets or sets PublishedServerUri to advertise for specific subnets.
+    /// </summary>
+    public string[] PublishedServerUriBySubnet { get; set; } = Array.Empty<string>();
+
+    /// <summary>
+    /// Gets or sets the filter for remote IP connectivity. Used in conjunction with <seealso cref="IsRemoteIPFilterBlacklist"/>.
+    /// </summary>
+    public string[] RemoteIPFilter { get; set; } = Array.Empty<string>();
+
+    /// <summary>
+    /// Gets or sets a value indicating whether <seealso cref="RemoteIPFilter"/> contains a blacklist or a whitelist. Default is a whitelist.
+    /// </summary>
+    public bool IsRemoteIPFilterBlacklist { get; set; }
+}

+ 19 - 0
MediaBrowser.Common/Net/NetworkConfigurationExtensions.cs

@@ -0,0 +1,19 @@
+using MediaBrowser.Common.Configuration;
+
+namespace MediaBrowser.Common.Net;
+
+/// <summary>
+/// Defines the <see cref="NetworkConfigurationExtensions" />.
+/// </summary>
+public static class NetworkConfigurationExtensions
+{
+    /// <summary>
+    /// Retrieves the network configuration.
+    /// </summary>
+    /// <param name="config">The <see cref="IConfigurationManager"/>.</param>
+    /// <returns>The <see cref="NetworkConfiguration"/>.</returns>
+    public static NetworkConfiguration GetNetworkConfiguration(this IConfigurationManager config)
+    {
+        return config.GetConfiguration<NetworkConfiguration>(NetworkConfigurationStore.StoreKey);
+    }
+}

+ 22 - 0
MediaBrowser.Common/Net/NetworkConfigurationFactory.cs

@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+using MediaBrowser.Common.Configuration;
+
+namespace MediaBrowser.Common.Net;
+
+/// <summary>
+/// Defines the <see cref="NetworkConfigurationFactory" />.
+/// </summary>
+public class NetworkConfigurationFactory : IConfigurationFactory
+{
+    /// <summary>
+    /// The GetConfigurations.
+    /// </summary>
+    /// <returns>The <see cref="IEnumerable{ConfigurationStore}"/>.</returns>
+    public IEnumerable<ConfigurationStore> GetConfigurations()
+    {
+        return new[]
+        {
+            new NetworkConfigurationStore()
+        };
+    }
+}

+ 23 - 0
MediaBrowser.Common/Net/NetworkConfigurationStore.cs

@@ -0,0 +1,23 @@
+using MediaBrowser.Common.Configuration;
+
+namespace MediaBrowser.Common.Net;
+
+/// <summary>
+/// A configuration that stores network related settings.
+/// </summary>
+public class NetworkConfigurationStore : ConfigurationStore
+{
+    /// <summary>
+    /// The name of the configuration in the storage.
+    /// </summary>
+    public const string StoreKey = "network";
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="NetworkConfigurationStore"/> class.
+    /// </summary>
+    public NetworkConfigurationStore()
+    {
+        ConfigurationType = typeof(NetworkConfiguration);
+        Key = StoreKey;
+    }
+}

+ 3 - 3
Jellyfin.Networking/Constants/Network.cs → MediaBrowser.Common/Net/NetworkConstants.cs

@@ -1,12 +1,12 @@
 using System.Net;
-using Microsoft.AspNetCore.HttpOverrides;
+using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
 
-namespace Jellyfin.Networking.Constants;
+namespace MediaBrowser.Common.Net;
 
 /// <summary>
 /// Networking constants.
 /// </summary>
-public static class Network
+public static class NetworkConstants
 {
     /// <summary>
     /// IPv4 mask bytes.

+ 24 - 42
Jellyfin.Networking/Extensions/NetworkExtensions.cs → MediaBrowser.Common/Net/NetworkUtils.cs

@@ -5,15 +5,14 @@ using System.Net;
 using System.Net.Sockets;
 using System.Text.RegularExpressions;
 using Jellyfin.Extensions;
-using Jellyfin.Networking.Constants;
-using Microsoft.AspNetCore.HttpOverrides;
+using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
 
-namespace Jellyfin.Networking.Extensions;
+namespace MediaBrowser.Common.Net;
 
 /// <summary>
-/// Defines the <see cref="NetworkExtensions" />.
+/// Defines the <see cref="NetworkUtils" />.
 /// </summary>
-public static partial class NetworkExtensions
+public static partial class NetworkUtils
 {
     // Use regular expression as CheckHostName isn't RFC5892 compliant.
     // Modified from gSkinner's expression at https://stackoverflow.com/questions/11809631/fully-qualified-domain-name-validation
@@ -59,7 +58,7 @@ public static partial class NetworkExtensions
     /// <returns>String value of the subnet mask in dotted decimal notation.</returns>
     public static IPAddress CidrToMask(byte cidr, AddressFamily family)
     {
-        uint addr = 0xFFFFFFFF << ((family == AddressFamily.InterNetwork ? Network.MinimumIPv4PrefixSize : Network.MinimumIPv6PrefixSize) - cidr);
+        uint addr = 0xFFFFFFFF << ((family == AddressFamily.InterNetwork ? NetworkConstants.MinimumIPv4PrefixSize : NetworkConstants.MinimumIPv6PrefixSize) - cidr);
         addr = ((addr & 0xff000000) >> 24)
                 | ((addr & 0x00ff0000) >> 8)
                 | ((addr & 0x0000ff00) << 8)
@@ -75,7 +74,7 @@ public static partial class NetworkExtensions
     /// <returns>String value of the subnet mask in dotted decimal notation.</returns>
     public static IPAddress CidrToMask(int cidr, AddressFamily family)
     {
-        uint addr = 0xFFFFFFFF << ((family == AddressFamily.InterNetwork ? Network.MinimumIPv4PrefixSize : Network.MinimumIPv6PrefixSize) - cidr);
+        uint addr = 0xFFFFFFFF << ((family == AddressFamily.InterNetwork ? NetworkConstants.MinimumIPv4PrefixSize : NetworkConstants.MinimumIPv6PrefixSize) - cidr);
         addr = ((addr & 0xff000000) >> 24)
                 | ((addr & 0x00ff0000) >> 8)
                 | ((addr & 0x0000ff00) << 8)
@@ -100,7 +99,7 @@ public static partial class NetworkExtensions
         }
 
         // GetAddressBytes
-        Span<byte> bytes = stackalloc byte[mask.AddressFamily == AddressFamily.InterNetwork ? Network.IPv4MaskBytes : Network.IPv6MaskBytes];
+        Span<byte> bytes = stackalloc byte[mask.AddressFamily == AddressFamily.InterNetwork ? NetworkConstants.IPv4MaskBytes : NetworkConstants.IPv6MaskBytes];
         if (!mask.TryWriteBytes(bytes, out var bytesWritten))
         {
             Console.WriteLine("Unable to write address bytes, only ${bytesWritten} bytes written.");
@@ -198,46 +197,29 @@ public static partial class NetworkExtensions
     /// <returns><c>True</c> if parsing was successful.</returns>
     public static bool TryParseToSubnet(ReadOnlySpan<char> value, [NotNullWhen(true)] out IPNetwork? result, bool negated = false)
     {
-        var splitString = value.Trim().Split('/');
-        if (splitString.MoveNext())
+        value = value.Trim();
+        if (value.Contains('/'))
         {
-            var ipBlock = splitString.Current;
-            var address = IPAddress.None;
-            if (negated && ipBlock.StartsWith("!") && IPAddress.TryParse(ipBlock[1..], out var tmpAddress))
+            if (negated && value.StartsWith("!") && IPNetwork.TryParse(value[1..], out result))
             {
-                address = tmpAddress;
+                return true;
             }
-            else if (!negated && IPAddress.TryParse(ipBlock, out tmpAddress))
+            else if (!negated && IPNetwork.TryParse(value, out result))
             {
-                address = tmpAddress;
+                return true;
             }
-
-            if (address != IPAddress.None)
+        }
+        else if (IPAddress.TryParse(value, out var address))
+        {
+            if (address.AddressFamily == AddressFamily.InterNetwork)
             {
-                if (splitString.MoveNext())
-                {
-                    var subnetBlock = splitString.Current;
-                    if (int.TryParse(subnetBlock, out var netmask))
-                    {
-                        result = new IPNetwork(address, netmask);
-                        return true;
-                    }
-                    else if (IPAddress.TryParse(subnetBlock, out var netmaskAddress))
-                    {
-                        result = new IPNetwork(address, NetworkExtensions.MaskToCidr(netmaskAddress));
-                        return true;
-                    }
-                }
-                else if (address.AddressFamily == AddressFamily.InterNetwork)
-                {
-                    result = address.Equals(IPAddress.Any) ? Network.IPv4Any : new IPNetwork(address, Network.MinimumIPv4PrefixSize);
-                    return true;
-                }
-                else if (address.AddressFamily == AddressFamily.InterNetworkV6)
-                {
-                    result = address.Equals(IPAddress.IPv6Any) ? Network.IPv6Any : new IPNetwork(address, Network.MinimumIPv6PrefixSize);
-                    return true;
-                }
+                result = address.Equals(IPAddress.Any) ? NetworkConstants.IPv4Any : new IPNetwork(address, NetworkConstants.MinimumIPv4PrefixSize);
+                return true;
+            }
+            else if (address.AddressFamily == AddressFamily.InterNetworkV6)
+            {
+                result = address.Equals(IPAddress.IPv6Any) ? NetworkConstants.IPv6Any : new IPNetwork(address, NetworkConstants.MinimumIPv6PrefixSize);
+                return true;
             }
         }
 

+ 0 - 19
MediaBrowser.Common/Plugins/IPluginServiceRegistrator.cs

@@ -1,19 +0,0 @@
-namespace MediaBrowser.Common.Plugins
-{
-    using Microsoft.Extensions.DependencyInjection;
-
-    /// <summary>
-    /// Defines the <see cref="IPluginServiceRegistrator" />.
-    /// </summary>
-    public interface IPluginServiceRegistrator
-    {
-        /// <summary>
-        /// Registers the plugin's services with the service collection.
-        /// </summary>
-        /// <remarks>
-        /// This interface is only used for service registration and requires a parameterless constructor.
-        /// </remarks>
-        /// <param name="serviceCollection">The service collection.</param>
-        void RegisterServices(IServiceCollection serviceCollection);
-    }
-}

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

@@ -20,7 +20,6 @@
   <ItemGroup>
     <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
-    <PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
     <PackageReference Include="System.Threading.Tasks.Dataflow" />
   </ItemGroup>
 
@@ -35,7 +34,7 @@
   </ItemGroup>
 
   <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <PublishRepositoryUrl>true</PublishRepositoryUrl>

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно