Merge branch 'jellyfin:master' into master
This commit is contained in:
commit
9d5dc4d71b
|
@ -7,7 +7,7 @@ parameters:
|
|||
default: "ubuntu-latest"
|
||||
- name: DotNetSdkVersion
|
||||
type: string
|
||||
default: 7.0.x
|
||||
default: 8.0.x
|
||||
|
||||
jobs:
|
||||
- job: CompatibilityCheck
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
parameters:
|
||||
LinuxImage: 'ubuntu-latest'
|
||||
RestoreBuildProjects: 'Jellyfin.Server/Jellyfin.Server.csproj'
|
||||
DotNetSdkVersion: 7.0.x
|
||||
DotNetSdkVersion: 8.0.x
|
||||
|
||||
jobs:
|
||||
- job: Build
|
||||
|
|
|
@ -208,10 +208,10 @@ jobs:
|
|||
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Use .NET 7.0 sdk'
|
||||
displayName: 'Use .NET 8.0 sdk'
|
||||
inputs:
|
||||
packageType: 'sdk'
|
||||
version: '7.0.x'
|
||||
version: '8.0.x'
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Build Stable Nuget packages'
|
||||
|
|
|
@ -10,7 +10,7 @@ parameters:
|
|||
default: "tests/**/*Tests.csproj"
|
||||
- name: DotNetSdkVersion
|
||||
type: string
|
||||
default: 7.0.x
|
||||
default: 8.0.x
|
||||
|
||||
jobs:
|
||||
- job: Test
|
||||
|
@ -94,5 +94,5 @@ jobs:
|
|||
displayName: 'Publish OpenAPI Artifact'
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
inputs:
|
||||
targetPath: "tests/Jellyfin.Server.Integration.Tests/bin/Release/net7.0/openapi.json"
|
||||
targetPath: "tests/Jellyfin.Server.Integration.Tests/bin/Release/net8.0/openapi.json"
|
||||
artifactName: 'OpenAPI Spec'
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-ef": {
|
||||
"version": "7.0.13",
|
||||
"version": "8.0.0",
|
||||
"commands": [
|
||||
"dotnet-ef"
|
||||
]
|
||||
|
|
8
.github/workflows/ci-codeql-analysis.yml
vendored
8
.github/workflows/ci-codeql-analysis.yml
vendored
|
@ -24,14 +24,14 @@ jobs:
|
|||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
|
||||
with:
|
||||
dotnet-version: '7.0.x'
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
|
||||
uses: github/codeql-action/init@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
queries: +security-extended
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
|
||||
uses: github/codeql-action/autobuild@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
|
||||
uses: github/codeql-action/analyze@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8
|
||||
|
|
8
.github/workflows/ci-openapi.yml
vendored
8
.github/workflows/ci-openapi.yml
vendored
|
@ -21,7 +21,7 @@ jobs:
|
|||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
|
||||
with:
|
||||
dotnet-version: '7.0.x'
|
||||
dotnet-version: '8.0.x'
|
||||
- name: Generate openapi.json
|
||||
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
||||
- name: Upload openapi.json
|
||||
|
@ -30,7 +30,7 @@ jobs:
|
|||
name: openapi-head
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net7.0/openapi.json
|
||||
path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net8.0/openapi.json
|
||||
|
||||
openapi-base:
|
||||
name: OpenAPI - BASE
|
||||
|
@ -55,7 +55,7 @@ jobs:
|
|||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
|
||||
with:
|
||||
dotnet-version: '7.0.x'
|
||||
dotnet-version: '8.0.x'
|
||||
- name: Generate openapi.json
|
||||
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
||||
- name: Upload openapi.json
|
||||
|
@ -64,7 +64,7 @@ jobs:
|
|||
name: openapi-base
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net7.0/openapi.json
|
||||
path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net8.0/openapi.json
|
||||
|
||||
openapi-diff:
|
||||
permissions:
|
||||
|
|
4
.github/workflows/ci-tests.yml
vendored
4
.github/workflows/ci-tests.yml
vendored
|
@ -9,7 +9,7 @@ on:
|
|||
pull_request:
|
||||
|
||||
env:
|
||||
SDK_VERSION: "7.0.x"
|
||||
SDK_VERSION: "8.0.x"
|
||||
|
||||
jobs:
|
||||
run-tests:
|
||||
|
@ -34,7 +34,7 @@ jobs:
|
|||
--verbosity minimal
|
||||
|
||||
- name: Merge code coverage results
|
||||
uses: danielpalme/ReportGenerator-GitHub-Action@873ee34c88a6234bdab7fd264d3666fd1ab417f7 # 5
|
||||
uses: danielpalme/ReportGenerator-GitHub-Action@4d510cbed8a05af5aefea46c7fd6e05b95844c89 # 5
|
||||
with:
|
||||
reports: "**/coverage.cobertura.xml"
|
||||
targetdir: "merged/"
|
||||
|
|
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
|
@ -6,7 +6,7 @@
|
|||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
"program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net7.0/jellyfin.dll",
|
||||
"program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net8.0/jellyfin.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/Jellyfin.Server",
|
||||
"console": "internalConsole",
|
||||
|
@ -22,7 +22,7 @@
|
|||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
"program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net7.0/jellyfin.dll",
|
||||
"program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net8.0/jellyfin.dll",
|
||||
"args": ["--nowebclient"],
|
||||
"cwd": "${workspaceFolder}/Jellyfin.Server",
|
||||
"console": "internalConsole",
|
||||
|
|
|
@ -23,32 +23,31 @@
|
|||
<PackageVersion Include="libse" Version="3.6.13" />
|
||||
<PackageVersion Include="LrcParser" Version="2023.524.0" />
|
||||
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="5.0.1" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="7.0.13" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.13" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="7.0.13" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.13" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.13" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.13" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.13" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="7.0.13" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="7.0.13" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="7.0.1" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
|
||||
<PackageVersion Include="MimeTypes" Version="2.4.0" />
|
||||
<PackageVersion Include="Mono.Nat" Version="3.0.4" />
|
||||
<PackageVersion Include="Moq" Version="4.18.4" />
|
||||
|
@ -58,9 +57,9 @@
|
|||
<PackageVersion Include="prometheus-net.AspNetCore" Version="8.1.0" />
|
||||
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
|
||||
<PackageVersion Include="prometheus-net" Version="8.1.0" />
|
||||
<PackageVersion Include="Serilog.AspNetCore" Version="7.0.0" />
|
||||
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.0" />
|
||||
<PackageVersion Include="Serilog.Enrichers.Thread" Version="3.1.0" />
|
||||
<PackageVersion Include="Serilog.Settings.Configuration" Version="7.0.1" />
|
||||
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Console" Version="5.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
|
@ -77,9 +76,9 @@
|
|||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||
<PackageVersion Include="System.Globalization" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="7.0.0" />
|
||||
<PackageVersion Include="System.Text.Json" Version="7.0.3" />
|
||||
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="7.0.0" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" />
|
||||
<PackageVersion Include="System.Text.Json" Version="8.0.0" />
|
||||
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="8.0.0" />
|
||||
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
|
||||
<PackageVersion Include="TMDbLib" Version="2.0.0" />
|
||||
<PackageVersion Include="UTF.Unknown" Version="2.5.1" />
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#####################################
|
||||
# Requires binfm_misc registration
|
||||
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
|
||||
ARG DOTNET_VERSION=7.0
|
||||
ARG DOTNET_VERSION=8.0
|
||||
|
||||
FROM node:20-alpine as web-builder
|
||||
ARG JELLYFIN_WEB_VERSION=master
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#####################################
|
||||
# Requires binfm_misc registration
|
||||
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
|
||||
ARG DOTNET_VERSION=7.0
|
||||
ARG DOTNET_VERSION=8.0
|
||||
|
||||
|
||||
FROM node:20-alpine as web-builder
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#####################################
|
||||
# Requires binfm_misc registration
|
||||
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
|
||||
ARG DOTNET_VERSION=7.0
|
||||
ARG DOTNET_VERSION=8.0
|
||||
|
||||
|
||||
FROM node:20-alpine as web-builder
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.Net.Http;
|
|||
using System.Text;
|
||||
using Emby.Dlna.ConnectionManager;
|
||||
using Emby.Dlna.ContentDirectory;
|
||||
using Emby.Dlna.Main;
|
||||
using Emby.Dlna.MediaReceiverRegistrar;
|
||||
using Emby.Dlna.Ssdp;
|
||||
using MediaBrowser.Common.Net;
|
||||
|
@ -65,5 +66,7 @@ public static class DlnaServiceCollectionExtensions
|
|||
{
|
||||
IsShared = true
|
||||
});
|
||||
|
||||
services.AddHostedService<DlnaHost>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
387
Emby.Dlna/Main/DlnaHost.cs
Normal 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();
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
|
@ -41,10 +41,6 @@
|
|||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Code Analyzers -->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="IDisposableAnalyzers">
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
|
|
@ -41,7 +41,6 @@ using Emby.Server.Implementations.Updates;
|
|||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Drawing;
|
||||
using Jellyfin.MediaEncoding.Hls.Playlist;
|
||||
using Jellyfin.Networking.Configuration;
|
||||
using Jellyfin.Networking.Manager;
|
||||
using Jellyfin.Server.Implementations;
|
||||
using MediaBrowser.Common;
|
||||
|
@ -100,6 +99,7 @@ using Microsoft.Extensions.DependencyInjection;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Prometheus.DotNetRuntime;
|
||||
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
|
||||
using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager;
|
||||
using WebSocketManager = Emby.Server.Implementations.HttpServer.WebSocketManager;
|
||||
|
||||
namespace Emby.Server.Implementations
|
||||
|
@ -310,7 +310,9 @@ namespace Emby.Server.Implementations
|
|||
{
|
||||
_creatingInstances.Add(type);
|
||||
Logger.LogDebug("Creating instance of {Type}", type);
|
||||
return ActivatorUtilities.CreateInstance(ServiceProvider, type);
|
||||
return ServiceProvider is null
|
||||
? Activator.CreateInstance(type)
|
||||
: ActivatorUtilities.CreateInstance(ServiceProvider, type);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -866,7 +868,7 @@ namespace Emby.Server.Implementations
|
|||
yield return typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder).Assembly;
|
||||
|
||||
// Dlna
|
||||
yield return typeof(DlnaEntryPoint).Assembly;
|
||||
yield return typeof(DlnaHost).Assembly;
|
||||
|
||||
// Local metadata
|
||||
yield return typeof(BoxSetXmlSaver).Assembly;
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
|
|
@ -9,7 +9,7 @@ using System.Net;
|
|||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Networking.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
@ -23,10 +19,13 @@ using MediaBrowser.Model.Entities;
|
|||
using MediaBrowser.Model.Session;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
namespace Emby.Server.Implementations.EntryPoints;
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="IServerEntryPoint"/> that notifies users when libraries are updated.
|
||||
/// </summary>
|
||||
public sealed class LibraryChangedNotifier : IServerEntryPoint
|
||||
{
|
||||
public class LibraryChangedNotifier : IServerEntryPoint
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IServerConfigurationManager _configurationManager;
|
||||
private readonly IProviderManager _providerManager;
|
||||
|
@ -34,18 +33,25 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
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>
|
||||
/// The library changed sync lock.
|
||||
/// Initializes a new instance of the <see cref="LibraryChangedNotifier"/> class.
|
||||
/// </summary>
|
||||
private readonly object _libraryChangedSyncLock = new object();
|
||||
|
||||
private readonly List<Folder> _foldersAddedTo = new List<Folder>();
|
||||
private readonly List<Folder> _foldersRemovedFrom = new List<Folder>();
|
||||
private readonly List<BaseItem> _itemsAdded = new List<BaseItem>();
|
||||
private readonly List<BaseItem> _itemsRemoved = new List<BaseItem>();
|
||||
private readonly List<BaseItem> _itemsUpdated = new List<BaseItem>();
|
||||
private readonly ConcurrentDictionary<Guid, DateTime> _lastProgressMessageTimes = new ConcurrentDictionary<Guid, DateTime>();
|
||||
|
||||
/// <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,
|
||||
|
@ -62,12 +68,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
_providerManager = providerManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the library update timer.
|
||||
/// </summary>
|
||||
/// <value>The library update timer.</value>
|
||||
private Timer LibraryUpdateTimer { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task RunAsync()
|
||||
{
|
||||
_libraryManager.ItemAdded += OnLibraryItemAdded;
|
||||
|
@ -81,7 +82,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void OnProviderRefreshProgress(object sender, GenericEventArgs<Tuple<BaseItem, double>> e)
|
||||
private void OnProviderRefreshProgress(object? sender, GenericEventArgs<Tuple<BaseItem, double>> e)
|
||||
{
|
||||
var item = e.Argument.Item1;
|
||||
|
||||
|
@ -134,12 +135,12 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
}
|
||||
}
|
||||
|
||||
private void OnProviderRefreshStarted(object sender, GenericEventArgs<BaseItem> e)
|
||||
private void OnProviderRefreshStarted(object? sender, GenericEventArgs<BaseItem> e)
|
||||
{
|
||||
OnProviderRefreshProgress(sender, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(e.Argument, 0)));
|
||||
}
|
||||
|
||||
private void OnProviderRefreshCompleted(object sender, GenericEventArgs<BaseItem> e)
|
||||
private void OnProviderRefreshCompleted(object? sender, GenericEventArgs<BaseItem> e)
|
||||
{
|
||||
OnProviderRefreshProgress(sender, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(e.Argument, 100)));
|
||||
|
||||
|
@ -147,135 +148,48 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
}
|
||||
|
||||
private static bool EnableRefreshMessage(BaseItem item)
|
||||
{
|
||||
if (item is not Folder folder)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
=> item is Folder { IsRoot: false, IsTopParent: true }
|
||||
and not (AggregateFolder or UserRootFolder or UserView or Channel);
|
||||
|
||||
if (folder.IsRoot)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
private void OnLibraryItemAdded(object? sender, ItemChangeEventArgs e)
|
||||
=> OnLibraryChange(e.Item, e.Parent, _itemsAdded, _foldersAddedTo);
|
||||
|
||||
if (folder is AggregateFolder || folder is UserRootFolder)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
private void OnLibraryItemUpdated(object? sender, ItemChangeEventArgs e)
|
||||
=> OnLibraryChange(e.Item, e.Parent, _itemsUpdated, null);
|
||||
|
||||
if (folder is UserView || folder is Channel)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
private void OnLibraryItemRemoved(object? sender, ItemChangeEventArgs e)
|
||||
=> OnLibraryChange(e.Item, e.Parent, _itemsRemoved, _foldersRemovedFrom);
|
||||
|
||||
if (!folder.IsTopParent)
|
||||
private void OnLibraryChange(BaseItem item, BaseItem parent, List<BaseItem> itemsList, List<Folder>? foldersList)
|
||||
{
|
||||
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))
|
||||
if (!FilterItem(item))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_libraryChangedSyncLock)
|
||||
{
|
||||
if (LibraryUpdateTimer is null)
|
||||
var updateDuration = TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration);
|
||||
|
||||
if (_libraryUpdateTimer is null)
|
||||
{
|
||||
LibraryUpdateTimer = new Timer(
|
||||
LibraryUpdateTimerCallback,
|
||||
null,
|
||||
TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration),
|
||||
Timeout.InfiniteTimeSpan);
|
||||
_libraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, updateDuration, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
LibraryUpdateTimer.Change(TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration), Timeout.InfiniteTimeSpan);
|
||||
_libraryUpdateTimer.Change(updateDuration, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
|
||||
if (e.Item.GetParent() is Folder parent)
|
||||
if (foldersList is not null && parent is Folder folder)
|
||||
{
|
||||
_foldersAddedTo.Add(parent);
|
||||
foldersList.Add(folder);
|
||||
}
|
||||
|
||||
_itemsAdded.Add(e.Item);
|
||||
itemsList.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the ItemUpdated event of the libraryManager control.
|
||||
/// </summary>
|
||||
/// <param name="sender">The source of the event.</param>
|
||||
/// <param name="e">The <see cref="ItemChangeEventArgs"/> instance containing the event data.</param>
|
||||
private void OnLibraryItemUpdated(object sender, ItemChangeEventArgs e)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
_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)
|
||||
private async void LibraryUpdateTimerCallback(object? state)
|
||||
{
|
||||
List<Folder> foldersAddedTo;
|
||||
List<Folder> foldersRemovedFrom;
|
||||
|
@ -301,10 +215,10 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
itemsAdded = _itemsAdded.ToList();
|
||||
itemsRemoved = _itemsRemoved.ToList();
|
||||
|
||||
if (LibraryUpdateTimer is not null)
|
||||
if (_libraryUpdateTimer is not null)
|
||||
{
|
||||
LibraryUpdateTimer.Dispose();
|
||||
LibraryUpdateTimer = null;
|
||||
_libraryUpdateTimer.Dispose();
|
||||
_libraryUpdateTimer = null;
|
||||
}
|
||||
|
||||
_itemsAdded.Clear();
|
||||
|
@ -317,16 +231,13 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
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)
|
||||
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)
|
||||
|
@ -355,7 +266,12 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
|
||||
try
|
||||
{
|
||||
await _sessionManager.SendMessageToUserSessions(new List<Guid> { userId }, SessionMessageType.LibraryChanged, info, cancellationToken).ConfigureAwait(false);
|
||||
await _sessionManager.SendMessageToUserSessions(
|
||||
new List<Guid> { userId },
|
||||
SessionMessageType.LibraryChanged,
|
||||
info,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -364,38 +280,48 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
private LibraryUpdateInfo GetLibraryUpdateInfo(
|
||||
List<BaseItem> itemsAdded,
|
||||
List<BaseItem> itemsUpdated,
|
||||
List<BaseItem> itemsRemoved,
|
||||
List<Folder> foldersAddedTo,
|
||||
List<Folder> foldersRemovedFrom,
|
||||
Guid userId)
|
||||
{
|
||||
var user = _userManager.GetUserById(userId);
|
||||
ArgumentNullException.ThrowIfNull(user);
|
||||
|
||||
var newAndRemoved = new List<BaseItem>();
|
||||
newAndRemoved.AddRange(foldersAddedTo);
|
||||
newAndRemoved.AddRange(foldersRemovedFrom);
|
||||
|
||||
var allUserRootChildren = _libraryManager.GetUserRootFolder().GetChildren(user, true).OfType<Folder>().ToList();
|
||||
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(),
|
||||
|
||||
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()
|
||||
};
|
||||
}
|
||||
|
@ -436,21 +362,13 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
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 (item is AggregateFolder)
|
||||
{
|
||||
return new[] { _libraryManager.GetUserRootFolder() as T };
|
||||
return _libraryManager.GetUserRootFolder() is T t ? new[] { t } : Array.Empty<T>();
|
||||
}
|
||||
|
||||
// Return it only if it's in the user's library
|
||||
|
@ -462,29 +380,9 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool dispose)
|
||||
{
|
||||
if (dispose)
|
||||
{
|
||||
if (LibraryUpdateTimer is not null)
|
||||
{
|
||||
LibraryUpdateTimer.Dispose();
|
||||
LibraryUpdateTimer = null;
|
||||
}
|
||||
|
||||
_libraryManager.ItemAdded -= OnLibraryItemAdded;
|
||||
_libraryManager.ItemUpdated -= OnLibraryItemUpdated;
|
||||
_libraryManager.ItemRemoved -= OnLibraryItemRemoved;
|
||||
|
@ -492,7 +390,11 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
_providerManager.RefreshCompleted -= OnProviderRefreshCompleted;
|
||||
_providerManager.RefreshStarted -= OnProviderRefreshStarted;
|
||||
_providerManager.RefreshProgress -= OnProviderRefreshProgress;
|
||||
}
|
||||
|
||||
if (_libraryUpdateTimer is not null)
|
||||
{
|
||||
_libraryUpdateTimer.Dispose();
|
||||
_libraryUpdateTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,14 +6,13 @@ using System.Net.Sockets;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Udp;
|
||||
using Jellyfin.Networking.Configuration;
|
||||
using Jellyfin.Networking.Extensions;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
|
@ -92,7 +91,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
var validInterfaces = _networkManager.GetInternalBindAddresses().Where(i => i.AddressFamily == AddressFamily.InterNetwork);
|
||||
foreach (var intf in validInterfaces)
|
||||
{
|
||||
var broadcastAddress = NetworkExtensions.GetBroadcastAddress(intf.Subnet);
|
||||
var broadcastAddress = NetworkUtils.GetBroadcastAddress(intf.Subnet);
|
||||
_logger.LogDebug("Binding UDP server to {Address} on port {PortNumber}", broadcastAddress, PortNumber);
|
||||
|
||||
server = new UdpServer(_logger, _appHost, _config, broadcastAddress, PortNumber);
|
||||
|
|
|
@ -84,15 +84,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Close()
|
||||
public async Task Close()
|
||||
{
|
||||
EnableStreamSharing = false;
|
||||
|
||||
Logger.LogInformation("Closing {Type}", GetType().Name);
|
||||
|
||||
LiveStreamCancellationTokenSource.Cancel();
|
||||
|
||||
return Task.CompletedTask;
|
||||
await LiveStreamCancellationTokenSource.CancelAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public Stream GetStream()
|
||||
|
|
|
@ -123,5 +123,7 @@
|
|||
"TaskKeyframeExtractorDescription": "Haal keyframes vanuit video lêers om meer presiese HLS afspeellyste te maak. Dit kan lank duur.",
|
||||
"TaskKeyframeExtractor": "Keyframe Ekstraktor",
|
||||
"External": "Ekstern",
|
||||
"HearingImpaired": "gehoorgestremd"
|
||||
"HearingImpaired": "gehoorgestremd",
|
||||
"TaskRefreshTrickplayImages": "Genereer Fopspeel Beelde",
|
||||
"TaskRefreshTrickplayImagesDescription": "Skep fopspeel voorskou vir videos in aangeskakelde media versameling."
|
||||
}
|
||||
|
|
|
@ -124,5 +124,6 @@
|
|||
"TaskKeyframeExtractorDescription": "يستخرج الإطارات الرئيسية من ملفات الفيديو لكي ينشئ قوائم تشغيل بث HTTP المباشر. قد تستمر هذه العملية لوقت طويل.",
|
||||
"TaskKeyframeExtractor": "مستخرج الإطار الرئيسي",
|
||||
"External": "خارجي",
|
||||
"HearingImpaired": "ضعاف السمع"
|
||||
"HearingImpaired": "ضعاف السمع",
|
||||
"TaskRefreshTrickplayImages": "توليد صور Trickplay"
|
||||
}
|
||||
|
|
|
@ -124,5 +124,7 @@
|
|||
"TaskKeyframeExtractor": "Pagrindinių kadrų ištraukėjas",
|
||||
"TaskOptimizeDatabaseDescription": "Suspaudžia duomenų bazę ir atlaisvina vietą. Paleidžiant šią užduotį, po bibliotekos skenavimo arba kitų veiksmų kurie galimai modifikuoja duomenų bazė, gali pagerinti greitaveiką.",
|
||||
"External": "Išorinis",
|
||||
"HearingImpaired": "Su klausos sutrikimais"
|
||||
"HearingImpaired": "Su klausos sutrikimais",
|
||||
"TaskRefreshTrickplayImages": "Generuoti Trickplay atvaizdus",
|
||||
"TaskRefreshTrickplayImagesDescription": "Sukuria trickplay peržiūras vaizdo įrašams įgalintose bibliotekose."
|
||||
}
|
||||
|
|
|
@ -123,5 +123,7 @@
|
|||
"DeviceOnlineWithName": "{0} कनेक्ट झाले",
|
||||
"DeviceOfflineWithName": "{0} डिस्कनेक्ट झाला आहे",
|
||||
"AuthenticationSucceededWithUserName": "{0} यशस्वीरित्या प्रमाणीकृत",
|
||||
"HearingImpaired": "कर्णबधीर"
|
||||
"HearingImpaired": "कर्णबधीर",
|
||||
"TaskRefreshTrickplayImages": "ट्रिकप्ले प्रतिमा तयार करा",
|
||||
"TaskRefreshTrickplayImagesDescription": "सक्षम लायब्ररीमधील व्हिडिओंसाठी ट्रिकप्ले पूर्वावलोकन तयार करते."
|
||||
}
|
||||
|
|
|
@ -124,5 +124,7 @@
|
|||
"TaskKeyframeExtractor": "Extrator de quadro-chave",
|
||||
"TaskKeyframeExtractorDescription": "Extrai quadros-chave de arquivos de vídeo para criar listas de reprodução HLS mais precisas. Esta tarefa pode ser executada por um longo tempo.",
|
||||
"External": "Externo",
|
||||
"HearingImpaired": "Deficiência Auditiva"
|
||||
"HearingImpaired": "Deficiência Auditiva",
|
||||
"TaskRefreshTrickplayImages": "Gerar imagens Trickplay",
|
||||
"TaskRefreshTrickplayImagesDescription": "Cria prévias Trickplay para vídeos em bibliotecas em que o recurso está habilitado."
|
||||
}
|
||||
|
|
|
@ -123,5 +123,7 @@
|
|||
"TaskKeyframeExtractorDescription": "Extrage cadrele cheie din fișierele video pentru a crea liste de redare HLS mai precise. Această sarcină poate rula o perioadă lungă de timp.",
|
||||
"External": "Extern",
|
||||
"TaskKeyframeExtractor": "Extractor de cadre cheie",
|
||||
"HearingImpaired": "Ascultare Impară"
|
||||
"HearingImpaired": "Ascultare Impară",
|
||||
"TaskRefreshTrickplayImages": "Generează imagini Trickplay",
|
||||
"TaskRefreshTrickplayImagesDescription": "Generează previzualizările trickplay pentru videourile din librăriile selectate."
|
||||
}
|
||||
|
|
|
@ -12,10 +12,11 @@ using System.Threading.Tasks;
|
|||
using Emby.Server.Implementations.Library;
|
||||
using Jellyfin.Extensions.Json;
|
||||
using Jellyfin.Extensions.Json.Converters;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Plugins;
|
||||
|
@ -37,7 +38,7 @@ namespace Emby.Server.Implementations.Plugins
|
|||
private readonly List<AssemblyLoadContext> _assemblyLoadContexts;
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
private readonly ILogger<PluginManager> _logger;
|
||||
private readonly IApplicationHost _appHost;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly ServerConfiguration _config;
|
||||
private readonly List<LocalPlugin> _plugins;
|
||||
private readonly Version _minimumVersion;
|
||||
|
@ -48,13 +49,13 @@ namespace Emby.Server.Implementations.Plugins
|
|||
/// Initializes a new instance of the <see cref="PluginManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The <see cref="ILogger{PluginManager}"/>.</param>
|
||||
/// <param name="appHost">The <see cref="IApplicationHost"/>.</param>
|
||||
/// <param name="appHost">The <see cref="IServerApplicationHost"/>.</param>
|
||||
/// <param name="config">The <see cref="ServerConfiguration"/>.</param>
|
||||
/// <param name="pluginsPath">The plugin path.</param>
|
||||
/// <param name="appVersion">The application version.</param>
|
||||
public PluginManager(
|
||||
ILogger<PluginManager> logger,
|
||||
IApplicationHost appHost,
|
||||
IServerApplicationHost appHost,
|
||||
ServerConfiguration config,
|
||||
string pluginsPath,
|
||||
Version appVersion)
|
||||
|
@ -222,7 +223,7 @@ namespace Emby.Server.Implementations.Plugins
|
|||
try
|
||||
{
|
||||
var instance = (IPluginServiceRegistrator?)Activator.CreateInstance(pluginServiceRegistrator);
|
||||
instance?.RegisterServices(serviceCollection);
|
||||
instance?.RegisterServices(serviceCollection, _appHost);
|
||||
}
|
||||
#pragma warning disable CA1031 // Do not catch general exception types
|
||||
catch (Exception ex)
|
||||
|
|
|
@ -551,8 +551,7 @@ namespace Emby.Server.Implementations.Updates
|
|||
}
|
||||
|
||||
stream.Position = 0;
|
||||
using var reader = new ZipArchive(stream);
|
||||
reader.ExtractToDirectory(targetDir, true);
|
||||
ZipFile.ExtractToDirectory(stream, targetDir, true);
|
||||
|
||||
// Ensure we create one or populate existing ones with missing data.
|
||||
await _pluginManager.PopulateManifest(package.PackageInfo, package.Version, targetDir, status).ConfigureAwait(false);
|
||||
|
|
|
@ -27,13 +27,12 @@ namespace Jellyfin.Api.Auth
|
|||
/// <param name="options">Options monitor.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="encoder">The url encoder.</param>
|
||||
/// <param name="clock">The system clock.</param>
|
||||
public CustomAuthenticationHandler(
|
||||
IAuthService authService,
|
||||
IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder,
|
||||
ISystemClock clock) : base(options, logger, encoder, clock)
|
||||
UrlEncoder encoder)
|
||||
: base(options, logger, encoder)
|
||||
{
|
||||
_authService = authService;
|
||||
_logger = logger.CreateLogger<CustomAuthenticationHandler>();
|
||||
|
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Data.Queries;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Model.Activity;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Constants;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Controller.Security;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.Threading.Tasks;
|
|||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Controller.Collections;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Model.Collections;
|
||||
|
|
|
@ -6,6 +6,7 @@ using Jellyfin.Api.Attributes;
|
|||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Models.ConfigurationDtos;
|
||||
using Jellyfin.Extensions.Json;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
|
|
|
@ -6,6 +6,7 @@ using Jellyfin.Api.Helpers;
|
|||
using Jellyfin.Data.Dtos;
|
||||
using Jellyfin.Data.Entities.Security;
|
||||
using Jellyfin.Data.Queries;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Devices;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Jellyfin.Api.Constants;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
|
|
@ -7,6 +7,7 @@ using System.Threading.Tasks;
|
|||
using Emby.Dlna;
|
||||
using Jellyfin.Api.Attributes;
|
||||
using Jellyfin.Api.Constants;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Net;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Models.EnvironmentDtos;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
@ -168,7 +169,7 @@ public class EnvironmentController : BaseJellyfinApiController
|
|||
// Check if unc share
|
||||
var index = path.LastIndexOf(UncSeparator);
|
||||
|
||||
if (index != -1 && path.IndexOf(UncSeparator, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
if (index != -1 && path[0] == UncSeparator)
|
||||
{
|
||||
parent = path.Substring(0, index);
|
||||
|
||||
|
|
|
@ -160,7 +160,7 @@ public class HlsSegmentController : BaseJellyfinApiController
|
|||
var pathExtension = Path.GetExtension(path);
|
||||
if ((string.Equals(pathExtension, segmentContainer, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(pathExtension, ".m3u8", StringComparison.OrdinalIgnoreCase))
|
||||
&& path.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1)
|
||||
&& path.Contains(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
playlistPath = path;
|
||||
break;
|
||||
|
|
|
@ -13,6 +13,7 @@ using System.Threading.Tasks;
|
|||
using Jellyfin.Api.Attributes;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
|
@ -79,7 +80,7 @@ public class ImageController : BaseJellyfinApiController
|
|||
_appPaths = appPaths;
|
||||
}
|
||||
|
||||
private static Stream GetFromBase64Stream(Stream inputStream)
|
||||
private static CryptoStream GetFromBase64Stream(Stream inputStream)
|
||||
=> new CryptoStream(inputStream, new FromBase64Transform(), CryptoStreamMode.Read);
|
||||
|
||||
/// <summary>
|
||||
|
@ -2079,30 +2080,30 @@ public class ImageController : BaseJellyfinApiController
|
|||
|
||||
foreach (var (key, value) in headers)
|
||||
{
|
||||
Response.Headers.Add(key, value);
|
||||
Response.Headers.Append(key, value);
|
||||
}
|
||||
|
||||
Response.ContentType = imageContentType ?? MediaTypeNames.Text.Plain;
|
||||
Response.Headers.Add(HeaderNames.Age, Convert.ToInt64((DateTime.UtcNow - dateImageModified).TotalSeconds).ToString(CultureInfo.InvariantCulture));
|
||||
Response.Headers.Add(HeaderNames.Vary, HeaderNames.Accept);
|
||||
Response.Headers.Append(HeaderNames.Age, Convert.ToInt64((DateTime.UtcNow - dateImageModified).TotalSeconds).ToString(CultureInfo.InvariantCulture));
|
||||
Response.Headers.Append(HeaderNames.Vary, HeaderNames.Accept);
|
||||
|
||||
if (disableCaching)
|
||||
{
|
||||
Response.Headers.Add(HeaderNames.CacheControl, "no-cache, no-store, must-revalidate");
|
||||
Response.Headers.Add(HeaderNames.Pragma, "no-cache, no-store, must-revalidate");
|
||||
Response.Headers.Append(HeaderNames.CacheControl, "no-cache, no-store, must-revalidate");
|
||||
Response.Headers.Append(HeaderNames.Pragma, "no-cache, no-store, must-revalidate");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cacheDuration.HasValue)
|
||||
{
|
||||
Response.Headers.Add(HeaderNames.CacheControl, "public, max-age=" + cacheDuration.Value.TotalSeconds);
|
||||
Response.Headers.Append(HeaderNames.CacheControl, "public, max-age=" + cacheDuration.Value.TotalSeconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
Response.Headers.Add(HeaderNames.CacheControl, "public");
|
||||
Response.Headers.Append(HeaderNames.CacheControl, "public");
|
||||
}
|
||||
|
||||
Response.Headers.Add(HeaderNames.LastModified, dateImageModified.ToUniversalTime().ToString("ddd, dd MMM yyyy HH:mm:ss \"GMT\"", CultureInfo.InvariantCulture));
|
||||
Response.Headers.Append(HeaderNames.LastModified, dateImageModified.ToUniversalTime().ToString("ddd, dd MMM yyyy HH:mm:ss \"GMT\"", CultureInfo.InvariantCulture));
|
||||
|
||||
// if the image was not modified since "ifModifiedSinceHeader"-header, return a HTTP status code 304 not modified
|
||||
if (!(dateImageModified > ifModifiedSinceHeader) && cacheDuration.HasValue)
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Constants;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
|
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Jellyfin.Api.Constants;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
|
|
@ -6,6 +6,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
|
|
|
@ -15,6 +15,7 @@ using Jellyfin.Api.Models.LibraryDtos;
|
|||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
|
|
|
@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
|||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Api.Models.LibraryStructureDto;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
|
|
|
@ -16,6 +16,7 @@ using Jellyfin.Api.Helpers;
|
|||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Api.Models.LiveTvDtos;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using Jellyfin.Api.Constants;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
|
|
@ -150,7 +150,7 @@ public class MusicGenresController : BaseJellyfinApiController
|
|||
|
||||
MusicGenre? item;
|
||||
|
||||
if (genreName.IndexOf(BaseItem.SlugChar, StringComparison.OrdinalIgnoreCase) != -1)
|
||||
if (genreName.Contains(BaseItem.SlugChar, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
item = GetItemFromSlugName<MusicGenre>(_libraryManager, genreName, dtoOptions, BaseItemKind.MusicGenre);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Constants;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Updates;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Model.Updates;
|
||||
|
|
|
@ -8,6 +8,7 @@ using System.Threading.Tasks;
|
|||
using Jellyfin.Api.Attributes;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Extensions.Json;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Common.Updates;
|
||||
using MediaBrowser.Model.Net;
|
||||
|
|
|
@ -6,6 +6,7 @@ using System.Linq;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Constants;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using Jellyfin.Api.Constants;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
|
|
@ -10,6 +10,7 @@ using Jellyfin.Api.Helpers;
|
|||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Api.Models.SessionDtos;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Session;
|
||||
|
|
|
@ -3,7 +3,8 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Models.StartupDtos;
|
||||
using Jellyfin.Networking.Configuration;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
|
|
@ -14,6 +14,7 @@ using Jellyfin.Api.Attributes;
|
|||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Models.SubtitleDtos;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
|
|
@ -6,6 +6,7 @@ using System.Threading.Tasks;
|
|||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.Models.SyncPlayDtos;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Controller.SyncPlay;
|
||||
|
|
|
@ -6,6 +6,7 @@ using System.Linq;
|
|||
using System.Net.Mime;
|
||||
using Jellyfin.Api.Attributes;
|
||||
using Jellyfin.Api.Constants;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
|
|
|
@ -8,6 +8,7 @@ using Jellyfin.Api.Extensions;
|
|||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.Models.UserDtos;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
|
|
|
@ -12,6 +12,7 @@ using Jellyfin.Api.Extensions;
|
|||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Api.Models.StreamingDtos;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
|
|
|
@ -38,10 +38,10 @@ public static class DtoExtensions
|
|||
|
||||
if (!dtoOptions.ContainsField(ItemFields.RecursiveItemCount))
|
||||
{
|
||||
if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||
client.IndexOf("wmc", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||
client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||
client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
if (client.Contains("kodi", StringComparison.OrdinalIgnoreCase) ||
|
||||
client.Contains("wmc", StringComparison.OrdinalIgnoreCase) ||
|
||||
client.Contains("media center", StringComparison.OrdinalIgnoreCase) ||
|
||||
client.Contains("classic", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
int oldLen = dtoOptions.Fields.Count;
|
||||
var arr = new ItemFields[oldLen + 1];
|
||||
|
@ -53,13 +53,13 @@ public static class DtoExtensions
|
|||
|
||||
if (!dtoOptions.ContainsField(ItemFields.ChildCount))
|
||||
{
|
||||
if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||
client.IndexOf("wmc", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||
client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||
client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||
client.IndexOf("roku", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||
client.IndexOf("samsung", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||
client.IndexOf("androidtv", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
if (client.Contains("kodi", StringComparison.OrdinalIgnoreCase) ||
|
||||
client.Contains("wmc", StringComparison.OrdinalIgnoreCase) ||
|
||||
client.Contains("media center", StringComparison.OrdinalIgnoreCase) ||
|
||||
client.Contains("classic", StringComparison.OrdinalIgnoreCase) ||
|
||||
client.Contains("roku", StringComparison.OrdinalIgnoreCase) ||
|
||||
client.Contains("samsung", StringComparison.OrdinalIgnoreCase) ||
|
||||
client.Contains("androidtv", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
int oldLen = dtoOptions.Fields.Count;
|
||||
var arr = new ItemFields[oldLen + 1];
|
||||
|
|
|
@ -147,7 +147,7 @@ public class DynamicHlsHelper
|
|||
cancellationTokenSource.Token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
_httpContextAccessor.HttpContext.Response.Headers.Add(HeaderNames.Expires, "0");
|
||||
_httpContextAccessor.HttpContext.Response.Headers.Append(HeaderNames.Expires, "0");
|
||||
if (isHeadRequest)
|
||||
{
|
||||
return new FileContentResult(Array.Empty<byte>(), MimeTypes.GetMimeType("playlist.m3u8"));
|
||||
|
@ -568,7 +568,7 @@ public class DynamicHlsHelper
|
|||
&& state.VideoStream is not null
|
||||
&& state.VideoStream.Level.HasValue)
|
||||
{
|
||||
levelString = state.VideoStream.Level.ToString() ?? string.Empty;
|
||||
levelString = state.VideoStream.Level.Value.ToString(CultureInfo.InvariantCulture) ?? string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -53,7 +53,7 @@ public static class HlsHelpers
|
|||
break;
|
||||
}
|
||||
|
||||
if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
if (line.Contains("#EXTINF:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
count++;
|
||||
if (count >= segmentCount)
|
||||
|
|
|
@ -279,15 +279,15 @@ public static class StreamingHelpers
|
|||
var profile = state.DeviceProfile;
|
||||
|
||||
StringValues transferMode = request.Headers["transferMode.dlna.org"];
|
||||
responseHeaders.Add("transferMode.dlna.org", string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode.ToString());
|
||||
responseHeaders.Add("realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*");
|
||||
responseHeaders.Append("transferMode.dlna.org", string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode.ToString());
|
||||
responseHeaders.Append("realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*");
|
||||
|
||||
if (state.RunTimeTicks.HasValue)
|
||||
{
|
||||
if (string.Equals(request.Headers["getMediaInfo.sec"], "1", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var ms = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds;
|
||||
responseHeaders.Add("MediaInfo.sec", string.Format(
|
||||
responseHeaders.Append("MediaInfo.sec", string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"SEC_Duration={0};",
|
||||
Convert.ToInt32(ms)));
|
||||
|
@ -305,7 +305,7 @@ public static class StreamingHelpers
|
|||
|
||||
if (!state.IsVideoRequest)
|
||||
{
|
||||
responseHeaders.Add("contentFeatures.dlna.org", ContentFeatureBuilder.BuildAudioHeader(
|
||||
responseHeaders.Append("contentFeatures.dlna.org", ContentFeatureBuilder.BuildAudioHeader(
|
||||
profile,
|
||||
state.OutputContainer,
|
||||
audioCodec,
|
||||
|
@ -321,7 +321,7 @@ public static class StreamingHelpers
|
|||
{
|
||||
var videoCodec = state.ActualOutputVideoCodec;
|
||||
|
||||
responseHeaders.Add(
|
||||
responseHeaders.Append(
|
||||
"contentFeatures.dlna.org",
|
||||
ContentFeatureBuilder.BuildVideoHeader(profile, state.OutputContainer, videoCodec, audioCodec, state.OutputWidth, state.OutputHeight, state.TargetVideoBitDepth, state.OutputVideoBitrate, state.TargetTimestamp, isStaticallyStreamed, state.RunTimeTicks, state.TargetVideoProfile, state.TargetVideoRangeType, state.TargetVideoLevel, state.TargetFramerate, state.TargetPacketLength, state.TranscodeSeekInfo, state.IsTargetAnamorphic, state.IsTargetInterlaced, state.TargetRefFrames, state.TargetVideoStreamCount, state.TargetAudioStreamCount, state.TargetVideoCodecTag, state.IsTargetAVC).FirstOrDefault() ?? string.Empty);
|
||||
}
|
||||
|
@ -404,12 +404,12 @@ public static class StreamingHelpers
|
|||
var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks!.Value).TotalSeconds.ToString(CultureInfo.InvariantCulture);
|
||||
var startSeconds = TimeSpan.FromTicks(startTimeTicks ?? 0).TotalSeconds.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
responseHeaders.Add("TimeSeekRange.dlna.org", string.Format(
|
||||
responseHeaders.Append("TimeSeekRange.dlna.org", string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"npt={0}-{1}/{1}",
|
||||
startSeconds,
|
||||
runtimeSeconds));
|
||||
responseHeaders.Add("X-AvailableSeekRange", string.Format(
|
||||
responseHeaders.Append("X-AvailableSeekRange", string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"1 npt={0}-{1}",
|
||||
startSeconds,
|
||||
|
|
|
@ -280,6 +280,7 @@ public class TranscodingJobHelper : IDisposable
|
|||
|
||||
if (job.CancellationTokenSource?.IsCancellationRequested == false)
|
||||
{
|
||||
#pragma warning disable CA1849 // Can't await in lock block
|
||||
job.CancellationTokenSource.Cancel();
|
||||
}
|
||||
}
|
||||
|
@ -291,7 +292,6 @@ public class TranscodingJobHelper : IDisposable
|
|||
|
||||
lock (job.ProcessLock!)
|
||||
{
|
||||
#pragma warning disable CA1849 // Can't await in lock block
|
||||
job.TranscodingThrottler?.Stop().GetAwaiter().GetResult();
|
||||
|
||||
var process = job.Process;
|
||||
|
@ -405,7 +405,7 @@ public class TranscodingJobHelper : IDisposable
|
|||
var name = Path.GetFileNameWithoutExtension(outputFilePath);
|
||||
|
||||
var filesToDelete = _fileSystem.GetFilePaths(directory)
|
||||
.Where(f => f.IndexOf(name, StringComparison.OrdinalIgnoreCase) != -1);
|
||||
.Where(f => f.Contains(name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
List<Exception>? exs = null;
|
||||
foreach (var file in filesToDelete)
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Networking.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System.Threading.Tasks;
|
||||
using Jellyfin.Networking.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
|
|
|
@ -86,11 +86,11 @@ public class StreamState : EncodingJobInfo, IDisposable
|
|||
{
|
||||
var userAgent = UserAgent ?? string.Empty;
|
||||
|
||||
if (userAgent.IndexOf("AppleTV", StringComparison.OrdinalIgnoreCase) != -1
|
||||
|| userAgent.IndexOf("cfnetwork", StringComparison.OrdinalIgnoreCase) != -1
|
||||
|| userAgent.IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1
|
||||
|| userAgent.IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1
|
||||
|| userAgent.IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
if (userAgent.Contains("AppleTV", StringComparison.OrdinalIgnoreCase)
|
||||
|| userAgent.Contains("cfnetwork", StringComparison.OrdinalIgnoreCase)
|
||||
|| userAgent.Contains("ipad", StringComparison.OrdinalIgnoreCase)
|
||||
|| userAgent.Contains("iphone", StringComparison.OrdinalIgnoreCase)
|
||||
|| userAgent.Contains("ipod", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return 6;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
|
@ -23,10 +23,6 @@
|
|||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Code Analyzers -->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="IDisposableAnalyzers">
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -65,7 +65,7 @@ namespace Jellyfin.Networking.HappyEyeballs
|
|||
// See https://github.com/dotnet/corefx/pull/29792/files#r189415885 for more details.
|
||||
if (await Task.WhenAny(tryConnectAsyncIPv6, Task.Delay(200, cancelIPv6.Token)).ConfigureAwait(false) == tryConnectAsyncIPv6 && tryConnectAsyncIPv6.IsCompletedSuccessfully)
|
||||
{
|
||||
cancelIPv6.Cancel();
|
||||
await cancelIPv6.CancelAsync().ConfigureAwait(false);
|
||||
return tryConnectAsyncIPv6.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,7 @@ namespace Jellyfin.Networking.HappyEyeballs
|
|||
{
|
||||
if (tryConnectAsyncIPv6.IsCompletedSuccessfully)
|
||||
{
|
||||
cancelIPv4.Cancel();
|
||||
await cancelIPv4.CancelAsync().ConfigureAwait(false);
|
||||
return tryConnectAsyncIPv6.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
|
@ -86,7 +86,7 @@ namespace Jellyfin.Networking.HappyEyeballs
|
|||
{
|
||||
if (tryConnectAsyncIPv4.IsCompletedSuccessfully)
|
||||
{
|
||||
cancelIPv6.Cancel();
|
||||
await cancelIPv6.CancelAsync().ConfigureAwait(false);
|
||||
return tryConnectAsyncIPv4.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
|
|
@ -7,9 +7,6 @@ using System.Net;
|
|||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using Jellyfin.Networking.Configuration;
|
||||
using Jellyfin.Networking.Constants;
|
||||
using Jellyfin.Networking.Extensions;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.Net;
|
||||
|
@ -18,6 +15,8 @@ using Microsoft.AspNetCore.HttpOverrides;
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
|
||||
using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager;
|
||||
using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
|
||||
|
||||
namespace Jellyfin.Networking.Manager
|
||||
{
|
||||
|
@ -289,12 +288,12 @@ namespace Jellyfin.Networking.Manager
|
|||
|
||||
if (IsIPv4Enabled)
|
||||
{
|
||||
interfaces.Add(new IPData(IPAddress.Loopback, Network.IPv4RFC5735Loopback, "lo"));
|
||||
interfaces.Add(new IPData(IPAddress.Loopback, NetworkConstants.IPv4RFC5735Loopback, "lo"));
|
||||
}
|
||||
|
||||
if (IsIPv6Enabled)
|
||||
{
|
||||
interfaces.Add(new IPData(IPAddress.IPv6Loopback, Network.IPv6RFC4291Loopback, "lo"));
|
||||
interfaces.Add(new IPData(IPAddress.IPv6Loopback, NetworkConstants.IPv6RFC4291Loopback, "lo"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -319,24 +318,24 @@ namespace Jellyfin.Networking.Manager
|
|||
var subnets = config.LocalNetworkSubnets;
|
||||
|
||||
// If no LAN addresses are specified, all private subnets and Loopback are deemed to be the LAN
|
||||
if (!NetworkExtensions.TryParseToSubnets(subnets, out var lanSubnets, false) || lanSubnets.Count == 0)
|
||||
if (!NetworkUtils.TryParseToSubnets(subnets, out var lanSubnets, false) || lanSubnets.Count == 0)
|
||||
{
|
||||
_logger.LogDebug("Using LAN interface addresses as user provided no LAN details.");
|
||||
|
||||
var fallbackLanSubnets = new List<IPNetwork>();
|
||||
if (IsIPv6Enabled)
|
||||
{
|
||||
fallbackLanSubnets.Add(Network.IPv6RFC4291Loopback); // RFC 4291 (Loopback)
|
||||
fallbackLanSubnets.Add(Network.IPv6RFC4291SiteLocal); // RFC 4291 (Site local)
|
||||
fallbackLanSubnets.Add(Network.IPv6RFC4193UniqueLocal); // RFC 4193 (Unique local)
|
||||
fallbackLanSubnets.Add(NetworkConstants.IPv6RFC4291Loopback); // RFC 4291 (Loopback)
|
||||
fallbackLanSubnets.Add(NetworkConstants.IPv6RFC4291SiteLocal); // RFC 4291 (Site local)
|
||||
fallbackLanSubnets.Add(NetworkConstants.IPv6RFC4193UniqueLocal); // RFC 4193 (Unique local)
|
||||
}
|
||||
|
||||
if (IsIPv4Enabled)
|
||||
{
|
||||
fallbackLanSubnets.Add(Network.IPv4RFC5735Loopback); // RFC 5735 (Loopback)
|
||||
fallbackLanSubnets.Add(Network.IPv4RFC1918PrivateClassA); // RFC 1918 (private Class A)
|
||||
fallbackLanSubnets.Add(Network.IPv4RFC1918PrivateClassB); // RFC 1918 (private Class B)
|
||||
fallbackLanSubnets.Add(Network.IPv4RFC1918PrivateClassC); // RFC 1918 (private Class C)
|
||||
fallbackLanSubnets.Add(NetworkConstants.IPv4RFC5735Loopback); // RFC 5735 (Loopback)
|
||||
fallbackLanSubnets.Add(NetworkConstants.IPv4RFC1918PrivateClassA); // RFC 1918 (private Class A)
|
||||
fallbackLanSubnets.Add(NetworkConstants.IPv4RFC1918PrivateClassB); // RFC 1918 (private Class B)
|
||||
fallbackLanSubnets.Add(NetworkConstants.IPv4RFC1918PrivateClassC); // RFC 1918 (private Class C)
|
||||
}
|
||||
|
||||
_lanSubnets = fallbackLanSubnets;
|
||||
|
@ -346,7 +345,7 @@ namespace Jellyfin.Networking.Manager
|
|||
_lanSubnets = lanSubnets;
|
||||
}
|
||||
|
||||
_excludedSubnets = NetworkExtensions.TryParseToSubnets(subnets, out var excludedSubnets, true)
|
||||
_excludedSubnets = NetworkUtils.TryParseToSubnets(subnets, out var excludedSubnets, true)
|
||||
? excludedSubnets
|
||||
: new List<IPNetwork>();
|
||||
}
|
||||
|
@ -364,7 +363,7 @@ namespace Jellyfin.Networking.Manager
|
|||
var localNetworkAddresses = config.LocalNetworkAddresses;
|
||||
if (localNetworkAddresses.Length > 0 && !string.IsNullOrWhiteSpace(localNetworkAddresses[0]))
|
||||
{
|
||||
var bindAddresses = localNetworkAddresses.Select(p => NetworkExtensions.TryParseToSubnet(p, out var network)
|
||||
var bindAddresses = localNetworkAddresses.Select(p => NetworkUtils.TryParseToSubnet(p, out var network)
|
||||
? network.Prefix
|
||||
: (interfaces.Where(x => x.Name.Equals(p, StringComparison.OrdinalIgnoreCase))
|
||||
.Select(x => x.Address)
|
||||
|
@ -375,12 +374,12 @@ namespace Jellyfin.Networking.Manager
|
|||
|
||||
if (bindAddresses.Contains(IPAddress.Loopback) && !interfaces.Any(i => i.Address.Equals(IPAddress.Loopback)))
|
||||
{
|
||||
interfaces.Add(new IPData(IPAddress.Loopback, Network.IPv4RFC5735Loopback, "lo"));
|
||||
interfaces.Add(new IPData(IPAddress.Loopback, NetworkConstants.IPv4RFC5735Loopback, "lo"));
|
||||
}
|
||||
|
||||
if (bindAddresses.Contains(IPAddress.IPv6Loopback) && !interfaces.Any(i => i.Address.Equals(IPAddress.IPv6Loopback)))
|
||||
{
|
||||
interfaces.Add(new IPData(IPAddress.IPv6Loopback, Network.IPv6RFC4291Loopback, "lo"));
|
||||
interfaces.Add(new IPData(IPAddress.IPv6Loopback, NetworkConstants.IPv6RFC4291Loopback, "lo"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -426,12 +425,12 @@ namespace Jellyfin.Networking.Manager
|
|||
{
|
||||
// Parse config values into filter collection
|
||||
var remoteIPFilter = config.RemoteIPFilter;
|
||||
if (remoteIPFilter.Any() && !string.IsNullOrWhiteSpace(remoteIPFilter.First()))
|
||||
if (remoteIPFilter.Length != 0 && !string.IsNullOrWhiteSpace(remoteIPFilter[0]))
|
||||
{
|
||||
// Parse all IPs with netmask to a subnet
|
||||
var remoteAddressFilter = new List<IPNetwork>();
|
||||
var remoteFilteredSubnets = remoteIPFilter.Where(x => x.Contains('/', StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||
if (NetworkExtensions.TryParseToSubnets(remoteFilteredSubnets, out var remoteAddressFilterResult, false))
|
||||
if (NetworkUtils.TryParseToSubnets(remoteFilteredSubnets, out var remoteAddressFilterResult, false))
|
||||
{
|
||||
remoteAddressFilter = remoteAddressFilterResult.ToList();
|
||||
}
|
||||
|
@ -442,7 +441,7 @@ namespace Jellyfin.Networking.Manager
|
|||
{
|
||||
if (IPAddress.TryParse(ip, out var ipp))
|
||||
{
|
||||
remoteAddressFilter.Add(new IPNetwork(ipp, ipp.AddressFamily == AddressFamily.InterNetwork ? Network.MinimumIPv4PrefixSize : Network.MinimumIPv6PrefixSize));
|
||||
remoteAddressFilter.Add(new IPNetwork(ipp, ipp.AddressFamily == AddressFamily.InterNetwork ? NetworkConstants.MinimumIPv4PrefixSize : NetworkConstants.MinimumIPv6PrefixSize));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -470,13 +469,13 @@ namespace Jellyfin.Networking.Manager
|
|||
{
|
||||
publishedServerUrls.Add(
|
||||
new PublishedServerUriOverride(
|
||||
new IPData(IPAddress.Any, Network.IPv4Any),
|
||||
new IPData(IPAddress.Any, NetworkConstants.IPv4Any),
|
||||
startupOverrideKey,
|
||||
true,
|
||||
true));
|
||||
publishedServerUrls.Add(
|
||||
new PublishedServerUriOverride(
|
||||
new IPData(IPAddress.IPv6Any, Network.IPv6Any),
|
||||
new IPData(IPAddress.IPv6Any, NetworkConstants.IPv6Any),
|
||||
startupOverrideKey,
|
||||
true,
|
||||
true));
|
||||
|
@ -502,13 +501,13 @@ namespace Jellyfin.Networking.Manager
|
|||
publishedServerUrls.Clear();
|
||||
publishedServerUrls.Add(
|
||||
new PublishedServerUriOverride(
|
||||
new IPData(IPAddress.Any, Network.IPv4Any),
|
||||
new IPData(IPAddress.Any, NetworkConstants.IPv4Any),
|
||||
replacement,
|
||||
true,
|
||||
true));
|
||||
publishedServerUrls.Add(
|
||||
new PublishedServerUriOverride(
|
||||
new IPData(IPAddress.IPv6Any, Network.IPv6Any),
|
||||
new IPData(IPAddress.IPv6Any, NetworkConstants.IPv6Any),
|
||||
replacement,
|
||||
true,
|
||||
true));
|
||||
|
@ -518,13 +517,13 @@ namespace Jellyfin.Networking.Manager
|
|||
{
|
||||
publishedServerUrls.Add(
|
||||
new PublishedServerUriOverride(
|
||||
new IPData(IPAddress.Any, Network.IPv4Any),
|
||||
new IPData(IPAddress.Any, NetworkConstants.IPv4Any),
|
||||
replacement,
|
||||
false,
|
||||
true));
|
||||
publishedServerUrls.Add(
|
||||
new PublishedServerUriOverride(
|
||||
new IPData(IPAddress.IPv6Any, Network.IPv6Any),
|
||||
new IPData(IPAddress.IPv6Any, NetworkConstants.IPv6Any),
|
||||
replacement,
|
||||
false,
|
||||
true));
|
||||
|
@ -542,7 +541,7 @@ namespace Jellyfin.Networking.Manager
|
|||
false));
|
||||
}
|
||||
}
|
||||
else if (NetworkExtensions.TryParseToSubnet(identifier, out var result) && result is not null)
|
||||
else if (NetworkUtils.TryParseToSubnet(identifier, out var result) && result is not null)
|
||||
{
|
||||
var data = new IPData(result.Prefix, result);
|
||||
publishedServerUrls.Add(
|
||||
|
@ -608,7 +607,7 @@ namespace Jellyfin.Networking.Manager
|
|||
foreach (var details in interfaceList)
|
||||
{
|
||||
var parts = details.Split(',');
|
||||
if (NetworkExtensions.TryParseToSubnet(parts[0], out var subnet))
|
||||
if (NetworkUtils.TryParseToSubnet(parts[0], out var subnet))
|
||||
{
|
||||
var address = subnet.Prefix;
|
||||
var index = int.Parse(parts[1], CultureInfo.InvariantCulture);
|
||||
|
@ -724,12 +723,12 @@ namespace Jellyfin.Networking.Manager
|
|||
var loopbackNetworks = new List<IPData>();
|
||||
if (IsIPv4Enabled)
|
||||
{
|
||||
loopbackNetworks.Add(new IPData(IPAddress.Loopback, Network.IPv4RFC5735Loopback, "lo"));
|
||||
loopbackNetworks.Add(new IPData(IPAddress.Loopback, NetworkConstants.IPv4RFC5735Loopback, "lo"));
|
||||
}
|
||||
|
||||
if (IsIPv6Enabled)
|
||||
{
|
||||
loopbackNetworks.Add(new IPData(IPAddress.IPv6Loopback, Network.IPv6RFC4291Loopback, "lo"));
|
||||
loopbackNetworks.Add(new IPData(IPAddress.IPv6Loopback, NetworkConstants.IPv6RFC4291Loopback, "lo"));
|
||||
}
|
||||
|
||||
return loopbackNetworks;
|
||||
|
@ -748,11 +747,11 @@ namespace Jellyfin.Networking.Manager
|
|||
if (IsIPv4Enabled && IsIPv6Enabled)
|
||||
{
|
||||
// Kestrel source code shows it uses Sockets.DualMode - so this also covers IPAddress.Any by default
|
||||
result.Add(new IPData(IPAddress.IPv6Any, Network.IPv6Any));
|
||||
result.Add(new IPData(IPAddress.IPv6Any, NetworkConstants.IPv6Any));
|
||||
}
|
||||
else if (IsIPv4Enabled)
|
||||
{
|
||||
result.Add(new IPData(IPAddress.Any, Network.IPv4Any));
|
||||
result.Add(new IPData(IPAddress.Any, NetworkConstants.IPv4Any));
|
||||
}
|
||||
else if (IsIPv6Enabled)
|
||||
{
|
||||
|
@ -772,7 +771,7 @@ namespace Jellyfin.Networking.Manager
|
|||
/// <inheritdoc/>
|
||||
public string GetBindAddress(string source, out int? port)
|
||||
{
|
||||
if (!NetworkExtensions.TryParseHost(source, out var addresses, IsIPv4Enabled, IsIPv6Enabled))
|
||||
if (!NetworkUtils.TryParseHost(source, out var addresses, IsIPv4Enabled, IsIPv6Enabled))
|
||||
{
|
||||
addresses = Array.Empty<IPAddress>();
|
||||
}
|
||||
|
@ -847,7 +846,7 @@ namespace Jellyfin.Networking.Manager
|
|||
// If no source address is given, use the preferred (first) interface
|
||||
if (source is null)
|
||||
{
|
||||
result = NetworkExtensions.FormatIPString(availableInterfaces.First().Address);
|
||||
result = NetworkUtils.FormatIPString(availableInterfaces.First().Address);
|
||||
_logger.LogDebug("{Source}: Using first internal interface as bind address: {Result}", source, result);
|
||||
return result;
|
||||
}
|
||||
|
@ -858,14 +857,14 @@ namespace Jellyfin.Networking.Manager
|
|||
{
|
||||
if (intf.Subnet.Contains(source))
|
||||
{
|
||||
result = NetworkExtensions.FormatIPString(intf.Address);
|
||||
result = NetworkUtils.FormatIPString(intf.Address);
|
||||
_logger.LogDebug("{Source}: Found interface with matching subnet, using it as bind address: {Result}", source, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to first available interface
|
||||
result = NetworkExtensions.FormatIPString(availableInterfaces[0].Address);
|
||||
result = NetworkUtils.FormatIPString(availableInterfaces[0].Address);
|
||||
_logger.LogDebug("{Source}: No matching interfaces found, using preferred interface as bind address: {Result}", source, result);
|
||||
return result;
|
||||
}
|
||||
|
@ -882,12 +881,12 @@ namespace Jellyfin.Networking.Manager
|
|||
/// <inheritdoc/>
|
||||
public bool IsInLocalNetwork(string address)
|
||||
{
|
||||
if (NetworkExtensions.TryParseToSubnet(address, out var subnet))
|
||||
if (NetworkUtils.TryParseToSubnet(address, out var subnet))
|
||||
{
|
||||
return IPAddress.IsLoopback(subnet.Prefix) || (_lanSubnets.Any(x => x.Contains(subnet.Prefix)) && !_excludedSubnets.Any(x => x.Contains(subnet.Prefix)));
|
||||
}
|
||||
|
||||
if (NetworkExtensions.TryParseHost(address, out var addresses, IsIPv4Enabled, IsIPv6Enabled))
|
||||
if (NetworkUtils.TryParseHost(address, out var addresses, IsIPv4Enabled, IsIPv6Enabled))
|
||||
{
|
||||
foreach (var ept in addresses)
|
||||
{
|
||||
|
@ -1045,7 +1044,7 @@ namespace Jellyfin.Networking.Manager
|
|||
.Select(x => x.Address)
|
||||
.First();
|
||||
|
||||
result = NetworkExtensions.FormatIPString(bindAddress);
|
||||
result = NetworkUtils.FormatIPString(bindAddress);
|
||||
_logger.LogDebug("{Source}: External request received, matching external bind address found: {Result}", source, result);
|
||||
return true;
|
||||
}
|
||||
|
@ -1064,7 +1063,7 @@ namespace Jellyfin.Networking.Manager
|
|||
|
||||
if (bindAddress is not null)
|
||||
{
|
||||
result = NetworkExtensions.FormatIPString(bindAddress);
|
||||
result = NetworkUtils.FormatIPString(bindAddress);
|
||||
_logger.LogDebug("{Source}: Internal request received, matching internal bind address found: {Result}", source, result);
|
||||
return true;
|
||||
}
|
||||
|
@ -1098,14 +1097,14 @@ namespace Jellyfin.Networking.Manager
|
|||
{
|
||||
if (intf.Subnet.Contains(source))
|
||||
{
|
||||
result = NetworkExtensions.FormatIPString(intf.Address);
|
||||
result = NetworkUtils.FormatIPString(intf.Address);
|
||||
_logger.LogDebug("{Source}: Found external interface with matching subnet, using it as bind address: {Result}", source, result);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to first external interface.
|
||||
result = NetworkExtensions.FormatIPString(extResult[0].Address);
|
||||
result = NetworkUtils.FormatIPString(extResult[0].Address);
|
||||
_logger.LogDebug("{Source}: Using first external interface as bind address: {Result}", source, result);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
|
|
@ -60,7 +60,7 @@ namespace Jellyfin.Server.Implementations.Security
|
|||
}
|
||||
|
||||
private async Task<AuthorizationInfo> GetAuthorizationInfoFromDictionary(
|
||||
IReadOnlyDictionary<string, string>? auth,
|
||||
Dictionary<string, string>? auth,
|
||||
IHeaderDictionary headers,
|
||||
IQueryCollection queryString)
|
||||
{
|
||||
|
|
|
@ -748,7 +748,7 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
return GetPasswordResetProviders(user)[0];
|
||||
}
|
||||
|
||||
private IList<IAuthenticationProvider> GetAuthenticationProviders(User? user)
|
||||
private List<IAuthenticationProvider> GetAuthenticationProviders(User? user)
|
||||
{
|
||||
var authenticationProviderId = user?.AuthenticationProviderId;
|
||||
|
||||
|
@ -775,7 +775,7 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
return providers;
|
||||
}
|
||||
|
||||
private IList<IPasswordResetProvider> GetPasswordResetProviders(User user)
|
||||
private IPasswordResetProvider[] GetPasswordResetProviders(User user)
|
||||
{
|
||||
var passwordResetProviderId = user.PasswordResetProviderId;
|
||||
var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using Jellyfin.Api.Middleware;
|
||||
using Jellyfin.Networking.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
|
|
@ -20,11 +20,10 @@ using Jellyfin.Api.Formatters;
|
|||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions.Json;
|
||||
using Jellyfin.Networking.Configuration;
|
||||
using Jellyfin.Networking.Constants;
|
||||
using Jellyfin.Networking.Extensions;
|
||||
using Jellyfin.Server.Configuration;
|
||||
using Jellyfin.Server.Filters;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
|
@ -38,6 +37,7 @@ using Microsoft.OpenApi.Interfaces;
|
|||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using AuthenticationSchemes = Jellyfin.Api.Constants.AuthenticationSchemes;
|
||||
using IPNetwork = System.Net.IPNetwork;
|
||||
|
||||
namespace Jellyfin.Server.Extensions
|
||||
{
|
||||
|
@ -275,20 +275,20 @@ namespace Jellyfin.Server.Extensions
|
|||
{
|
||||
if (IPAddress.TryParse(allowedProxies[i], out var addr))
|
||||
{
|
||||
AddIPAddress(config, options, addr, addr.AddressFamily == AddressFamily.InterNetwork ? Network.MinimumIPv4PrefixSize : Network.MinimumIPv6PrefixSize);
|
||||
AddIPAddress(config, options, addr, addr.AddressFamily == AddressFamily.InterNetwork ? NetworkConstants.MinimumIPv4PrefixSize : NetworkConstants.MinimumIPv6PrefixSize);
|
||||
}
|
||||
else if (NetworkExtensions.TryParseToSubnet(allowedProxies[i], out var subnet))
|
||||
else if (NetworkUtils.TryParseToSubnet(allowedProxies[i], out var subnet))
|
||||
{
|
||||
if (subnet is not null)
|
||||
{
|
||||
AddIPAddress(config, options, subnet.Prefix, subnet.PrefixLength);
|
||||
}
|
||||
}
|
||||
else if (NetworkExtensions.TryParseHost(allowedProxies[i], out var addresses, config.EnableIPv4, config.EnableIPv6))
|
||||
else if (NetworkUtils.TryParseHost(allowedProxies[i], out var addresses, config.EnableIPv4, config.EnableIPv6))
|
||||
{
|
||||
foreach (var address in addresses)
|
||||
{
|
||||
AddIPAddress(config, options, address, address.AddressFamily == AddressFamily.InterNetwork ? Network.MinimumIPv4PrefixSize : Network.MinimumIPv6PrefixSize);
|
||||
AddIPAddress(config, options, address, address.AddressFamily == AddressFamily.InterNetwork ? NetworkConstants.MinimumIPv4PrefixSize : NetworkConstants.MinimumIPv6PrefixSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -306,13 +306,13 @@ namespace Jellyfin.Server.Extensions
|
|||
return;
|
||||
}
|
||||
|
||||
if (prefixLength == Network.MinimumIPv4PrefixSize)
|
||||
if (prefixLength == NetworkConstants.MinimumIPv4PrefixSize)
|
||||
{
|
||||
options.KnownProxies.Add(addr);
|
||||
}
|
||||
else
|
||||
{
|
||||
options.KnownNetworks.Add(new IPNetwork(addr, prefixLength));
|
||||
options.KnownNetworks.Add(new Microsoft.AspNetCore.HttpOverrides.IPNetwork(addr, prefixLength));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<PropertyGroup>
|
||||
<AssemblyName>jellyfin</AssemblyName>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ServerGarbageCollection>false</ServerGarbageCollection>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
|
|
|
@ -3,7 +3,7 @@ using System.IO;
|
|||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
using Emby.Server.Implementations;
|
||||
using Jellyfin.Networking.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Server.Migrations.PreStartupRoutines;
|
||||
|
|
|
@ -78,11 +78,7 @@ namespace Jellyfin.Server.Migrations.Routines
|
|||
}
|
||||
else
|
||||
{
|
||||
var ratingValue = _localizationManager.GetRatingLevel(ratingString).ToString();
|
||||
if (string.IsNullOrEmpty(ratingValue))
|
||||
{
|
||||
ratingValue = "NULL";
|
||||
}
|
||||
var ratingValue = _localizationManager.GetRatingLevel(ratingString)?.ToString(CultureInfo.InvariantCulture) ?? "NULL";
|
||||
|
||||
using var statement = connection.PrepareStatement("UPDATE TypedBaseItems SET InheritedParentalRatingValue = @Value WHERE OfficialRating = @Rating;");
|
||||
statement.TryBind("@Value", ratingValue);
|
||||
|
|
|
@ -40,7 +40,7 @@ namespace Jellyfin.Server
|
|||
/// </summary>
|
||||
public const string LoggingConfigFileSystem = "logging.json";
|
||||
|
||||
private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory();
|
||||
private static readonly SerilogLoggerFactory _loggerFactory = new SerilogLoggerFactory();
|
||||
private static long _startTimestamp;
|
||||
private static ILogger _logger = NullLogger.Instance;
|
||||
private static bool _restartOnShutdown;
|
||||
|
|
|
@ -7,7 +7,6 @@ using System.Text;
|
|||
using Emby.Dlna.Extensions;
|
||||
using Jellyfin.Api.Middleware;
|
||||
using Jellyfin.MediaEncoding.Hls.Extensions;
|
||||
using Jellyfin.Networking.Configuration;
|
||||
using Jellyfin.Networking.HappyEyeballs;
|
||||
using Jellyfin.Server.Extensions;
|
||||
using Jellyfin.Server.HealthChecks;
|
||||
|
@ -36,7 +35,7 @@ namespace Jellyfin.Server
|
|||
/// </summary>
|
||||
public class Startup
|
||||
{
|
||||
private readonly IServerApplicationHost _serverApplicationHost;
|
||||
private readonly CoreAppHost _serverApplicationHost;
|
||||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace Jellyfin.Api.Constants;
|
||||
namespace MediaBrowser.Common.Api;
|
||||
|
||||
/// <summary>
|
||||
/// Policies for the API authorization.
|
|
@ -21,7 +21,6 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -29,7 +28,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
|
|
175
MediaBrowser.Common/Net/NetworkConfiguration.cs
Normal file
175
MediaBrowser.Common/Net/NetworkConfiguration.cs
Normal 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; }
|
||||
}
|
19
MediaBrowser.Common/Net/NetworkConfigurationExtensions.cs
Normal file
19
MediaBrowser.Common/Net/NetworkConfigurationExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
22
MediaBrowser.Common/Net/NetworkConfigurationFactory.cs
Normal file
22
MediaBrowser.Common/Net/NetworkConfigurationFactory.cs
Normal 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()
|
||||
};
|
||||
}
|
||||
}
|
23
MediaBrowser.Common/Net/NetworkConfigurationStore.cs
Normal file
23
MediaBrowser.Common/Net/NetworkConfigurationStore.cs
Normal 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;
|
||||
}
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
using System.Net;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
|
||||
|
||||
namespace Jellyfin.Networking.Constants;
|
||||
namespace MediaBrowser.Common.Net;
|
||||
|
||||
/// <summary>
|
||||
/// Networking constants.
|
||||
/// </summary>
|
||||
public static class Network
|
||||
public static class NetworkConstants
|
||||
{
|
||||
/// <summary>
|
||||
/// IPv4 mask bytes.
|
|
@ -5,15 +5,14 @@ using System.Net;
|
|||
using System.Net.Sockets;
|
||||
using System.Text.RegularExpressions;
|
||||
using Jellyfin.Extensions;
|
||||
using Jellyfin.Networking.Constants;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
|
||||
|
||||
namespace Jellyfin.Networking.Extensions;
|
||||
namespace MediaBrowser.Common.Net;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="NetworkExtensions" />.
|
||||
/// Defines the <see cref="NetworkUtils" />.
|
||||
/// </summary>
|
||||
public static partial class NetworkExtensions
|
||||
public static partial class NetworkUtils
|
||||
{
|
||||
// Use regular expression as CheckHostName isn't RFC5892 compliant.
|
||||
// Modified from gSkinner's expression at https://stackoverflow.com/questions/11809631/fully-qualified-domain-name-validation
|
||||
|
@ -59,7 +58,7 @@ public static partial class NetworkExtensions
|
|||
/// <returns>String value of the subnet mask in dotted decimal notation.</returns>
|
||||
public static IPAddress CidrToMask(byte cidr, AddressFamily family)
|
||||
{
|
||||
uint addr = 0xFFFFFFFF << ((family == AddressFamily.InterNetwork ? Network.MinimumIPv4PrefixSize : Network.MinimumIPv6PrefixSize) - cidr);
|
||||
uint addr = 0xFFFFFFFF << ((family == AddressFamily.InterNetwork ? NetworkConstants.MinimumIPv4PrefixSize : NetworkConstants.MinimumIPv6PrefixSize) - cidr);
|
||||
addr = ((addr & 0xff000000) >> 24)
|
||||
| ((addr & 0x00ff0000) >> 8)
|
||||
| ((addr & 0x0000ff00) << 8)
|
||||
|
@ -75,7 +74,7 @@ public static partial class NetworkExtensions
|
|||
/// <returns>String value of the subnet mask in dotted decimal notation.</returns>
|
||||
public static IPAddress CidrToMask(int cidr, AddressFamily family)
|
||||
{
|
||||
uint addr = 0xFFFFFFFF << ((family == AddressFamily.InterNetwork ? Network.MinimumIPv4PrefixSize : Network.MinimumIPv6PrefixSize) - cidr);
|
||||
uint addr = 0xFFFFFFFF << ((family == AddressFamily.InterNetwork ? NetworkConstants.MinimumIPv4PrefixSize : NetworkConstants.MinimumIPv6PrefixSize) - cidr);
|
||||
addr = ((addr & 0xff000000) >> 24)
|
||||
| ((addr & 0x00ff0000) >> 8)
|
||||
| ((addr & 0x0000ff00) << 8)
|
||||
|
@ -100,7 +99,7 @@ public static partial class NetworkExtensions
|
|||
}
|
||||
|
||||
// GetAddressBytes
|
||||
Span<byte> bytes = stackalloc byte[mask.AddressFamily == AddressFamily.InterNetwork ? Network.IPv4MaskBytes : Network.IPv6MaskBytes];
|
||||
Span<byte> bytes = stackalloc byte[mask.AddressFamily == AddressFamily.InterNetwork ? NetworkConstants.IPv4MaskBytes : NetworkConstants.IPv6MaskBytes];
|
||||
if (!mask.TryWriteBytes(bytes, out var bytesWritten))
|
||||
{
|
||||
Console.WriteLine("Unable to write address bytes, only ${bytesWritten} bytes written.");
|
||||
|
@ -198,48 +197,31 @@ public static partial class NetworkExtensions
|
|||
/// <returns><c>True</c> if parsing was successful.</returns>
|
||||
public static bool TryParseToSubnet(ReadOnlySpan<char> value, [NotNullWhen(true)] out IPNetwork? result, bool negated = false)
|
||||
{
|
||||
var splitString = value.Trim().Split('/');
|
||||
if (splitString.MoveNext())
|
||||
value = value.Trim();
|
||||
if (value.Contains('/'))
|
||||
{
|
||||
var ipBlock = splitString.Current;
|
||||
var address = IPAddress.None;
|
||||
if (negated && ipBlock.StartsWith("!") && IPAddress.TryParse(ipBlock[1..], out var tmpAddress))
|
||||
if (negated && value.StartsWith("!") && IPNetwork.TryParse(value[1..], out result))
|
||||
{
|
||||
address = tmpAddress;
|
||||
}
|
||||
else if (!negated && IPAddress.TryParse(ipBlock, out tmpAddress))
|
||||
{
|
||||
address = tmpAddress;
|
||||
}
|
||||
|
||||
if (address != IPAddress.None)
|
||||
{
|
||||
if (splitString.MoveNext())
|
||||
{
|
||||
var subnetBlock = splitString.Current;
|
||||
if (int.TryParse(subnetBlock, out var netmask))
|
||||
{
|
||||
result = new IPNetwork(address, netmask);
|
||||
return true;
|
||||
}
|
||||
else if (IPAddress.TryParse(subnetBlock, out var netmaskAddress))
|
||||
else if (!negated && IPNetwork.TryParse(value, out result))
|
||||
{
|
||||
result = new IPNetwork(address, NetworkExtensions.MaskToCidr(netmaskAddress));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (address.AddressFamily == AddressFamily.InterNetwork)
|
||||
else if (IPAddress.TryParse(value, out var address))
|
||||
{
|
||||
result = address.Equals(IPAddress.Any) ? Network.IPv4Any : new IPNetwork(address, Network.MinimumIPv4PrefixSize);
|
||||
if (address.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
result = address.Equals(IPAddress.Any) ? NetworkConstants.IPv4Any : new IPNetwork(address, NetworkConstants.MinimumIPv4PrefixSize);
|
||||
return true;
|
||||
}
|
||||
else if (address.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
result = address.Equals(IPAddress.IPv6Any) ? Network.IPv6Any : new IPNetwork(address, Network.MinimumIPv6PrefixSize);
|
||||
result = address.Equals(IPAddress.IPv6Any) ? NetworkConstants.IPv6Any : new IPNetwork(address, NetworkConstants.MinimumIPv6PrefixSize);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -20,7 +20,6 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
|
||||
<PackageReference Include="System.Threading.Tasks.Dataflow" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -35,7 +34,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user