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