Merge branch 'jellyfin:master' into master

This commit is contained in:
Abdulmohsen 2023-11-24 00:00:53 +03:00 committed by GitHub
commit 9d5dc4d71b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
180 changed files with 1401 additions and 1487 deletions

View File

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

View File

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

View File

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

View File

@ -10,7 +10,7 @@ parameters:
default: "tests/**/*Tests.csproj" default: "tests/**/*Tests.csproj"
- name: DotNetSdkVersion - name: DotNetSdkVersion
type: string type: string
default: 7.0.x default: 8.0.x
jobs: jobs:
- job: Test - job: Test
@ -94,5 +94,5 @@ jobs:
displayName: 'Publish OpenAPI Artifact' displayName: 'Publish OpenAPI Artifact'
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
inputs: 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' artifactName: 'OpenAPI Spec'

View File

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

View File

@ -24,14 +24,14 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
with: with:
dotnet-version: '7.0.x' dotnet-version: '8.0.x'
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5 uses: github/codeql-action/init@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
queries: +security-extended queries: +security-extended
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5 uses: github/codeql-action/autobuild@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5 uses: github/codeql-action/analyze@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8

View File

@ -21,7 +21,7 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
with: with:
dotnet-version: '7.0.x' dotnet-version: '8.0.x'
- name: Generate openapi.json - 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" 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 - name: Upload openapi.json
@ -30,7 +30,7 @@ jobs:
name: openapi-head name: openapi-head
retention-days: 14 retention-days: 14
if-no-files-found: error 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: openapi-base:
name: OpenAPI - BASE name: OpenAPI - BASE
@ -55,7 +55,7 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
with: with:
dotnet-version: '7.0.x' dotnet-version: '8.0.x'
- name: Generate openapi.json - 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" 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 - name: Upload openapi.json
@ -64,7 +64,7 @@ jobs:
name: openapi-base name: openapi-base
retention-days: 14 retention-days: 14
if-no-files-found: error 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: openapi-diff:
permissions: permissions:

View File

@ -9,7 +9,7 @@ on:
pull_request: pull_request:
env: env:
SDK_VERSION: "7.0.x" SDK_VERSION: "8.0.x"
jobs: jobs:
run-tests: run-tests:
@ -34,7 +34,7 @@ jobs:
--verbosity minimal --verbosity minimal
- name: Merge code coverage results - name: Merge code coverage results
uses: danielpalme/ReportGenerator-GitHub-Action@873ee34c88a6234bdab7fd264d3666fd1ab417f7 # 5 uses: danielpalme/ReportGenerator-GitHub-Action@4d510cbed8a05af5aefea46c7fd6e05b95844c89 # 5
with: with:
reports: "**/coverage.cobertura.xml" reports: "**/coverage.cobertura.xml"
targetdir: "merged/" targetdir: "merged/"

4
.vscode/launch.json vendored
View File

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

View File

@ -23,32 +23,31 @@
<PackageVersion Include="libse" Version="3.6.13" /> <PackageVersion Include="libse" Version="3.6.13" />
<PackageVersion Include="LrcParser" Version="2023.524.0" /> <PackageVersion Include="LrcParser" Version="2023.524.0" />
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="5.0.1" /> <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.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.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="7.0.13" /> <PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.13" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.13" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.13" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.13" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" /> <PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" /> <PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" /> <PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.4" /> <PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0" /> <PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" /> <PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" /> <PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" /> <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="7.0.13" /> <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="7.0.13" /> <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="7.0.0" /> <PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="7.0.0" /> <PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" /> <PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="7.0.0" /> <PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="7.0.1" /> <PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.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="MimeTypes" Version="2.4.0" />
<PackageVersion Include="Mono.Nat" Version="3.0.4" /> <PackageVersion Include="Mono.Nat" Version="3.0.4" />
<PackageVersion Include="Moq" Version="4.18.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.AspNetCore" Version="8.1.0" />
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" /> <PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
<PackageVersion Include="prometheus-net" Version="8.1.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.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.Async" Version="1.5.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="5.0.0" /> <PackageVersion Include="Serilog.Sinks.Console" Version="5.0.0" />
<PackageVersion Include="Serilog.Sinks.File" 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="Swashbuckle.AspNetCore" Version="6.2.3" />
<PackageVersion Include="System.Globalization" Version="4.3.0" /> <PackageVersion Include="System.Globalization" Version="4.3.0" />
<PackageVersion Include="System.Linq.Async" Version="6.0.1" /> <PackageVersion Include="System.Linq.Async" Version="6.0.1" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="7.0.0" /> <PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" />
<PackageVersion Include="System.Text.Json" Version="7.0.3" /> <PackageVersion Include="System.Text.Json" Version="8.0.0" />
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="7.0.0" /> <PackageVersion Include="System.Threading.Tasks.Dataflow" Version="8.0.0" />
<PackageVersion Include="TagLibSharp" Version="2.3.0" /> <PackageVersion Include="TagLibSharp" Version="2.3.0" />
<PackageVersion Include="TMDbLib" Version="2.0.0" /> <PackageVersion Include="TMDbLib" Version="2.0.0" />
<PackageVersion Include="UTF.Unknown" Version="2.5.1" /> <PackageVersion Include="UTF.Unknown" Version="2.5.1" />
@ -88,4 +87,4 @@
<PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" /> <PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" />
<PackageVersion Include="xunit" Version="2.6.1" /> <PackageVersion Include="xunit" Version="2.6.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -2,7 +2,7 @@
##################################### #####################################
# Requires binfm_misc registration # Requires binfm_misc registration
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register # 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 FROM node:20-alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master ARG JELLYFIN_WEB_VERSION=master

View File

@ -2,7 +2,7 @@
##################################### #####################################
# Requires binfm_misc registration # Requires binfm_misc registration
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register # 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 FROM node:20-alpine as web-builder

View File

@ -2,7 +2,7 @@
##################################### #####################################
# Requires binfm_misc registration # Requires binfm_misc registration
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register # 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 FROM node:20-alpine as web-builder

View File

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

View File

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

View File

@ -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
Emby.Dlna/Main/DlnaHost.cs Normal file
View File

@ -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();
}
}

View File

@ -6,7 +6,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<PublishRepositoryUrl>true</PublishRepositoryUrl> <PublishRepositoryUrl>true</PublishRepositoryUrl>
@ -41,10 +41,6 @@
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
</ItemGroup>
<!-- Code Analyzers --> <!-- Code Analyzers -->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="IDisposableAnalyzers"> <PackageReference Include="IDisposableAnalyzers">

View File

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

View File

@ -41,7 +41,6 @@ using Emby.Server.Implementations.Updates;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Drawing; using Jellyfin.Drawing;
using Jellyfin.MediaEncoding.Hls.Playlist; using Jellyfin.MediaEncoding.Hls.Playlist;
using Jellyfin.Networking.Configuration;
using Jellyfin.Networking.Manager; using Jellyfin.Networking.Manager;
using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations;
using MediaBrowser.Common; using MediaBrowser.Common;
@ -100,6 +99,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Prometheus.DotNetRuntime; using Prometheus.DotNetRuntime;
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager;
using WebSocketManager = Emby.Server.Implementations.HttpServer.WebSocketManager; using WebSocketManager = Emby.Server.Implementations.HttpServer.WebSocketManager;
namespace Emby.Server.Implementations namespace Emby.Server.Implementations
@ -310,7 +310,9 @@ namespace Emby.Server.Implementations
{ {
_creatingInstances.Add(type); _creatingInstances.Add(type);
Logger.LogDebug("Creating instance of {Type}", 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) catch (Exception ex)
{ {
@ -866,7 +868,7 @@ namespace Emby.Server.Implementations
yield return typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder).Assembly; yield return typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder).Assembly;
// Dlna // Dlna
yield return typeof(DlnaEntryPoint).Assembly; yield return typeof(DlnaHost).Assembly;
// Local metadata // Local metadata
yield return typeof(BoxSetXmlSaver).Assembly; yield return typeof(BoxSetXmlSaver).Assembly;

View File

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

View File

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

View File

@ -1,7 +1,3 @@
#nullable disable
#pragma warning disable CS1591
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@ -23,476 +19,382 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Session; using MediaBrowser.Model.Session;
using Microsoft.Extensions.Logging; 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; _libraryManager = libraryManager;
private readonly IServerConfigurationManager _configurationManager; _configurationManager = configurationManager;
private readonly IProviderManager _providerManager; _sessionManager = sessionManager;
private readonly ISessionManager _sessionManager; _userManager = userManager;
private readonly IUserManager _userManager; _logger = logger;
private readonly ILogger<LibraryChangedNotifier> _logger; _providerManager = providerManager;
}
/// <summary> /// <inheritdoc />
/// The library changed sync lock. public Task RunAsync()
/// </summary> {
private readonly object _libraryChangedSyncLock = new object(); _libraryManager.ItemAdded += OnLibraryItemAdded;
_libraryManager.ItemUpdated += OnLibraryItemUpdated;
_libraryManager.ItemRemoved += OnLibraryItemRemoved;
private readonly List<Folder> _foldersAddedTo = new List<Folder>(); _providerManager.RefreshCompleted += OnProviderRefreshCompleted;
private readonly List<Folder> _foldersRemovedFrom = new List<Folder>(); _providerManager.RefreshStarted += OnProviderRefreshStarted;
private readonly List<BaseItem> _itemsAdded = new List<BaseItem>(); _providerManager.RefreshProgress += OnProviderRefreshProgress;
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( return Task.CompletedTask;
ILibraryManager libraryManager, }
IServerConfigurationManager configurationManager,
ISessionManager sessionManager, private void OnProviderRefreshProgress(object? sender, GenericEventArgs<Tuple<BaseItem, double>> e)
IUserManager userManager, {
ILogger<LibraryChangedNotifier> logger, var item = e.Argument.Item1;
IProviderManager providerManager)
if (!EnableRefreshMessage(item))
{ {
_libraryManager = libraryManager; return;
_configurationManager = configurationManager;
_sessionManager = sessionManager;
_userManager = userManager;
_logger = logger;
_providerManager = providerManager;
} }
/// <summary> var progress = e.Argument.Item2;
/// Gets or sets the library update timer.
/// </summary>
/// <value>The library update timer.</value>
private Timer LibraryUpdateTimer { get; set; }
public Task RunAsync() if (_lastProgressMessageTimes.TryGetValue(item.Id, out var lastMessageSendTime))
{ {
_libraryManager.ItemAdded += OnLibraryItemAdded; if (progress > 0 && progress < 100 && (DateTime.UtcNow - lastMessageSendTime).TotalMilliseconds < 1000)
_libraryManager.ItemUpdated += OnLibraryItemUpdated;
_libraryManager.ItemRemoved += OnLibraryItemRemoved;
_providerManager.RefreshCompleted += OnProviderRefreshCompleted;
_providerManager.RefreshStarted += OnProviderRefreshStarted;
_providerManager.RefreshProgress += OnProviderRefreshProgress;
return Task.CompletedTask;
}
private void OnProviderRefreshProgress(object sender, GenericEventArgs<Tuple<BaseItem, double>> e)
{
var item = e.Argument.Item1;
if (!EnableRefreshMessage(item))
{ {
return; return;
} }
}
var progress = e.Argument.Item2; _lastProgressMessageTimes.AddOrUpdate(item.Id, _ => DateTime.UtcNow, (_, _) => DateTime.UtcNow);
if (_lastProgressMessageTimes.TryGetValue(item.Id, out var lastMessageSendTime)) var dict = new Dictionary<string, string>();
dict["ItemId"] = item.Id.ToString("N", CultureInfo.InvariantCulture);
dict["Progress"] = progress.ToString(CultureInfo.InvariantCulture);
try
{
_sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, dict, CancellationToken.None);
}
catch
{
}
var collectionFolders = _libraryManager.GetCollectionFolders(item);
foreach (var collectionFolder in collectionFolders)
{
var collectionFolderDict = new Dictionary<string, string>
{ {
if (progress > 0 && progress < 100 && (DateTime.UtcNow - lastMessageSendTime).TotalMilliseconds < 1000) ["ItemId"] = collectionFolder.Id.ToString("N", CultureInfo.InvariantCulture),
{ ["Progress"] = (collectionFolder.GetRefreshProgress() ?? 0).ToString(CultureInfo.InvariantCulture)
return; };
}
}
_lastProgressMessageTimes.AddOrUpdate(item.Id, _ => DateTime.UtcNow, (_, _) => DateTime.UtcNow);
var dict = new Dictionary<string, string>();
dict["ItemId"] = item.Id.ToString("N", CultureInfo.InvariantCulture);
dict["Progress"] = progress.ToString(CultureInfo.InvariantCulture);
try try
{ {
_sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, dict, CancellationToken.None); _sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, collectionFolderDict, CancellationToken.None);
} }
catch 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) private void OnProviderRefreshCompleted(object? sender, GenericEventArgs<BaseItem> e)
{ {
var collectionFolderDict = new Dictionary<string, string> OnProviderRefreshProgress(sender, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(e.Argument, 100)));
{
["ItemId"] = collectionFolder.Id.ToString("N", CultureInfo.InvariantCulture),
["Progress"] = (collectionFolder.GetRefreshProgress() ?? 0).ToString(CultureInfo.InvariantCulture)
};
try _lastProgressMessageTimes.TryRemove(e.Argument.Id, out _);
{ }
_sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, collectionFolderDict, CancellationToken.None);
} private static bool EnableRefreshMessage(BaseItem item)
catch => item is Folder { IsRoot: false, IsTopParent: true }
{ and not (AggregateFolder or UserRootFolder or UserView or Channel);
}
} private void OnLibraryItemAdded(object? sender, ItemChangeEventArgs e)
=> OnLibraryChange(e.Item, e.Parent, _itemsAdded, _foldersAddedTo);
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))
{
return;
} }
private void OnProviderRefreshStarted(object sender, GenericEventArgs<BaseItem> e) lock (_libraryChangedSyncLock)
{ {
OnProviderRefreshProgress(sender, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(e.Argument, 0))); var updateDuration = TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration);
if (_libraryUpdateTimer is null)
{
_libraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, updateDuration, Timeout.InfiniteTimeSpan);
}
else
{
_libraryUpdateTimer.Change(updateDuration, Timeout.InfiniteTimeSpan);
}
if (foldersList is not null && parent is Folder folder)
{
foldersList.Add(folder);
}
itemsList.Add(item);
}
}
private async void LibraryUpdateTimerCallback(object? state)
{
List<Folder> foldersAddedTo;
List<Folder> foldersRemovedFrom;
List<BaseItem> itemsUpdated;
List<BaseItem> itemsAdded;
List<BaseItem> itemsRemoved;
lock (_libraryChangedSyncLock)
{
// 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();
} }
private void OnProviderRefreshCompleted(object sender, GenericEventArgs<BaseItem> 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)
{ {
OnProviderRefreshProgress(sender, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(e.Argument, 100))); LibraryUpdateInfo info;
_lastProgressMessageTimes.TryRemove(e.Argument.Id, out _); try
}
private static bool EnableRefreshMessage(BaseItem item)
{
if (item is not Folder folder)
{ {
return false; info = GetLibraryUpdateInfo(itemsAdded, itemsUpdated, itemsRemoved, foldersAddedTo, foldersRemovedFrom, userId);
} }
catch (Exception ex)
if (folder.IsRoot)
{
return false;
}
if (folder is AggregateFolder || folder is UserRootFolder)
{
return false;
}
if (folder is UserView || folder is Channel)
{
return false;
}
if (!folder.IsTopParent)
{
return false;
}
return true;
}
/// <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)
{
if (!FilterItem(e.Item))
{ {
_logger.LogError(ex, "Error in GetLibraryUpdateInfo");
return; return;
} }
lock (_libraryChangedSyncLock) if (info.IsEmpty)
{ {
if (LibraryUpdateTimer is null) continue;
{ }
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) try
{ {
_foldersAddedTo.Add(parent); await _sessionManager.SendMessageToUserSessions(
} new List<Guid> { userId },
SessionMessageType.LibraryChanged,
_itemsAdded.Add(e.Item); info,
cancellationToken)
.ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error sending LibraryChanged message");
} }
} }
}
/// <summary> private LibraryUpdateInfo GetLibraryUpdateInfo(
/// Handles the ItemUpdated event of the libraryManager control. List<BaseItem> itemsAdded,
/// </summary> List<BaseItem> itemsUpdated,
/// <param name="sender">The source of the event.</param> List<BaseItem> itemsRemoved,
/// <param name="e">The <see cref="ItemChangeEventArgs"/> instance containing the event data.</param> List<Folder> foldersAddedTo,
private void OnLibraryItemUpdated(object sender, ItemChangeEventArgs e) List<Folder> foldersRemovedFrom,
Guid userId)
{
var user = _userManager.GetUserById(userId);
ArgumentNullException.ThrowIfNull(user);
var newAndRemoved = new List<BaseItem>();
newAndRemoved.AddRange(foldersAddedTo);
newAndRemoved.AddRange(foldersRemovedFrom);
var allUserRootChildren = _libraryManager.GetUserRootFolder()
.GetChildren(user, true)
.OfType<Folder>()
.ToList();
return new LibraryUpdateInfo
{ {
if (!FilterItem(e.Item)) ItemsAdded = itemsAdded.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user))
{ .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture))
return;
}
lock (_libraryChangedSyncLock)
{
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);
}
}
/// <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))
{
return;
}
lock (_libraryChangedSyncLock)
{
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);
}
}
/// <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)
{
// 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 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() .Distinct()
.ToArray(); .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()
};
}
foreach (var userId in userIds) private static bool FilterItem(BaseItem item)
{ {
LibraryUpdateInfo info; if (!item.IsFolder && !item.HasPathProtocol)
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");
}
}
}
/// <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); return false;
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(),
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()
};
} }
private static bool FilterItem(BaseItem item) if (item is IItemByName && item is not MusicArtist)
{ {
if (!item.IsFolder && !item.HasPathProtocol) return false;
{
return false;
}
if (item is IItemByName && item is not MusicArtist)
{
return false;
}
return item.SourceType == SourceType.Library;
} }
private IEnumerable<string> GetTopParentIds(List<BaseItem> items, List<Folder> allUserRootChildren) return item.SourceType == SourceType.Library;
{ }
var list = new List<string>();
foreach (var item in items) private IEnumerable<string> GetTopParentIds(List<BaseItem> items, List<Folder> allUserRootChildren)
{ {
// If the physical root changed, return the user root var list = new List<string>();
if (item is AggregateFolder)
{
continue;
}
foreach (var folder in allUserRootChildren) foreach (var item in items)
{
list.Add(folder.Id.ToString("N", CultureInfo.InvariantCulture));
}
}
return list.Distinct(StringComparer.Ordinal);
}
/// <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
{ {
// If the physical root changed, return the user root // If the physical root changed, return the user root
if (item is AggregateFolder) if (item is AggregateFolder)
{ {
return new[] { _libraryManager.GetUserRootFolder() as T }; continue;
} }
// Return it only if it's in the user's library foreach (var folder in allUserRootChildren)
if (includeIfNotFound || item.IsVisibleStandalone(user))
{ {
return new[] { item }; list.Add(folder.Id.ToString("N", CultureInfo.InvariantCulture));
} }
return Array.Empty<T>();
} }
/// <summary> return list.Distinct(StringComparer.Ordinal);
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. }
/// </summary>
public void Dispose() 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)
{ {
Dispose(true); return _libraryManager.GetUserRootFolder() is T t ? new[] { t } : Array.Empty<T>();
GC.SuppressFinalize(this);
} }
/// <summary> // Return it only if it's in the user's library
/// Releases unmanaged and - optionally - managed resources. if (includeIfNotFound || item.IsVisibleStandalone(user))
/// </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)
{ {
if (dispose) return new[] { item };
{ }
if (LibraryUpdateTimer is not null)
{
LibraryUpdateTimer.Dispose();
LibraryUpdateTimer = null;
}
_libraryManager.ItemAdded -= OnLibraryItemAdded; return Array.Empty<T>();
_libraryManager.ItemUpdated -= OnLibraryItemUpdated; }
_libraryManager.ItemRemoved -= OnLibraryItemRemoved;
_providerManager.RefreshCompleted -= OnProviderRefreshCompleted; /// <inheritdoc />
_providerManager.RefreshStarted -= OnProviderRefreshStarted; public void Dispose()
_providerManager.RefreshProgress -= OnProviderRefreshProgress; {
} _libraryManager.ItemAdded -= OnLibraryItemAdded;
_libraryManager.ItemUpdated -= OnLibraryItemUpdated;
_libraryManager.ItemRemoved -= OnLibraryItemRemoved;
_providerManager.RefreshCompleted -= OnProviderRefreshCompleted;
_providerManager.RefreshStarted -= OnProviderRefreshStarted;
_providerManager.RefreshProgress -= OnProviderRefreshProgress;
if (_libraryUpdateTimer is not null)
{
_libraryUpdateTimer.Dispose();
_libraryUpdateTimer = null;
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -124,5 +124,7 @@
"TaskKeyframeExtractor": "Pagrindinių kadrų ištraukėjas", "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ą.", "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", "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."
} }

View File

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

View File

@ -124,5 +124,7 @@
"TaskKeyframeExtractor": "Extrator de quadro-chave", "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.", "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", "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."
} }

View File

@ -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.", "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", "External": "Extern",
"TaskKeyframeExtractor": "Extractor de cadre cheie", "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."
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@ using System.IO;
using System.Linq; using System.Linq;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Api.Models.EnvironmentDtos; using Jellyfin.Api.Models.EnvironmentDtos;
using MediaBrowser.Common.Api;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -168,7 +169,7 @@ public class EnvironmentController : BaseJellyfinApiController
// Check if unc share // Check if unc share
var index = path.LastIndexOf(UncSeparator); 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); parent = path.Substring(0, index);

View File

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

View File

@ -13,6 +13,7 @@ using System.Threading.Tasks;
using Jellyfin.Api.Attributes; using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using MediaBrowser.Common.Api;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
@ -79,7 +80,7 @@ public class ImageController : BaseJellyfinApiController
_appPaths = appPaths; _appPaths = appPaths;
} }
private static Stream GetFromBase64Stream(Stream inputStream) private static CryptoStream GetFromBase64Stream(Stream inputStream)
=> new CryptoStream(inputStream, new FromBase64Transform(), CryptoStreamMode.Read); => new CryptoStream(inputStream, new FromBase64Transform(), CryptoStreamMode.Read);
/// <summary> /// <summary>
@ -2079,30 +2080,30 @@ public class ImageController : BaseJellyfinApiController
foreach (var (key, value) in headers) foreach (var (key, value) in headers)
{ {
Response.Headers.Add(key, value); Response.Headers.Append(key, value);
} }
Response.ContentType = imageContentType ?? MediaTypeNames.Text.Plain; Response.ContentType = imageContentType ?? MediaTypeNames.Text.Plain;
Response.Headers.Add(HeaderNames.Age, Convert.ToInt64((DateTime.UtcNow - dateImageModified).TotalSeconds).ToString(CultureInfo.InvariantCulture)); Response.Headers.Append(HeaderNames.Age, Convert.ToInt64((DateTime.UtcNow - dateImageModified).TotalSeconds).ToString(CultureInfo.InvariantCulture));
Response.Headers.Add(HeaderNames.Vary, HeaderNames.Accept); Response.Headers.Append(HeaderNames.Vary, HeaderNames.Accept);
if (disableCaching) if (disableCaching)
{ {
Response.Headers.Add(HeaderNames.CacheControl, "no-cache, no-store, must-revalidate"); Response.Headers.Append(HeaderNames.CacheControl, "no-cache, no-store, must-revalidate");
Response.Headers.Add(HeaderNames.Pragma, "no-cache, no-store, must-revalidate"); Response.Headers.Append(HeaderNames.Pragma, "no-cache, no-store, must-revalidate");
} }
else else
{ {
if (cacheDuration.HasValue) 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 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 the image was not modified since "ifModifiedSinceHeader"-header, return a HTTP status code 304 not modified
if (!(dateImageModified > ifModifiedSinceHeader) && cacheDuration.HasValue) if (!(dateImageModified > ifModifiedSinceHeader) && cacheDuration.HasValue)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -150,7 +150,7 @@ public class MusicGenresController : BaseJellyfinApiController
MusicGenre? item; 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); item = GetItemFromSlugName<MusicGenre>(_libraryManager, genreName, dtoOptions, BaseItemKind.MusicGenre);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -279,15 +279,15 @@ public static class StreamingHelpers
var profile = state.DeviceProfile; var profile = state.DeviceProfile;
StringValues transferMode = request.Headers["transferMode.dlna.org"]; StringValues transferMode = request.Headers["transferMode.dlna.org"];
responseHeaders.Add("transferMode.dlna.org", string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode.ToString()); responseHeaders.Append("transferMode.dlna.org", string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode.ToString());
responseHeaders.Add("realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*"); responseHeaders.Append("realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*");
if (state.RunTimeTicks.HasValue) if (state.RunTimeTicks.HasValue)
{ {
if (string.Equals(request.Headers["getMediaInfo.sec"], "1", StringComparison.OrdinalIgnoreCase)) if (string.Equals(request.Headers["getMediaInfo.sec"], "1", StringComparison.OrdinalIgnoreCase))
{ {
var ms = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds; var ms = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds;
responseHeaders.Add("MediaInfo.sec", string.Format( responseHeaders.Append("MediaInfo.sec", string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"SEC_Duration={0};", "SEC_Duration={0};",
Convert.ToInt32(ms))); Convert.ToInt32(ms)));
@ -305,7 +305,7 @@ public static class StreamingHelpers
if (!state.IsVideoRequest) if (!state.IsVideoRequest)
{ {
responseHeaders.Add("contentFeatures.dlna.org", ContentFeatureBuilder.BuildAudioHeader( responseHeaders.Append("contentFeatures.dlna.org", ContentFeatureBuilder.BuildAudioHeader(
profile, profile,
state.OutputContainer, state.OutputContainer,
audioCodec, audioCodec,
@ -321,7 +321,7 @@ public static class StreamingHelpers
{ {
var videoCodec = state.ActualOutputVideoCodec; var videoCodec = state.ActualOutputVideoCodec;
responseHeaders.Add( responseHeaders.Append(
"contentFeatures.dlna.org", "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); 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 runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks!.Value).TotalSeconds.ToString(CultureInfo.InvariantCulture);
var startSeconds = TimeSpan.FromTicks(startTimeTicks ?? 0).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, CultureInfo.InvariantCulture,
"npt={0}-{1}/{1}", "npt={0}-{1}/{1}",
startSeconds, startSeconds,
runtimeSeconds)); runtimeSeconds));
responseHeaders.Add("X-AvailableSeekRange", string.Format( responseHeaders.Append("X-AvailableSeekRange", string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"1 npt={0}-{1}", "1 npt={0}-{1}",
startSeconds, startSeconds,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<PublishRepositoryUrl>true</PublishRepositoryUrl> <PublishRepositoryUrl>true</PublishRepositoryUrl>
@ -23,10 +23,6 @@
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
</ItemGroup>
<!-- Code Analyzers --> <!-- Code Analyzers -->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="IDisposableAnalyzers"> <PackageReference Include="IDisposableAnalyzers">

View File

@ -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; }
}
}

View File

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

View File

@ -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()
};
}
}
}

View File

@ -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;
}
}
}

View File

@ -65,7 +65,7 @@ namespace Jellyfin.Networking.HappyEyeballs
// See https://github.com/dotnet/corefx/pull/29792/files#r189415885 for more details. // 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) 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(); return tryConnectAsyncIPv6.GetAwaiter().GetResult();
} }
@ -76,7 +76,7 @@ namespace Jellyfin.Networking.HappyEyeballs
{ {
if (tryConnectAsyncIPv6.IsCompletedSuccessfully) if (tryConnectAsyncIPv6.IsCompletedSuccessfully)
{ {
cancelIPv4.Cancel(); await cancelIPv4.CancelAsync().ConfigureAwait(false);
return tryConnectAsyncIPv6.GetAwaiter().GetResult(); return tryConnectAsyncIPv6.GetAwaiter().GetResult();
} }
@ -86,7 +86,7 @@ namespace Jellyfin.Networking.HappyEyeballs
{ {
if (tryConnectAsyncIPv4.IsCompletedSuccessfully) if (tryConnectAsyncIPv4.IsCompletedSuccessfully)
{ {
cancelIPv6.Cancel(); await cancelIPv6.CancelAsync().ConfigureAwait(false);
return tryConnectAsyncIPv4.GetAwaiter().GetResult(); return tryConnectAsyncIPv4.GetAwaiter().GetResult();
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,11 +20,10 @@ using Jellyfin.Api.Formatters;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions.Json; using Jellyfin.Extensions.Json;
using Jellyfin.Networking.Configuration;
using Jellyfin.Networking.Constants;
using Jellyfin.Networking.Extensions;
using Jellyfin.Server.Configuration; using Jellyfin.Server.Configuration;
using Jellyfin.Server.Filters; using Jellyfin.Server.Filters;
using MediaBrowser.Common.Api;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Session; using MediaBrowser.Model.Session;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
@ -38,6 +37,7 @@ using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen; using Swashbuckle.AspNetCore.SwaggerGen;
using AuthenticationSchemes = Jellyfin.Api.Constants.AuthenticationSchemes; using AuthenticationSchemes = Jellyfin.Api.Constants.AuthenticationSchemes;
using IPNetwork = System.Net.IPNetwork;
namespace Jellyfin.Server.Extensions namespace Jellyfin.Server.Extensions
{ {
@ -275,20 +275,20 @@ namespace Jellyfin.Server.Extensions
{ {
if (IPAddress.TryParse(allowedProxies[i], out var addr)) 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) if (subnet is not null)
{ {
AddIPAddress(config, options, subnet.Prefix, subnet.PrefixLength); 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) 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; return;
} }
if (prefixLength == Network.MinimumIPv4PrefixSize) if (prefixLength == NetworkConstants.MinimumIPv4PrefixSize)
{ {
options.KnownProxies.Add(addr); options.KnownProxies.Add(addr);
} }
else else
{ {
options.KnownNetworks.Add(new IPNetwork(addr, prefixLength)); options.KnownNetworks.Add(new Microsoft.AspNetCore.HttpOverrides.IPNetwork(addr, prefixLength));
} }
} }

View File

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

View File

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

View File

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

View File

@ -40,7 +40,7 @@ namespace Jellyfin.Server
/// </summary> /// </summary>
public const string LoggingConfigFileSystem = "logging.json"; 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 long _startTimestamp;
private static ILogger _logger = NullLogger.Instance; private static ILogger _logger = NullLogger.Instance;
private static bool _restartOnShutdown; private static bool _restartOnShutdown;

View File

@ -7,7 +7,6 @@ using System.Text;
using Emby.Dlna.Extensions; using Emby.Dlna.Extensions;
using Jellyfin.Api.Middleware; using Jellyfin.Api.Middleware;
using Jellyfin.MediaEncoding.Hls.Extensions; using Jellyfin.MediaEncoding.Hls.Extensions;
using Jellyfin.Networking.Configuration;
using Jellyfin.Networking.HappyEyeballs; using Jellyfin.Networking.HappyEyeballs;
using Jellyfin.Server.Extensions; using Jellyfin.Server.Extensions;
using Jellyfin.Server.HealthChecks; using Jellyfin.Server.HealthChecks;
@ -36,7 +35,7 @@ namespace Jellyfin.Server
/// </summary> /// </summary>
public class Startup public class Startup
{ {
private readonly IServerApplicationHost _serverApplicationHost; private readonly CoreAppHost _serverApplicationHost;
private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IServerConfigurationManager _serverConfigurationManager;
/// <summary> /// <summary>

View File

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

View File

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

View File

@ -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; }
}

View File

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

View File

@ -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()
};
}
}

View File

@ -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;
}
}

View File

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

View File

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

View File

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

View File

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

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