mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-05-31 04:05:50 -04:00
Merge branch 'master' into network-rewrite
This commit is contained in:
commit
3a91c37283
10
.github/workflows/automation.yml
vendored
10
.github/workflows/automation.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
||||
if: ${{ github.repository == 'jellyfin/jellyfin' }}
|
||||
steps:
|
||||
- name: Remove from 'Current Release' project
|
||||
uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d # tag=v0.8.2
|
||||
uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3
|
||||
if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport')
|
||||
continue-on-error: true
|
||||
with:
|
||||
@ -36,7 +36,7 @@ jobs:
|
||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
|
||||
- name: Add to 'Release Next' project
|
||||
uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d # tag=v0.8.2
|
||||
uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3
|
||||
if: (github.event.pull_request || github.event.issue.pull_request) && github.event.action == 'opened'
|
||||
continue-on-error: true
|
||||
with:
|
||||
@ -45,7 +45,7 @@ jobs:
|
||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
|
||||
- name: Add to 'Current Release' project
|
||||
uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d # tag=v0.8.2
|
||||
uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3
|
||||
if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport')
|
||||
continue-on-error: true
|
||||
with:
|
||||
@ -59,7 +59,7 @@ jobs:
|
||||
run: echo "::set-output name=number::$(curl -s ${{ github.event.issue.comments_url }} | jq '.[] | select(.author_association == "MEMBER") | .author_association' | wc -l)"
|
||||
|
||||
- name: Move issue to needs triage
|
||||
uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d # tag=v0.8.2
|
||||
uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3
|
||||
if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' && steps.member_comments.outputs.number <= 1
|
||||
continue-on-error: true
|
||||
with:
|
||||
@ -68,7 +68,7 @@ jobs:
|
||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
|
||||
- name: Add issue to triage project
|
||||
uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d # tag=v0.8.2
|
||||
uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3
|
||||
if: github.event.issue.pull_request == '' && github.event.action == 'opened'
|
||||
continue-on-error: true
|
||||
with:
|
||||
|
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@ -27,11 +27,11 @@ jobs:
|
||||
dotnet-version: '7.0.x'
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@a34ca99b4610d924e04c68db79e503e1f79f9f02 # v2
|
||||
uses: github/codeql-action/init@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
queries: +security-extended
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@a34ca99b4610d924e04c68db79e503e1f79f9f02 # v2
|
||||
uses: github/codeql-action/autobuild@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@a34ca99b4610d924e04c68db79e503e1f79f9f02 # v2
|
||||
uses: github/codeql-action/analyze@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2
|
||||
|
10
.github/workflows/commands.yml
vendored
10
.github/workflows/commands.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Notify as seen
|
||||
uses: peter-evans/create-or-update-comment@5adcb0bb0f9fb3f95ef05400558bdb3f329ee808 # tag=v2
|
||||
uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d # v2
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
comment-id: ${{ github.event.comment.id }}
|
||||
@ -43,7 +43,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Notify as seen
|
||||
uses: peter-evans/create-or-update-comment@5adcb0bb0f9fb3f95ef05400558bdb3f329ee808 # tag=v2
|
||||
uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d # v2
|
||||
if: ${{ github.event.comment != null }}
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
- name: Notify as running
|
||||
id: comment_running
|
||||
uses: peter-evans/create-or-update-comment@5adcb0bb0f9fb3f95ef05400558bdb3f329ee808 # tag=v2
|
||||
uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d # v2
|
||||
if: ${{ github.event.comment != null }}
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
@ -93,7 +93,7 @@ jobs:
|
||||
exit ${retcode}
|
||||
|
||||
- name: Notify with result success
|
||||
uses: peter-evans/create-or-update-comment@5adcb0bb0f9fb3f95ef05400558bdb3f329ee808 # tag=v2
|
||||
uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d # v2
|
||||
if: ${{ github.event.comment != null && success() }}
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
@ -108,7 +108,7 @@ jobs:
|
||||
reactions: hooray
|
||||
|
||||
- name: Notify with result failure
|
||||
uses: peter-evans/create-or-update-comment@5adcb0bb0f9fb3f95ef05400558bdb3f329ee808 # tag=v2
|
||||
uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d # v2
|
||||
if: ${{ github.event.comment != null && failure() }}
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
|
6
.github/workflows/openapi.yml
vendored
6
.github/workflows/openapi.yml
vendored
@ -103,14 +103,14 @@ jobs:
|
||||
body="${body//$'\r'/'%0D'}"
|
||||
echo ::set-output name=body::$body
|
||||
- name: Find difference comment
|
||||
uses: peter-evans/find-comment@81e2da3af01c92f83cb927cf3ace0e085617c556 # v2
|
||||
uses: peter-evans/find-comment@85a676a52594b4481e0532825a2d8906ef96dac2 # v2
|
||||
id: find-comment
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
direction: last
|
||||
body-includes: openapi-diff-workflow-comment
|
||||
- name: Reply or edit difference comment (changed)
|
||||
uses: peter-evans/create-or-update-comment@5adcb0bb0f9fb3f95ef05400558bdb3f329ee808 # tag=v2
|
||||
uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d # v2
|
||||
if: ${{ steps.read-diff.outputs.body != '' }}
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
@ -125,7 +125,7 @@ jobs:
|
||||
|
||||
</details>
|
||||
- name: Edit difference comment (unchanged)
|
||||
uses: peter-evans/create-or-update-comment@5adcb0bb0f9fb3f95ef05400558bdb3f329ee808 # tag=v2
|
||||
uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d # v2
|
||||
if: ${{ steps.read-diff.outputs.body == '' && steps.find-comment.outputs.comment-id != '' }}
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
|
3
.npmrc
3
.npmrc
@ -1,3 +0,0 @@
|
||||
registry=https://registry.npmjs.org/
|
||||
@jellyfin:registry=https://pkgs.dev.azure.com/jellyfin-project/jellyfin/_packaging/unstable/npm/registry/
|
||||
always-auth=true
|
@ -58,6 +58,7 @@
|
||||
- [HelloWorld017](https://github.com/HelloWorld017)
|
||||
- [ikomhoog](https://github.com/ikomhoog)
|
||||
- [jftuga](https://github.com/jftuga)
|
||||
- [jmshrv](https://github.com/jmshrv)
|
||||
- [joern-h](https://github.com/joern-h)
|
||||
- [joshuaboniface](https://github.com/joshuaboniface)
|
||||
- [JustAMan](https://github.com/JustAMan)
|
||||
@ -231,3 +232,4 @@
|
||||
- [Matthew Jones](https://github.com/matthew-jones-uk)
|
||||
- [Jakob Kukla](https://github.com/jakobkukla)
|
||||
- [Utku Özdemir](https://github.com/utkuozdemir)
|
||||
- [JPUC1143](https://github.com/Jpuc1143/)
|
||||
|
90
Directory.Packages.props
Normal file
90
Directory.Packages.props
Normal file
@ -0,0 +1,90 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.-->
|
||||
|
||||
<ItemGroup Label="Package Dependencies">
|
||||
<PackageVersion Include="AutoFixture.AutoMoq" Version="4.17.0" />
|
||||
<PackageVersion Include="AutoFixture.Xunit2" Version="4.17.0" />
|
||||
<PackageVersion Include="AutoFixture" Version="4.17.0" />
|
||||
<PackageVersion Include="BDInfo" Version="0.7.6.2" />
|
||||
<PackageVersion Include="BlurHashSharp.SkiaSharp" Version="1.2.0" />
|
||||
<PackageVersion Include="BlurHashSharp" Version="1.2.0" />
|
||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageVersion Include="coverlet.collector" Version="3.2.0" />
|
||||
<PackageVersion Include="Diacritics" Version="3.3.14" />
|
||||
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
|
||||
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
|
||||
<PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="3.8.3" />
|
||||
<PackageVersion Include="FsCheck.Xunit" Version="2.16.5" />
|
||||
<PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
|
||||
<PackageVersion Include="libse" Version="3.6.10" />
|
||||
<PackageVersion Include="LrcParser" Version="2022.529.1" />
|
||||
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="5.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="7.0.3" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.3" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.3" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.3" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.3" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.3" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.3" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="7.0.3" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="7.0.3" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="7.0.1" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
|
||||
<PackageVersion Include="MimeTypes" Version="2.4.0" />
|
||||
<PackageVersion Include="Mono.Nat" Version="3.0.4" />
|
||||
<PackageVersion Include="Moq" Version="4.18.4" />
|
||||
<PackageVersion Include="NEbml" Version="0.11.0" />
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
<PackageVersion Include="PlaylistsNET" Version="1.3.1" />
|
||||
<PackageVersion Include="prometheus-net.AspNetCore" Version="7.0.0" />
|
||||
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
|
||||
<PackageVersion Include="prometheus-net" Version="7.0.0" />
|
||||
<PackageVersion Include="Serilog.AspNetCore" Version="6.1.0" />
|
||||
<PackageVersion Include="Serilog.Enrichers.Thread" Version="3.1.0" />
|
||||
<PackageVersion Include="Serilog.Settings.Configuration" Version="3.4.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Console" Version="4.1.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Graylog" Version="2.3.0" />
|
||||
<PackageVersion Include="SerilogAnalyzer" Version="0.15.0" />
|
||||
<PackageVersion Include="SharpFuzz" Version="2.0.1" />
|
||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.3" />
|
||||
<PackageVersion Include="SkiaSharp.Svg" Version="1.60.0" />
|
||||
<PackageVersion Include="SkiaSharp" Version="2.88.3" />
|
||||
<PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
|
||||
<PackageVersion Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
|
||||
<PackageVersion Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.4" />
|
||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.435" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="6.4.0" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||
<PackageVersion Include="System.Globalization" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="7.0.0" />
|
||||
<PackageVersion Include="System.Text.Json" Version="7.0.2" />
|
||||
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="7.0.0" />
|
||||
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
|
||||
<PackageVersion Include="TMDbLib" Version="2.0.0" />
|
||||
<PackageVersion Include="UTF.Unknown" Version="2.5.1" />
|
||||
<PackageVersion Include="Xunit.Priority" Version="1.1.6" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="2.4.5" />
|
||||
<PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" />
|
||||
<PackageVersion Include="xunit" Version="2.4.2" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -37,7 +37,7 @@ RUN apt-get update \
|
||||
&& apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
||||
mesa-va-drivers \
|
||||
jellyfin-ffmpeg \
|
||||
jellyfin-ffmpeg5 \
|
||||
openssl \
|
||||
locales \
|
||||
# Intel VAAPI Tone mapping dependencies:
|
||||
|
@ -28,13 +28,13 @@
|
||||
|
||||
<!-- Code Analyzers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
<PackageReference Include="SerilogAnalyzer" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -80,7 +80,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -7,6 +7,7 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Dlna.PlayTo;
|
||||
using Emby.Dlna.Ssdp;
|
||||
@ -261,7 +262,7 @@ namespace Emby.Dlna.Main
|
||||
{
|
||||
_publisher = new SsdpDevicePublisher(
|
||||
_communicationsServer,
|
||||
MediaBrowser.Common.System.OperatingSystem.Name,
|
||||
Environment.OSVersion.Platform.ToString(),
|
||||
Environment.OSVersion.VersionString,
|
||||
_config.GetDlnaConfiguration().SendOnlyMatchedHost)
|
||||
{
|
||||
|
@ -42,18 +42,18 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Code Analyzers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
<PackageReference Include="SerilogAnalyzer" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -1,123 +0,0 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.Notifications;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.Notifications;
|
||||
|
||||
namespace Emby.Notifications
|
||||
{
|
||||
public class CoreNotificationTypes : INotificationTypeFactory
|
||||
{
|
||||
private readonly ILocalizationManager _localization;
|
||||
|
||||
public CoreNotificationTypes(ILocalizationManager localization)
|
||||
{
|
||||
_localization = localization;
|
||||
}
|
||||
|
||||
public IEnumerable<NotificationTypeInfo> GetNotificationTypes()
|
||||
{
|
||||
var knownTypes = new NotificationTypeInfo[]
|
||||
{
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.ApplicationUpdateInstalled)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.InstallationFailed)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.PluginInstalled)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.PluginError)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.PluginUninstalled)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.PluginUpdateInstalled)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.ServerRestartRequired)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.TaskFailed)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.NewLibraryContent)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.AudioPlayback)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.VideoPlayback)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.AudioPlaybackStopped)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.VideoPlaybackStopped)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.UserLockedOut)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.ApplicationUpdateAvailable)
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var type in knownTypes)
|
||||
{
|
||||
Update(type);
|
||||
}
|
||||
|
||||
var systemName = _localization.GetLocalizedString("System");
|
||||
|
||||
return knownTypes.OrderByDescending(i => string.Equals(i.Category, systemName, StringComparison.OrdinalIgnoreCase))
|
||||
.ThenBy(i => i.Category)
|
||||
.ThenBy(i => i.Name);
|
||||
}
|
||||
|
||||
private void Update(NotificationTypeInfo note)
|
||||
{
|
||||
note.Name = _localization.GetLocalizedString("NotificationOption" + note.Type);
|
||||
|
||||
note.IsBasedOnUserEvent = note.Type.IndexOf("Playback", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
|
||||
if (note.Type.IndexOf("Playback", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
note.Category = _localization.GetLocalizedString("User");
|
||||
}
|
||||
else if (note.Type.IndexOf("Plugin", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
note.Category = _localization.GetLocalizedString("Plugin");
|
||||
}
|
||||
else if (note.Type.IndexOf("UserLockedOut", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
note.Category = _localization.GetLocalizedString("User");
|
||||
}
|
||||
else
|
||||
{
|
||||
note.Category = _localization.GetLocalizedString("System");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{2E030C33-6923-4530-9E54-FA29FA6AD1A9}</ProjectGuid>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\SharedVersion.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Code analyzers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -1,23 +0,0 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.Notifications;
|
||||
|
||||
namespace Emby.Notifications
|
||||
{
|
||||
public class NotificationConfigurationFactory : IConfigurationFactory
|
||||
{
|
||||
public IEnumerable<ConfigurationStore> GetConfigurations()
|
||||
{
|
||||
return new ConfigurationStore[]
|
||||
{
|
||||
new ConfigurationStore
|
||||
{
|
||||
Key = "notifications",
|
||||
ConfigurationType = typeof(NotificationOptions)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -1,314 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Events;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Notifications;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Model.Activity;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.Notifications;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates notifications for various system events.
|
||||
/// </summary>
|
||||
public class NotificationEntryPoint : IServerEntryPoint
|
||||
{
|
||||
private readonly ILogger<NotificationEntryPoint> _logger;
|
||||
private readonly IActivityManager _activityManager;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly INotificationManager _notificationManager;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IConfigurationManager _config;
|
||||
|
||||
private readonly object _libraryChangedSyncLock = new object();
|
||||
private readonly List<BaseItem> _itemsAdded = new List<BaseItem>();
|
||||
|
||||
private Timer? _libraryUpdateTimer;
|
||||
|
||||
private string[] _coreNotificationTypes;
|
||||
|
||||
private bool _disposed = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NotificationEntryPoint" /> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="activityManager">The activity manager.</param>
|
||||
/// <param name="localization">The localization manager.</param>
|
||||
/// <param name="notificationManager">The notification manager.</param>
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="appHost">The application host.</param>
|
||||
/// <param name="config">The configuration manager.</param>
|
||||
public NotificationEntryPoint(
|
||||
ILogger<NotificationEntryPoint> logger,
|
||||
IActivityManager activityManager,
|
||||
ILocalizationManager localization,
|
||||
INotificationManager notificationManager,
|
||||
ILibraryManager libraryManager,
|
||||
IServerApplicationHost appHost,
|
||||
IConfigurationManager config)
|
||||
{
|
||||
_logger = logger;
|
||||
_activityManager = activityManager;
|
||||
_localization = localization;
|
||||
_notificationManager = notificationManager;
|
||||
_libraryManager = libraryManager;
|
||||
_appHost = appHost;
|
||||
_config = config;
|
||||
|
||||
_coreNotificationTypes = new CoreNotificationTypes(localization).GetNotificationTypes().Select(i => i.Type).ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task RunAsync()
|
||||
{
|
||||
_libraryManager.ItemAdded += OnLibraryManagerItemAdded;
|
||||
_appHost.HasPendingRestartChanged += OnAppHostHasPendingRestartChanged;
|
||||
_activityManager.EntryCreated += OnActivityManagerEntryCreated;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async void OnAppHostHasPendingRestartChanged(object? sender, EventArgs e)
|
||||
{
|
||||
var type = NotificationType.ServerRestartRequired.ToString();
|
||||
|
||||
var notification = new NotificationRequest
|
||||
{
|
||||
NotificationType = type,
|
||||
Name = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("ServerNameNeedsToBeRestarted"),
|
||||
_appHost.Name)
|
||||
};
|
||||
|
||||
await SendNotification(notification, null).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnActivityManagerEntryCreated(object? sender, GenericEventArgs<ActivityLogEntry> e)
|
||||
{
|
||||
var entry = e.Argument;
|
||||
|
||||
var type = entry.Type;
|
||||
|
||||
if (string.IsNullOrEmpty(type) || !_coreNotificationTypes.Contains(type, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var userId = e.Argument.UserId;
|
||||
|
||||
if (!userId.Equals(default) && !GetOptions().IsEnabledToMonitorUser(type, userId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var notification = new NotificationRequest
|
||||
{
|
||||
NotificationType = type,
|
||||
Name = entry.Name,
|
||||
Description = entry.Overview
|
||||
};
|
||||
|
||||
await SendNotification(notification, null).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private NotificationOptions GetOptions()
|
||||
{
|
||||
return _config.GetConfiguration<NotificationOptions>("notifications");
|
||||
}
|
||||
|
||||
private void OnLibraryManagerItemAdded(object? sender, ItemChangeEventArgs e)
|
||||
{
|
||||
if (!FilterItem(e.Item))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_libraryChangedSyncLock)
|
||||
{
|
||||
if (_libraryUpdateTimer is null)
|
||||
{
|
||||
_libraryUpdateTimer = new Timer(
|
||||
LibraryUpdateTimerCallback,
|
||||
null,
|
||||
5000,
|
||||
Timeout.Infinite);
|
||||
}
|
||||
else
|
||||
{
|
||||
_libraryUpdateTimer.Change(5000, Timeout.Infinite);
|
||||
}
|
||||
|
||||
_itemsAdded.Add(e.Item);
|
||||
}
|
||||
}
|
||||
|
||||
private bool FilterItem(BaseItem item)
|
||||
{
|
||||
if (item.IsFolder)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!item.HasPathProtocol)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item is IItemByName)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return item.SourceType == SourceType.Library;
|
||||
}
|
||||
|
||||
private async void LibraryUpdateTimerCallback(object? state)
|
||||
{
|
||||
List<BaseItem> items;
|
||||
|
||||
lock (_libraryChangedSyncLock)
|
||||
{
|
||||
items = _itemsAdded.ToList();
|
||||
_itemsAdded.Clear();
|
||||
_libraryUpdateTimer!.Dispose(); // Shouldn't be null as it just set off this callback
|
||||
_libraryUpdateTimer = null;
|
||||
}
|
||||
|
||||
if (items.Count > 10)
|
||||
{
|
||||
items = items.GetRange(0, 10);
|
||||
}
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
var notification = new NotificationRequest
|
||||
{
|
||||
NotificationType = NotificationType.NewLibraryContent.ToString(),
|
||||
Name = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("ValueHasBeenAddedToLibrary"),
|
||||
GetItemName(item)),
|
||||
Description = item.Overview
|
||||
};
|
||||
|
||||
await SendNotification(notification, item).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a human readable name for the item.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns>A human readable name for the item.</returns>
|
||||
public static string GetItemName(BaseItem item)
|
||||
{
|
||||
var name = item.Name;
|
||||
if (item is Episode episode)
|
||||
{
|
||||
if (episode.IndexNumber.HasValue)
|
||||
{
|
||||
name = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Ep{0} - {1}",
|
||||
episode.IndexNumber.Value,
|
||||
name);
|
||||
}
|
||||
|
||||
if (episode.ParentIndexNumber.HasValue)
|
||||
{
|
||||
name = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"S{0}, {1}",
|
||||
episode.ParentIndexNumber.Value,
|
||||
name);
|
||||
}
|
||||
}
|
||||
|
||||
if (item is IHasSeries hasSeries)
|
||||
{
|
||||
name = hasSeries.SeriesName + " - " + name;
|
||||
}
|
||||
|
||||
if (item is IHasAlbumArtist hasAlbumArtist)
|
||||
{
|
||||
var artists = hasAlbumArtist.AlbumArtists;
|
||||
|
||||
if (artists.Count > 0)
|
||||
{
|
||||
name = artists[0] + " - " + name;
|
||||
}
|
||||
}
|
||||
else if (item is IHasArtist hasArtist)
|
||||
{
|
||||
var artists = hasArtist.Artists;
|
||||
|
||||
if (artists.Count > 0)
|
||||
{
|
||||
name = artists[0] + " - " + name;
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
private async Task SendNotification(NotificationRequest notification, BaseItem? relatedItem)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _notificationManager.SendNotification(notification, relatedItem, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error sending notification");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and optionally managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_libraryUpdateTimer?.Dispose();
|
||||
}
|
||||
|
||||
_libraryUpdateTimer = null;
|
||||
|
||||
_libraryManager.ItemAdded -= OnLibraryManagerItemAdded;
|
||||
_appHost.HasPendingRestartChanged -= OnAppHostHasPendingRestartChanged;
|
||||
_activityManager.EntryCreated -= OnActivityManagerEntryCreated;
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,224 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Notifications;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Notifications;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// NotificationManager class.
|
||||
/// </summary>
|
||||
public class NotificationManager : INotificationManager
|
||||
{
|
||||
private readonly ILogger<NotificationManager> _logger;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
|
||||
private INotificationService[] _services = Array.Empty<INotificationService>();
|
||||
private INotificationTypeFactory[] _typeFactories = Array.Empty<INotificationTypeFactory>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NotificationManager" /> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="userManager">The user manager.</param>
|
||||
/// <param name="config">The server configuration manager.</param>
|
||||
public NotificationManager(
|
||||
ILogger<NotificationManager> logger,
|
||||
IUserManager userManager,
|
||||
IServerConfigurationManager config)
|
||||
{
|
||||
_logger = logger;
|
||||
_userManager = userManager;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
private NotificationOptions GetConfiguration()
|
||||
{
|
||||
return _config.GetConfiguration<NotificationOptions>("notifications");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SendNotification(NotificationRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return SendNotification(request, null, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SendNotification(NotificationRequest request, BaseItem? relatedItem, CancellationToken cancellationToken)
|
||||
{
|
||||
var notificationType = request.NotificationType;
|
||||
|
||||
var options = string.IsNullOrEmpty(notificationType) ?
|
||||
null :
|
||||
GetConfiguration().GetOptions(notificationType);
|
||||
|
||||
var users = GetUserIds(request, options)
|
||||
.Select(i => _userManager.GetUserById(i))
|
||||
.Where(i => relatedItem is null || relatedItem.IsVisibleStandalone(i))
|
||||
.ToArray();
|
||||
|
||||
var title = request.Name;
|
||||
var description = request.Description;
|
||||
|
||||
var tasks = _services.Where(i => IsEnabled(i, notificationType))
|
||||
.Select(i => SendNotification(request, i, users, title, description, cancellationToken));
|
||||
|
||||
return Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
private Task SendNotification(
|
||||
NotificationRequest request,
|
||||
INotificationService service,
|
||||
IEnumerable<User> users,
|
||||
string title,
|
||||
string description,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
users = users.Where(i => IsEnabledForUser(service, i));
|
||||
|
||||
var tasks = users.Select(i => SendNotification(request, service, title, description, i, cancellationToken));
|
||||
|
||||
return Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
private IEnumerable<Guid> GetUserIds(NotificationRequest request, NotificationOption? options)
|
||||
{
|
||||
if (request.SendToUserMode.HasValue)
|
||||
{
|
||||
switch (request.SendToUserMode.Value)
|
||||
{
|
||||
case SendToUserType.Admins:
|
||||
return _userManager.Users.Where(i => i.HasPermission(PermissionKind.IsAdministrator))
|
||||
.Select(i => i.Id);
|
||||
case SendToUserType.All:
|
||||
return _userManager.UsersIds;
|
||||
case SendToUserType.Custom:
|
||||
return request.UserIds;
|
||||
default:
|
||||
throw new ArgumentException("Unrecognized SendToUserMode: " + request.SendToUserMode.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (options is not null && !string.IsNullOrEmpty(request.NotificationType))
|
||||
{
|
||||
var config = GetConfiguration();
|
||||
|
||||
return _userManager.Users
|
||||
.Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N", CultureInfo.InvariantCulture), i))
|
||||
.Select(i => i.Id);
|
||||
}
|
||||
|
||||
return request.UserIds;
|
||||
}
|
||||
|
||||
private async Task SendNotification(
|
||||
NotificationRequest request,
|
||||
INotificationService service,
|
||||
string title,
|
||||
string description,
|
||||
User user,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var notification = new UserNotification
|
||||
{
|
||||
Date = request.Date,
|
||||
Description = description,
|
||||
Level = request.Level,
|
||||
Name = title,
|
||||
Url = request.Url,
|
||||
User = user
|
||||
};
|
||||
|
||||
_logger.LogDebug("Sending notification via {0} to user {1}", service.Name, user.Username);
|
||||
|
||||
try
|
||||
{
|
||||
await service.SendNotification(notification, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error sending notification to {0}", service.Name);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsEnabledForUser(INotificationService service, User user)
|
||||
{
|
||||
try
|
||||
{
|
||||
return service.IsEnabledForUser(user);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error in IsEnabledForUser");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsEnabled(INotificationService service, string notificationType)
|
||||
{
|
||||
if (string.IsNullOrEmpty(notificationType))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return GetConfiguration().IsServiceEnabled(service.Name, notificationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddParts(IEnumerable<INotificationService> services, IEnumerable<INotificationTypeFactory> notificationTypeFactories)
|
||||
{
|
||||
_services = services.ToArray();
|
||||
_typeFactories = notificationTypeFactories.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<NotificationTypeInfo> GetNotificationTypes()
|
||||
{
|
||||
var list = _typeFactories.Select(i =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return i.GetNotificationTypes().ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error in GetNotificationTypes");
|
||||
return new List<NotificationTypeInfo>();
|
||||
}
|
||||
}).SelectMany(i => i).ToList();
|
||||
|
||||
var config = GetConfiguration();
|
||||
|
||||
foreach (var i in list)
|
||||
{
|
||||
i.Enabled = config.IsEnabled(i.Type);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<NameIdPair> GetNotificationServices()
|
||||
{
|
||||
return _services.Select(i => new NameIdPair
|
||||
{
|
||||
Name = i.Name,
|
||||
Id = i.Name.GetMD5().ToString("N", CultureInfo.InvariantCulture)
|
||||
}).OrderBy(i => i.Name);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Emby.Notifications")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Jellyfin Project")]
|
||||
[assembly: AssemblyProduct("Jellyfin Server")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: NeutralResourcesLanguage("en")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
@ -15,7 +15,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||
<PackageReference Include="TagLibSharp" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
@ -26,13 +26,13 @@
|
||||
|
||||
<!-- Code Analyzers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" />
|
||||
<PackageReference Include="SerilogAnalyzer" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -1,5 +1,3 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
@ -33,15 +31,10 @@ namespace Emby.Server.Implementations.AppBase
|
||||
private ConfigurationStore[] _configurationStores = Array.Empty<ConfigurationStore>();
|
||||
private IConfigurationFactory[] _configurationFactories = Array.Empty<IConfigurationFactory>();
|
||||
|
||||
/// <summary>
|
||||
/// The _configuration loaded.
|
||||
/// </summary>
|
||||
private bool _configurationLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// The _configuration.
|
||||
/// </summary>
|
||||
private BaseApplicationConfiguration _configuration;
|
||||
private BaseApplicationConfiguration? _configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BaseConfigurationManager" /> class.
|
||||
@ -63,17 +56,17 @@ namespace Emby.Server.Implementations.AppBase
|
||||
/// <summary>
|
||||
/// Occurs when [configuration updated].
|
||||
/// </summary>
|
||||
public event EventHandler<EventArgs> ConfigurationUpdated;
|
||||
public event EventHandler<EventArgs>? ConfigurationUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when [configuration updating].
|
||||
/// </summary>
|
||||
public event EventHandler<ConfigurationUpdateEventArgs> NamedConfigurationUpdating;
|
||||
public event EventHandler<ConfigurationUpdateEventArgs>? NamedConfigurationUpdating;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when [named configuration updated].
|
||||
/// </summary>
|
||||
public event EventHandler<ConfigurationUpdateEventArgs> NamedConfigurationUpdated;
|
||||
public event EventHandler<ConfigurationUpdateEventArgs>? NamedConfigurationUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the configuration.
|
||||
@ -107,31 +100,25 @@ namespace Emby.Server.Implementations.AppBase
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_configurationLoaded)
|
||||
if (_configuration is not null)
|
||||
{
|
||||
return _configuration;
|
||||
}
|
||||
|
||||
lock (_configurationSyncLock)
|
||||
{
|
||||
if (_configurationLoaded)
|
||||
if (_configuration is not null)
|
||||
{
|
||||
return _configuration;
|
||||
}
|
||||
|
||||
_configuration = (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer);
|
||||
|
||||
_configurationLoaded = true;
|
||||
|
||||
return _configuration;
|
||||
return _configuration = (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer);
|
||||
}
|
||||
}
|
||||
|
||||
protected set
|
||||
{
|
||||
_configuration = value;
|
||||
|
||||
_configurationLoaded = value is not null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -183,7 +170,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||
Logger.LogInformation("Saving system configuration");
|
||||
var path = CommonApplicationPaths.SystemConfigurationFilePath;
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path) ?? throw new InvalidOperationException("Path can't be a root directory."));
|
||||
|
||||
lock (_configurationSyncLock)
|
||||
{
|
||||
@ -323,25 +310,20 @@ namespace Emby.Server.Implementations.AppBase
|
||||
|
||||
private object LoadConfiguration(string path, Type configurationType)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return Activator.CreateInstance(configurationType);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
return XmlSerializer.DeserializeFromFile(configurationType, path);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return Activator.CreateInstance(configurationType);
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception ex) when (ex is not IOException)
|
||||
{
|
||||
Logger.LogError(ex, "Error loading configuration file: {Path}", path);
|
||||
|
||||
return Activator.CreateInstance(configurationType);
|
||||
}
|
||||
|
||||
return Activator.CreateInstance(configurationType)
|
||||
?? throw new InvalidOperationException("Configuration type can't be Nullable<T>.");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -367,7 +349,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||
_configurations.AddOrUpdate(key, configuration, (_, _) => configuration);
|
||||
|
||||
var path = GetConfigurationFile(key);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path) ?? throw new InvalidOperationException("Path can't be a root directory."));
|
||||
|
||||
lock (_configurationSyncLock)
|
||||
{
|
||||
|
@ -11,7 +11,6 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -19,7 +18,6 @@ using Emby.Dlna;
|
||||
using Emby.Dlna.Main;
|
||||
using Emby.Dlna.Ssdp;
|
||||
using Emby.Naming.Common;
|
||||
using Emby.Notifications;
|
||||
using Emby.Photos;
|
||||
using Emby.Server.Implementations.Channels;
|
||||
using Emby.Server.Implementations.Collections;
|
||||
@ -70,7 +68,6 @@ using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Lyrics;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Notifications;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Controller.Playlists;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
@ -115,15 +112,11 @@ namespace Emby.Server.Implementations
|
||||
/// </summary>
|
||||
public abstract class ApplicationHost : IServerApplicationHost, IAsyncDisposable, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The environment variable prefixes to log at server startup.
|
||||
/// </summary>
|
||||
private static readonly string[] _relevantEnvVarPrefixes = { "JELLYFIN_", "DOTNET_", "ASPNETCORE_" };
|
||||
|
||||
/// <summary>
|
||||
/// The disposable parts.
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<IDisposable, byte> _disposableParts = new();
|
||||
private readonly DeviceId _deviceId;
|
||||
|
||||
private readonly IFileSystem _fileSystemManager;
|
||||
private readonly IConfiguration _startupConfig;
|
||||
@ -132,7 +125,6 @@ namespace Emby.Server.Implementations
|
||||
private readonly IPluginManager _pluginManager;
|
||||
|
||||
private List<Type> _creatingInstances;
|
||||
private IMediaEncoder _mediaEncoder;
|
||||
private ISessionManager _sessionManager;
|
||||
|
||||
/// <summary>
|
||||
@ -141,8 +133,6 @@ namespace Emby.Server.Implementations
|
||||
/// <value>All concrete types.</value>
|
||||
private Type[] _allConcreteTypes;
|
||||
|
||||
private DeviceId _deviceId;
|
||||
|
||||
private bool _disposed = false;
|
||||
|
||||
/// <summary>
|
||||
@ -166,6 +156,7 @@ namespace Emby.Server.Implementations
|
||||
|
||||
Logger = LoggerFactory.CreateLogger<ApplicationHost>();
|
||||
_fileSystemManager.AddShortcutHandler(new MbLinkShortcutHandler(_fileSystemManager));
|
||||
_deviceId = new DeviceId(ApplicationPaths, LoggerFactory);
|
||||
|
||||
ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version;
|
||||
ApplicationVersionString = ApplicationVersion.ToString(3);
|
||||
@ -193,23 +184,9 @@ namespace Emby.Server.Implementations
|
||||
|
||||
public bool CoreStartupHasCompleted { get; private set; }
|
||||
|
||||
public virtual bool CanLaunchWebBrowser
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Environment.UserInteractive)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_startupOptions.IsService)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return OperatingSystem.IsWindows() || OperatingSystem.IsMacOS();
|
||||
}
|
||||
}
|
||||
public virtual bool CanLaunchWebBrowser => Environment.UserInteractive
|
||||
&& !_startupOptions.IsService
|
||||
&& (OperatingSystem.IsWindows() || OperatingSystem.IsMacOS());
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="INetworkManager"/> singleton instance.
|
||||
@ -286,15 +263,7 @@ namespace Emby.Server.Implementations
|
||||
/// <value>The application name.</value>
|
||||
public string ApplicationProductName { get; } = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName;
|
||||
|
||||
public string SystemId
|
||||
{
|
||||
get
|
||||
{
|
||||
_deviceId ??= new DeviceId(ApplicationPaths, LoggerFactory);
|
||||
|
||||
return _deviceId.Value;
|
||||
}
|
||||
}
|
||||
public string SystemId => _deviceId.Value;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name => ApplicationProductName;
|
||||
@ -447,7 +416,7 @@ namespace Emby.Server.Implementations
|
||||
ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated;
|
||||
ConfigurationManager.NamedConfigurationUpdated += OnConfigurationUpdated;
|
||||
|
||||
_mediaEncoder.SetFFmpegPath();
|
||||
Resolve<IMediaEncoder>().SetFFmpegPath();
|
||||
|
||||
Logger.LogInformation("ServerId: {ServerId}", SystemId);
|
||||
|
||||
@ -615,8 +584,6 @@ namespace Emby.Server.Implementations
|
||||
|
||||
serviceCollection.AddSingleton<IUserViewManager, UserViewManager>();
|
||||
|
||||
serviceCollection.AddSingleton<INotificationManager, NotificationManager>();
|
||||
|
||||
serviceCollection.AddSingleton<IDeviceDiscovery, DeviceDiscovery>();
|
||||
|
||||
serviceCollection.AddSingleton<IChapterManager, ChapterManager>();
|
||||
@ -659,7 +626,6 @@ namespace Emby.Server.Implementations
|
||||
var localizationManager = (LocalizationManager)Resolve<ILocalizationManager>();
|
||||
await localizationManager.LoadAll().ConfigureAwait(false);
|
||||
|
||||
_mediaEncoder = Resolve<IMediaEncoder>();
|
||||
_sessionManager = Resolve<ISessionManager>();
|
||||
|
||||
SetStaticProperties();
|
||||
@ -670,36 +636,6 @@ namespace Emby.Server.Implementations
|
||||
FindParts();
|
||||
}
|
||||
|
||||
public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths)
|
||||
{
|
||||
// Distinct these to prevent users from reporting problems that aren't actually problems
|
||||
var commandLineArgs = Environment
|
||||
.GetCommandLineArgs()
|
||||
.Distinct();
|
||||
|
||||
// Get all relevant environment variables
|
||||
var allEnvVars = Environment.GetEnvironmentVariables();
|
||||
var relevantEnvVars = new Dictionary<object, object>();
|
||||
foreach (var key in allEnvVars.Keys)
|
||||
{
|
||||
if (_relevantEnvVarPrefixes.Any(prefix => key.ToString().StartsWith(prefix, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
relevantEnvVars.Add(key, allEnvVars[key]);
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogInformation("Environment Variables: {EnvVars}", relevantEnvVars);
|
||||
logger.LogInformation("Arguments: {Args}", commandLineArgs);
|
||||
logger.LogInformation("Operating system: {OS}", MediaBrowser.Common.System.OperatingSystem.Name);
|
||||
logger.LogInformation("Architecture: {Architecture}", RuntimeInformation.OSArchitecture);
|
||||
logger.LogInformation("64-Bit Process: {Is64Bit}", Environment.Is64BitProcess);
|
||||
logger.LogInformation("User Interactive: {IsUserInteractive}", Environment.UserInteractive);
|
||||
logger.LogInformation("Processor count: {ProcessorCount}", Environment.ProcessorCount);
|
||||
logger.LogInformation("Program data path: {ProgramDataPath}", appPaths.ProgramDataPath);
|
||||
logger.LogInformation("Web resources path: {WebPath}", appPaths.WebPath);
|
||||
logger.LogInformation("Application directory: {ApplicationPath}", appPaths.ProgramSystemPath);
|
||||
}
|
||||
|
||||
private X509Certificate2 GetCertificate(string path, string password)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
@ -786,13 +722,7 @@ namespace Emby.Server.Implementations
|
||||
|
||||
Resolve<ILiveTvManager>().AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>());
|
||||
|
||||
Resolve<ISubtitleManager>().AddParts(GetExports<ISubtitleProvider>());
|
||||
|
||||
Resolve<IChannelManager>().AddParts(GetExports<IChannel>());
|
||||
|
||||
Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>());
|
||||
|
||||
Resolve<INotificationManager>().AddParts(GetExports<INotificationService>(), GetExports<INotificationTypeFactory>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -991,9 +921,6 @@ namespace Emby.Server.Implementations
|
||||
// Local metadata
|
||||
yield return typeof(BoxSetXmlSaver).Assembly;
|
||||
|
||||
// Notifications
|
||||
yield return typeof(NotificationManager).Assembly;
|
||||
|
||||
// Xbmc
|
||||
yield return typeof(ArtistNfoProvider).Assembly;
|
||||
|
||||
@ -1032,14 +959,11 @@ namespace Emby.Server.Implementations
|
||||
ItemsByNamePath = ApplicationPaths.InternalMetadataPath,
|
||||
InternalMetadataPath = ApplicationPaths.InternalMetadataPath,
|
||||
CachePath = ApplicationPaths.CachePath,
|
||||
OperatingSystem = MediaBrowser.Common.System.OperatingSystem.Id.ToString(),
|
||||
OperatingSystemDisplayName = MediaBrowser.Common.System.OperatingSystem.Name,
|
||||
CanLaunchWebBrowser = CanLaunchWebBrowser,
|
||||
TranscodingTempPath = ConfigurationManager.GetTranscodePath(),
|
||||
ServerName = FriendlyName,
|
||||
LocalAddress = GetSmartApiUrl(request),
|
||||
SupportsLibraryMonitor = true,
|
||||
SystemArchitecture = RuntimeInformation.OSArchitecture,
|
||||
PackageName = _startupOptions.PackageName
|
||||
};
|
||||
}
|
||||
@ -1051,7 +975,6 @@ namespace Emby.Server.Implementations
|
||||
Version = ApplicationVersionString,
|
||||
ProductName = ApplicationProductName,
|
||||
Id = SystemId,
|
||||
OperatingSystem = MediaBrowser.Common.System.OperatingSystem.Id.ToString(),
|
||||
ServerName = FriendlyName,
|
||||
LocalAddress = GetSmartApiUrl(request),
|
||||
StartupWizardCompleted = ConfigurationManager.CommonConfiguration.IsStartupWizardCompleted
|
||||
@ -1263,6 +1186,8 @@ namespace Emby.Server.Implementations
|
||||
}
|
||||
}
|
||||
|
||||
if (_sessionManager != null)
|
||||
{
|
||||
// used for closing websockets
|
||||
foreach (var session in _sessionManager.Sessions)
|
||||
{
|
||||
@ -1270,4 +1195,5 @@ namespace Emby.Server.Implementations
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,6 +66,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
/// <param name="userDataManager">The user data manager.</param>
|
||||
/// <param name="providerManager">The provider manager.</param>
|
||||
/// <param name="memoryCache">The memory cache.</param>
|
||||
/// <param name="channels">The channels.</param>
|
||||
public ChannelManager(
|
||||
IUserManager userManager,
|
||||
IDtoService dtoService,
|
||||
@ -75,7 +76,8 @@ namespace Emby.Server.Implementations.Channels
|
||||
IFileSystem fileSystem,
|
||||
IUserDataManager userDataManager,
|
||||
IProviderManager providerManager,
|
||||
IMemoryCache memoryCache)
|
||||
IMemoryCache memoryCache,
|
||||
IEnumerable<IChannel> channels)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_dtoService = dtoService;
|
||||
@ -86,18 +88,13 @@ namespace Emby.Server.Implementations.Channels
|
||||
_userDataManager = userDataManager;
|
||||
_providerManager = providerManager;
|
||||
_memoryCache = memoryCache;
|
||||
}
|
||||
|
||||
internal IChannel[] Channels { get; private set; }
|
||||
|
||||
private static TimeSpan CacheLength => TimeSpan.FromHours(3);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddParts(IEnumerable<IChannel> channels)
|
||||
{
|
||||
Channels = channels.ToArray();
|
||||
}
|
||||
|
||||
internal IChannel[] Channels { get; }
|
||||
|
||||
private static TimeSpan CacheLength => TimeSpan.FromHours(3);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool EnableMediaSourceDisplay(BaseItem item)
|
||||
{
|
||||
|
@ -1,5 +1,3 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
@ -36,7 +34,7 @@ namespace Emby.Server.Implementations.Configuration
|
||||
/// <summary>
|
||||
/// Configuration updating event.
|
||||
/// </summary>
|
||||
public event EventHandler<GenericEventArgs<ServerConfiguration>> ConfigurationUpdating;
|
||||
public event EventHandler<GenericEventArgs<ServerConfiguration>>? ConfigurationUpdating;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the configuration.
|
||||
|
@ -4477,6 +4477,24 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
}
|
||||
|
||||
if (query.IncludeInheritedTags.Length > 0)
|
||||
{
|
||||
var paramName = "@IncludeInheritedTags";
|
||||
if (statement is null)
|
||||
{
|
||||
int index = 0;
|
||||
string includedTags = string.Join(',', query.IncludeInheritedTags.Select(_ => paramName + index++));
|
||||
whereClauses.Add("((select CleanValue from ItemValues where ItemId=Guid and Type=6 and cleanvalue in (" + includedTags + ")) is not null)");
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int index = 0; index < query.IncludeInheritedTags.Length; index++)
|
||||
{
|
||||
statement.TryBind(paramName + index, GetCleanValue(query.IncludeInheritedTags[index]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (query.SeriesStatuses.Length > 0)
|
||||
{
|
||||
var statuses = new List<string>();
|
||||
@ -5440,6 +5458,9 @@ AND Type = @InternalPersonType)");
|
||||
|
||||
list.AddRange(inheritedTags.Select(i => (6, i)));
|
||||
|
||||
// Remove all invalid values.
|
||||
list.RemoveAll(i => string.IsNullOrEmpty(i.Item2));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" />
|
||||
<ProjectReference Include="..\Emby.Notifications\Emby.Notifications.csproj" />
|
||||
<ProjectReference Include="..\Jellyfin.Api\Jellyfin.Api.csproj" />
|
||||
<ProjectReference Include="..\Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||
@ -23,17 +22,17 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DiscUtils.Udf" Version="0.16.13" />
|
||||
<PackageReference Include="Jellyfin.XmlTv" Version="10.8.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.2" />
|
||||
<PackageReference Include="Mono.Nat" Version="3.0.4" />
|
||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
|
||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
|
||||
<PackageReference Include="DotNet.Glob" Version="3.1.3" />
|
||||
<PackageReference Include="DiscUtils.Udf" />
|
||||
<PackageReference Include="Jellyfin.XmlTv" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" />
|
||||
<PackageReference Include="Mono.Nat" />
|
||||
<PackageReference Include="prometheus-net.DotNetRuntime" />
|
||||
<PackageReference Include="SQLitePCL.pretty.netstandard" />
|
||||
<PackageReference Include="DotNet.Glob" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -54,13 +53,13 @@
|
||||
|
||||
<!-- Code Analyzers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
<PackageReference Include="SerilogAnalyzer" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -1,5 +1,3 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
@ -1,5 +1,3 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Common.Configuration;
|
||||
|
@ -1,5 +1,3 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
@ -137,32 +137,33 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
|
||||
private static ProgramInfo GetProgramInfo(XmlTvProgram program, ListingsProviderInfo info)
|
||||
{
|
||||
string episodeTitle = program.Episode?.Title;
|
||||
string episodeTitle = program.Episode.Title;
|
||||
var programCategories = program.Categories.Where(c => !string.IsNullOrWhiteSpace(c)).ToList();
|
||||
|
||||
var programInfo = new ProgramInfo
|
||||
{
|
||||
ChannelId = program.ChannelId,
|
||||
EndDate = program.EndDate.UtcDateTime,
|
||||
EpisodeNumber = program.Episode?.Episode,
|
||||
EpisodeNumber = program.Episode.Episode,
|
||||
EpisodeTitle = episodeTitle,
|
||||
Genres = program.Categories,
|
||||
Genres = programCategories,
|
||||
StartDate = program.StartDate.UtcDateTime,
|
||||
Name = program.Title,
|
||||
Overview = program.Description,
|
||||
ProductionYear = program.CopyrightDate?.Year,
|
||||
SeasonNumber = program.Episode?.Series,
|
||||
IsSeries = program.Episode is not null,
|
||||
SeasonNumber = program.Episode.Series,
|
||||
IsSeries = program.Episode.Series is not null,
|
||||
IsRepeat = program.IsPreviouslyShown && !program.IsNew,
|
||||
IsPremiere = program.Premiere is not null,
|
||||
IsKids = program.Categories.Any(c => info.KidsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
|
||||
IsMovie = program.Categories.Any(c => info.MovieCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
|
||||
IsNews = program.Categories.Any(c => info.NewsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
|
||||
IsSports = program.Categories.Any(c => info.SportsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
|
||||
IsKids = programCategories.Any(c => info.KidsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
|
||||
IsMovie = programCategories.Any(c => info.MovieCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
|
||||
IsNews = programCategories.Any(c => info.NewsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
|
||||
IsSports = programCategories.Any(c => info.SportsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
|
||||
ImageUrl = string.IsNullOrEmpty(program.Icon?.Source) ? null : program.Icon.Source,
|
||||
HasImage = !string.IsNullOrEmpty(program.Icon?.Source),
|
||||
OfficialRating = string.IsNullOrEmpty(program.Rating?.Value) ? null : program.Rating.Value,
|
||||
CommunityRating = program.StarRating,
|
||||
SeriesId = program.Episode is null ? null : program.Title?.GetMD5().ToString("N", CultureInfo.InvariantCulture)
|
||||
SeriesId = program.Episode.Episode is null ? null : program.Title?.GetMD5().ToString("N", CultureInfo.InvariantCulture)
|
||||
};
|
||||
|
||||
if (string.IsNullOrWhiteSpace(program.ProgramId))
|
||||
@ -243,7 +244,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
Id = c.Id,
|
||||
Name = c.DisplayName,
|
||||
ImageUrl = string.IsNullOrEmpty(c.Icon.Source) ? null : c.Icon.Source,
|
||||
ImageUrl = string.IsNullOrEmpty(c.Icon?.Source) ? null : c.Icon.Source,
|
||||
Number = string.IsNullOrWhiteSpace(c.Number) ? c.Id : c.Number
|
||||
}).ToList();
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Extensions;
|
||||
using Jellyfin.Extensions.Json;
|
||||
using Jellyfin.Extensions.Json.Converters;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
@ -58,7 +59,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
_socketFactory = socketFactory;
|
||||
_streamHelper = streamHelper;
|
||||
|
||||
_jsonOptions = JsonDefaults.Options;
|
||||
_jsonOptions = new JsonSerializerOptions(JsonDefaults.Options);
|
||||
_jsonOptions.Converters.Add(new JsonBoolNumberConverter());
|
||||
}
|
||||
|
||||
public string Name => "HD Homerun";
|
||||
|
@ -1,4 +1,127 @@
|
||||
{
|
||||
"Sync": "Сінхранізацыя",
|
||||
"Playlists": "Плэйліст"
|
||||
"Sync": "Сінхранізаваць",
|
||||
"Playlists": "Плэйлісты",
|
||||
"Latest": "Апошні",
|
||||
"LabelIpAddressValue": "IP-адрас: {0}",
|
||||
"ItemAddedWithName": "{0} быў дададзены ў бібліятэку",
|
||||
"MessageApplicationUpdated": "Сервер Jellyfin абноўлены",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Абнаўленне прыкладання ўсталявана",
|
||||
"PluginInstalledWithName": "{0} быў усталяваны",
|
||||
"UserCreatedWithName": "Карыстальнік {0} быў створаны",
|
||||
"Albums": "Альбомы",
|
||||
"Application": "Прыкладанне",
|
||||
"AuthenticationSucceededWithUserName": "{0} паспяхова аўтэнтыфікаваны",
|
||||
"Channels": "Каналы",
|
||||
"ChapterNameValue": "Раздзел {0}",
|
||||
"Collections": "Калекцыі",
|
||||
"Default": "Па змаўчанні",
|
||||
"FailedLoginAttemptWithUserName": "Няўдалая спроба ўваходу з {0}",
|
||||
"Folders": "Папкі",
|
||||
"Favorites": "Абранае",
|
||||
"External": "Знешні",
|
||||
"Genres": "Жанры",
|
||||
"HeaderContinueWatching": "Працягнуць прагляд",
|
||||
"HeaderFavoriteAlbums": "Абраныя альбомы",
|
||||
"HeaderFavoriteEpisodes": "Абраныя серыі",
|
||||
"HeaderFavoriteShows": "Абраныя шоу",
|
||||
"HeaderFavoriteSongs": "Абраныя песні",
|
||||
"HeaderLiveTV": "Прамы эфір",
|
||||
"HeaderAlbumArtists": "Выканаўцы альбома",
|
||||
"LabelRunningTimeValue": "Працягласць: {0}",
|
||||
"HomeVideos": "Хатнія відэа",
|
||||
"ItemRemovedWithName": "{0} быў выдалены з бібліятэкі",
|
||||
"MessageApplicationUpdatedTo": "Сервер Jellyfin абноўлены да {0}",
|
||||
"Movies": "Фільмы",
|
||||
"Music": "Музыка",
|
||||
"MusicVideos": "Музычныя кліпы",
|
||||
"NameInstallFailed": "Устаноўка {0} не атрымалася",
|
||||
"NameSeasonNumber": "Сезон {0}",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Даступна абнаўленне прыкладання",
|
||||
"NotificationOptionPluginInstalled": "Плагін усталяваны",
|
||||
"NotificationOptionPluginUpdateInstalled": "Абнаўленне плагіна усталявана",
|
||||
"NotificationOptionServerRestartRequired": "Патрабуецца перазапуск сервера",
|
||||
"Photos": "Фатаграфіі",
|
||||
"Plugin": "Плагін",
|
||||
"PluginUninstalledWithName": "{0} быў выдалены",
|
||||
"PluginUpdatedWithName": "{0} быў абноўлены",
|
||||
"ProviderValue": "Пастаўшчык: {0}",
|
||||
"Songs": "Песні",
|
||||
"System": "Сістэма",
|
||||
"User": "Карыстальнік",
|
||||
"UserDeletedWithName": "Карыстальнік {0} быў выдалены",
|
||||
"UserDownloadingItemWithValues": "{0} спампоўваецца {1}",
|
||||
"TaskOptimizeDatabase": "Аптымізаваць базу дадзеных",
|
||||
"Artists": "Выканаўцы",
|
||||
"UserOfflineFromDevice": "{0} адключыўся ад {1}",
|
||||
"UserPolicyUpdatedWithName": "Палітыка карыстальніка абноўлена для {0}",
|
||||
"TaskCleanActivityLogDescription": "Выдаляе старэйшыя за зададзены ўзрост запісы ў журнале актыўнасці.",
|
||||
"TaskRefreshChapterImagesDescription": "Стварае мініяцюры для відэа, якія маюць раздзелы.",
|
||||
"TaskCleanLogsDescription": "Выдаляе файлы журналу, якім больш за {0} дзён.",
|
||||
"TaskUpdatePluginsDescription": "Спампоўвае і ўсталёўвае абнаўленні для плагінаў, якія настроены на аўтаматычнае абнаўленне.",
|
||||
"TaskRefreshChannelsDescription": "Абнаўляе інфармацыю аб інтэрнэт-канале.",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Шукае ў інтэрнэце адсутныя субтытры на аснове канфігурацыі метададзеных.",
|
||||
"TaskOptimizeDatabaseDescription": "Ушчыльняе базу дадзеных і скарачае вольную прастору. Выкананне гэтай задачы пасля сканавання бібліятэкі або ўнясення іншых змяненняў, якія прадугледжваюць мадыфікацыю базы дадзеных, можа палепшыць прадукцыйнасць.",
|
||||
"TaskKeyframeExtractor": "Экстрактар ключавых кадраў",
|
||||
"TasksApplicationCategory": "Прыкладанне",
|
||||
"AppDeviceValues": "Прыкладанне: {0}, Прылада: {1}",
|
||||
"Books": "Кнігі",
|
||||
"CameraImageUploadedFrom": "Новая выява камеры была загружана з {0}",
|
||||
"DeviceOfflineWithName": "{0} адключыўся",
|
||||
"DeviceOnlineWithName": "{0} падлучаны",
|
||||
"Forced": "Прымусова",
|
||||
"HeaderRecordingGroups": "Групы запісаў",
|
||||
"HeaderNextUp": "Наступнае",
|
||||
"HeaderFavoriteArtists": "Абраныя выканаўцы",
|
||||
"HearingImpaired": "Са слабым слыхам",
|
||||
"Inherit": "Атрымаць у спадчыну",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Канфігурацыя сервера {0} абноўлена",
|
||||
"MessageServerConfigurationUpdated": "Канфігурацыя сервера абноўлена",
|
||||
"MixedContent": "Змешаны змест",
|
||||
"NameSeasonUnknown": "Невядомы сезон",
|
||||
"NotificationOptionInstallationFailed": "Збой усталёўкі",
|
||||
"NewVersionIsAvailable": "Новая версія сервера Jellyfin даступная для cпампоўкі.",
|
||||
"NotificationOptionCameraImageUploaded": "Выява камеры запампавана",
|
||||
"NotificationOptionAudioPlaybackStopped": "Прайграванне аўдыё спынена",
|
||||
"NotificationOptionAudioPlayback": "Прайграванне аўдыё пачалося",
|
||||
"NotificationOptionNewLibraryContent": "Дададзены новы кантэнт",
|
||||
"NotificationOptionPluginError": "Збой плагіна",
|
||||
"NotificationOptionPluginUninstalled": "Плагін выдалены",
|
||||
"NotificationOptionTaskFailed": "Збой запланаванага задання",
|
||||
"NotificationOptionUserLockedOut": "Карыстальнік заблакіраваны",
|
||||
"NotificationOptionVideoPlayback": "Пачалося прайграванне відэа",
|
||||
"NotificationOptionVideoPlaybackStopped": "Прайграванне відэа спынена",
|
||||
"ScheduledTaskFailedWithName": "{0} не атрымалася",
|
||||
"ScheduledTaskStartedWithName": "{0} пачалося",
|
||||
"ServerNameNeedsToBeRestarted": "{0} трэба перазапусціць",
|
||||
"Shows": "Шоу",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server загружаецца. Калі ласка, паўтарыце спробу крыху пазней.",
|
||||
"SubtitleDownloadFailureFromForItem": "Не атрымалася спампаваць субтытры з {0} для {1}",
|
||||
"TvShows": "ТБ-шоу",
|
||||
"Undefined": "Нявызначана",
|
||||
"UserLockedOutWithName": "Карыстальнік {0} быў заблакіраваны",
|
||||
"UserOnlineFromDevice": "{0} падключаны з {1}",
|
||||
"UserPasswordChangedWithName": "Пароль быў зменены для карыстальніка {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} грае {1} на {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} скончыў прайграванне {1} на {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} быў дададзены ў вашу медыятэку",
|
||||
"ValueSpecialEpisodeName": "Спецэпізод - {0}",
|
||||
"VersionNumber": "Версія {0}",
|
||||
"TasksMaintenanceCategory": "Абслугоўванне",
|
||||
"TasksLibraryCategory": "Медыятэка",
|
||||
"TasksChannelsCategory": "Інтэрнэт-каналы",
|
||||
"TaskCleanActivityLog": "Ачысціць журнал актыўнасці",
|
||||
"TaskCleanCache": "Ачысціць кэш",
|
||||
"TaskCleanCacheDescription": "Выдаляе файлы кэша, якія больш не патрэбныя сістэме.",
|
||||
"TaskRefreshChapterImages": "Выняць выявы раздзелаў",
|
||||
"TaskRefreshLibrary": "Сканіраваць медыятэку",
|
||||
"TaskRefreshLibraryDescription": "Сканіруе вашу медыятэку на наяўнасць новых файлаў і абнаўляе метададзеныя.",
|
||||
"TaskCleanLogs": "Ачысціць часопіс",
|
||||
"TaskRefreshPeople": "Абнавіць людзей",
|
||||
"TaskRefreshPeopleDescription": "Абнаўленне метаданых для акцёраў і рэжысёраў у вашай медыятэцы.",
|
||||
"TaskUpdatePlugins": "Абнавіць плагіны",
|
||||
"TaskCleanTranscode": "Ачысціць каталог перакадзіравання",
|
||||
"TaskCleanTranscodeDescription": "Выдаляе перакадзіраваныя файлы, старэйшыя за адзін дзень.",
|
||||
"TaskRefreshChannels": "Абнавіць каналы",
|
||||
"TaskDownloadMissingSubtitles": "Спампаваць адсутныя субтытры",
|
||||
"TaskKeyframeExtractorDescription": "Выдае ключавыя кадры з відэафайлаў для стварэння больш дакладных спісаў прайгравання HLS. Гэта задача можа працаваць у працягу доўгага часу."
|
||||
}
|
||||
|
@ -122,5 +122,6 @@
|
||||
"TaskOptimizeDatabase": "Optimizar base de datos",
|
||||
"External": "Externo",
|
||||
"TaskKeyframeExtractorDescription": "Extrae Fotogramas Clave de los archivos de vídeo para crear Listas de Reproducción HLS más precisas. Esta tarea puede durar mucho tiempo.",
|
||||
"TaskKeyframeExtractor": "Extractor de Fotogramas Clave"
|
||||
"TaskKeyframeExtractor": "Extractor de Fotogramas Clave",
|
||||
"HearingImpaired": "Discapacidad auditiva"
|
||||
}
|
||||
|
@ -123,5 +123,6 @@
|
||||
"TaskOptimizeDatabaseDescription": "فشرده سازی پایگاه داده و باز کردن فضای آزاد.اجرای این گزینه بعد از اسکن کردن کتابخانه یا تغییرات دیگر که روی پایگاه داده تأثیر میگذارند میتواند کارایی را بهبود ببخشد.",
|
||||
"TaskKeyframeExtractorDescription": "فریم های کلیدی را از فایل های ویدئویی استخراج می کند تا لیست های پخش HLS دقیق تری ایجاد کند. این کار ممکن است برای مدت طولانی اجرا شود.",
|
||||
"TaskKeyframeExtractor": "استخراج کننده فریم کلیدی",
|
||||
"External": "خارجی"
|
||||
"External": "خارجی",
|
||||
"HearingImpaired": "مشکل شنوایی"
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"Albums": "Alben",
|
||||
"AppDeviceValues": "App: {0}, Gerät: {1}",
|
||||
"Application": "Anwendung",
|
||||
"Application": "Applikation",
|
||||
"Artists": "Künstler",
|
||||
"AuthenticationSucceededWithUserName": "{0} hat sich angemeldet",
|
||||
"Books": "Bücher",
|
||||
@ -14,7 +14,7 @@
|
||||
"FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}",
|
||||
"Favorites": "Favoriten",
|
||||
"Folders": "Ordner",
|
||||
"Genres": "Genres",
|
||||
"Genres": "Genre",
|
||||
"HeaderAlbumArtists": "Album-Künstler",
|
||||
"HeaderContinueWatching": "weiter schauen",
|
||||
"HeaderFavoriteAlbums": "Lieblingsalben",
|
||||
@ -49,7 +49,7 @@
|
||||
"NotificationOptionAudioPlayback": "Audiowedergab gstartet",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audiwedergab gstoppt",
|
||||
"NotificationOptionCameraImageUploaded": "Foti ueglade",
|
||||
"NotificationOptionInstallationFailed": "Installationsfehler",
|
||||
"NotificationOptionInstallationFailed": "Installationsfähler",
|
||||
"NotificationOptionNewLibraryContent": "Nöie Inhaut hinzuegfüegt",
|
||||
"NotificationOptionPluginError": "Plugin-Fäuer",
|
||||
"NotificationOptionPluginInstalled": "Plugin installiert",
|
||||
@ -120,5 +120,9 @@
|
||||
"Forced": "Erzwungen",
|
||||
"Default": "Standard",
|
||||
"TaskOptimizeDatabase": "Datenbank optimieren",
|
||||
"External": "Extern"
|
||||
"External": "Extern",
|
||||
"TaskOptimizeDatabaseDescription": "Kompromiert d Datenbank und trennt freie Speicherplatz. Durch die Ufagb cha d Leistig nach em ne Scan vor Bibliothek oder andere Ufgabe verbesseret werde.",
|
||||
"HearingImpaired": "Hörgschädigti",
|
||||
"TaskKeyframeExtractor": "Keyframe-Extraktor",
|
||||
"TaskKeyframeExtractorDescription": "Extrahiert Keyframes us Videodateien zum erstelle vo genauere HLS Playliste. Die Ufgab cha für e langi Zyt laufe."
|
||||
}
|
||||
|
@ -19,5 +19,10 @@
|
||||
"FailedLoginAttemptWithUserName": "Ye failed to get in, try from {0}",
|
||||
"Favorites": "Finest Loot",
|
||||
"ItemRemovedWithName": "{0} was taken from yer treasure",
|
||||
"LabelIpAddressValue": "Ship's coordinates: {0}"
|
||||
"LabelIpAddressValue": "Ship's coordinates: {0}",
|
||||
"Genres": "types o' booty",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Scours the seven seas o' the internet for subtitles that be missin' based on the captain's map o' metadata.",
|
||||
"HeaderAlbumArtists": "Buccaneers o' the musical arts",
|
||||
"HeaderFavoriteAlbums": "Beloved booty o' musical adventures",
|
||||
"HeaderFavoriteArtists": "Treasured scallywags o' the creative seas"
|
||||
}
|
||||
|
@ -66,7 +66,7 @@
|
||||
"PluginInstalledWithName": "{0} installerades",
|
||||
"PluginUninstalledWithName": "{0} avinstallerades",
|
||||
"PluginUpdatedWithName": "{0} uppdaterades",
|
||||
"ProviderValue": "Källa: {0}",
|
||||
"ProviderValue": "Leverantör: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} misslyckades",
|
||||
"ScheduledTaskStartedWithName": "{0} startades",
|
||||
"ServerNameNeedsToBeRestarted": "{0} behöver startas om",
|
||||
|
@ -123,41 +123,64 @@ namespace Emby.Server.Implementations.Plugins
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var file in plugin.DllFiles)
|
||||
{
|
||||
Assembly assembly;
|
||||
try
|
||||
{
|
||||
var assemblyLoadContext = new PluginLoadContext(file);
|
||||
var assemblyLoadContext = new PluginLoadContext(plugin.Path);
|
||||
_assemblyLoadContexts.Add(assemblyLoadContext);
|
||||
|
||||
assembly = assemblyLoadContext.LoadFromAssemblyPath(file);
|
||||
var assemblies = new List<Assembly>(plugin.DllFiles.Count);
|
||||
var loadedAll = true;
|
||||
|
||||
// Load all required types to verify that the plugin will load
|
||||
assembly.GetTypes();
|
||||
foreach (var file in plugin.DllFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
assemblies.Add(assemblyLoadContext.LoadFromAssemblyPath(file));
|
||||
}
|
||||
catch (FileLoadException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to load assembly {Path}. Disabling plugin.", file);
|
||||
_logger.LogError(ex, "Failed to load assembly {Path}. Disabling plugin", file);
|
||||
ChangePluginState(plugin, PluginStatus.Malfunctioned);
|
||||
continue;
|
||||
}
|
||||
catch (SystemException ex) when (ex is TypeLoadException or ReflectionTypeLoadException) // Undocumented exception
|
||||
{
|
||||
_logger.LogError(ex, "Failed to load assembly {Path}. This error occurs when a plugin references an incompatible version of one of the shared libraries. Disabling plugin.", file);
|
||||
ChangePluginState(plugin, PluginStatus.NotSupported);
|
||||
continue;
|
||||
loadedAll = false;
|
||||
break;
|
||||
}
|
||||
#pragma warning disable CA1031 // Do not catch general exception types
|
||||
catch (Exception ex)
|
||||
#pragma warning restore CA1031 // Do not catch general exception types
|
||||
{
|
||||
_logger.LogError(ex, "Failed to load assembly {Path}. Unknown exception was thrown. Disabling plugin.", file);
|
||||
_logger.LogError(ex, "Failed to load assembly {Path}. Unknown exception was thrown. Disabling plugin", file);
|
||||
ChangePluginState(plugin, PluginStatus.Malfunctioned);
|
||||
loadedAll = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!loadedAll)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Loaded assembly {Assembly} from {Path}", assembly.FullName, file);
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Load all required types to verify that the plugin will load
|
||||
assembly.GetTypes();
|
||||
}
|
||||
catch (SystemException ex) when (ex is TypeLoadException or ReflectionTypeLoadException) // Undocumented exception
|
||||
{
|
||||
_logger.LogError(ex, "Failed to load assembly {Path}. This error occurs when a plugin references an incompatible version of one of the shared libraries. Disabling plugin", assembly.Location);
|
||||
ChangePluginState(plugin, PluginStatus.NotSupported);
|
||||
break;
|
||||
}
|
||||
#pragma warning disable CA1031 // Do not catch general exception types
|
||||
catch (Exception ex)
|
||||
#pragma warning restore CA1031 // Do not catch general exception types
|
||||
{
|
||||
_logger.LogError(ex, "Failed to load assembly {Path}. Unknown exception was thrown. Disabling plugin", assembly.Location);
|
||||
ChangePluginState(plugin, PluginStatus.Malfunctioned);
|
||||
break;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Loaded assembly {Assembly} from {Path}", assembly.FullName, assembly.Location);
|
||||
yield return assembly;
|
||||
}
|
||||
}
|
||||
|
@ -2,14 +2,14 @@
|
||||
|
||||
using System;
|
||||
|
||||
namespace Jellyfin.Api.Attributes
|
||||
namespace Jellyfin.Api.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Internal produces image attribute.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class AcceptsFileAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal produces image attribute.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class AcceptsFileAttribute : Attribute
|
||||
{
|
||||
private readonly string[] _contentTypes;
|
||||
|
||||
/// <summary>
|
||||
@ -26,5 +26,4 @@ namespace Jellyfin.Api.Attributes
|
||||
/// </summary>
|
||||
/// <returns>the configured content types.</returns>
|
||||
public string[] ContentTypes => _contentTypes;
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
namespace Jellyfin.Api.Attributes
|
||||
namespace Jellyfin.Api.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Produces file attribute of "image/*".
|
||||
/// </summary>
|
||||
public sealed class AcceptsImageFileAttribute : AcceptsFileAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Produces file attribute of "image/*".
|
||||
/// </summary>
|
||||
public sealed class AcceptsImageFileAttribute : AcceptsFileAttribute
|
||||
{
|
||||
private const string ContentType = "image/*";
|
||||
|
||||
/// <summary>
|
||||
@ -14,5 +14,4 @@
|
||||
: base(ContentType)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,13 +2,13 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
|
||||
namespace Jellyfin.Api.Attributes
|
||||
namespace Jellyfin.Api.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Identifies an action that supports the HTTP GET method.
|
||||
/// </summary>
|
||||
public sealed class HttpSubscribeAttribute : HttpMethodAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifies an action that supports the HTTP GET method.
|
||||
/// </summary>
|
||||
public sealed class HttpSubscribeAttribute : HttpMethodAttribute
|
||||
{
|
||||
private static readonly IEnumerable<string> _supportedMethods = new[] { "SUBSCRIBE" };
|
||||
|
||||
/// <summary>
|
||||
@ -26,5 +26,4 @@ namespace Jellyfin.Api.Attributes
|
||||
public HttpSubscribeAttribute(string template)
|
||||
: base(_supportedMethods, template)
|
||||
=> ArgumentNullException.ThrowIfNull(template);
|
||||
}
|
||||
}
|
||||
|
@ -2,13 +2,13 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
|
||||
namespace Jellyfin.Api.Attributes
|
||||
namespace Jellyfin.Api.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Identifies an action that supports the HTTP GET method.
|
||||
/// </summary>
|
||||
public sealed class HttpUnsubscribeAttribute : HttpMethodAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifies an action that supports the HTTP GET method.
|
||||
/// </summary>
|
||||
public sealed class HttpUnsubscribeAttribute : HttpMethodAttribute
|
||||
{
|
||||
private static readonly IEnumerable<string> _supportedMethods = new[] { "UNSUBSCRIBE" };
|
||||
|
||||
/// <summary>
|
||||
@ -26,5 +26,4 @@ namespace Jellyfin.Api.Attributes
|
||||
public HttpUnsubscribeAttribute(string template)
|
||||
: base(_supportedMethods, template)
|
||||
=> ArgumentNullException.ThrowIfNull(template);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Jellyfin.Api.Attributes
|
||||
namespace Jellyfin.Api.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Attribute to mark a parameter as obsolete.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
public sealed class ParameterObsoleteAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute to mark a parameter as obsolete.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
public sealed class ParameterObsoleteAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
namespace Jellyfin.Api.Attributes
|
||||
namespace Jellyfin.Api.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Produces file attribute of "image/*".
|
||||
/// </summary>
|
||||
public sealed class ProducesAudioFileAttribute : ProducesFileAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Produces file attribute of "image/*".
|
||||
/// </summary>
|
||||
public sealed class ProducesAudioFileAttribute : ProducesFileAttribute
|
||||
{
|
||||
private const string ContentType = "audio/*";
|
||||
|
||||
/// <summary>
|
||||
@ -14,5 +14,4 @@
|
||||
: base(ContentType)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,14 +2,14 @@
|
||||
|
||||
using System;
|
||||
|
||||
namespace Jellyfin.Api.Attributes
|
||||
namespace Jellyfin.Api.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Internal produces image attribute.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class ProducesFileAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal produces image attribute.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class ProducesFileAttribute : Attribute
|
||||
{
|
||||
private readonly string[] _contentTypes;
|
||||
|
||||
/// <summary>
|
||||
@ -26,5 +26,4 @@ namespace Jellyfin.Api.Attributes
|
||||
/// </summary>
|
||||
/// <returns>the configured content types.</returns>
|
||||
public string[] ContentTypes => _contentTypes;
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
namespace Jellyfin.Api.Attributes
|
||||
namespace Jellyfin.Api.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Produces file attribute of "image/*".
|
||||
/// </summary>
|
||||
public sealed class ProducesImageFileAttribute : ProducesFileAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Produces file attribute of "image/*".
|
||||
/// </summary>
|
||||
public sealed class ProducesImageFileAttribute : ProducesFileAttribute
|
||||
{
|
||||
private const string ContentType = "image/*";
|
||||
|
||||
/// <summary>
|
||||
@ -14,5 +14,4 @@
|
||||
: base(ContentType)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
namespace Jellyfin.Api.Attributes
|
||||
namespace Jellyfin.Api.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Produces file attribute of "image/*".
|
||||
/// </summary>
|
||||
public sealed class ProducesPlaylistFileAttribute : ProducesFileAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Produces file attribute of "image/*".
|
||||
/// </summary>
|
||||
public sealed class ProducesPlaylistFileAttribute : ProducesFileAttribute
|
||||
{
|
||||
private const string ContentType = "application/x-mpegURL";
|
||||
|
||||
/// <summary>
|
||||
@ -14,5 +14,4 @@
|
||||
: base(ContentType)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
namespace Jellyfin.Api.Attributes
|
||||
namespace Jellyfin.Api.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Produces file attribute of "video/*".
|
||||
/// </summary>
|
||||
public sealed class ProducesVideoFileAttribute : ProducesFileAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Produces file attribute of "video/*".
|
||||
/// </summary>
|
||||
public sealed class ProducesVideoFileAttribute : ProducesFileAttribute
|
||||
{
|
||||
private const string ContentType = "video/*";
|
||||
|
||||
/// <summary>
|
||||
@ -14,5 +14,4 @@
|
||||
: base(ContentType)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
@ -29,7 +30,7 @@ namespace Jellyfin.Api.Auth.AnonymousLanAccessPolicy
|
||||
/// <inheritdoc />
|
||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AnonymousLanAccessRequirement requirement)
|
||||
{
|
||||
var ip = _httpContextAccessor.HttpContext?.Connection.RemoteIpAddress;
|
||||
var ip = _httpContextAccessor.HttpContext?.GetNormalizedRemoteIp();
|
||||
|
||||
// Loopback will be on LAN, so we can accept null.
|
||||
if (ip is null || _networkManager.IsInLocalNetwork(ip))
|
||||
|
@ -1,113 +0,0 @@
|
||||
using System.Security.Claims;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Jellyfin.Api.Auth
|
||||
{
|
||||
/// <summary>
|
||||
/// Base authorization handler.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of Authorization Requirement.</typeparam>
|
||||
public abstract class BaseAuthorizationHandler<T> : AuthorizationHandler<T>
|
||||
where T : IAuthorizationRequirement
|
||||
{
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly INetworkManager _networkManager;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BaseAuthorizationHandler{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
|
||||
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
|
||||
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
|
||||
protected BaseAuthorizationHandler(
|
||||
IUserManager userManager,
|
||||
INetworkManager networkManager,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_networkManager = networkManager;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate authenticated claims.
|
||||
/// </summary>
|
||||
/// <param name="claimsPrincipal">Request claims.</param>
|
||||
/// <param name="ignoreSchedule">Whether to ignore parental control.</param>
|
||||
/// <param name="localAccessOnly">Whether access is to be allowed locally only.</param>
|
||||
/// <param name="requiredDownloadPermission">Whether validation requires download permission.</param>
|
||||
/// <returns>Validated claim status.</returns>
|
||||
protected bool ValidateClaims(
|
||||
ClaimsPrincipal claimsPrincipal,
|
||||
bool ignoreSchedule = false,
|
||||
bool localAccessOnly = false,
|
||||
bool requiredDownloadPermission = false)
|
||||
{
|
||||
// ApiKey is currently global admin, always allow.
|
||||
var isApiKey = claimsPrincipal.GetIsApiKey();
|
||||
if (isApiKey)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ensure claim has userId.
|
||||
var userId = claimsPrincipal.GetUserId();
|
||||
if (userId.Equals(default))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure userId links to a valid user.
|
||||
var user = _userManager.GetUserById(userId);
|
||||
if (user is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure user is not disabled.
|
||||
if (user.HasPermission(PermissionKind.IsDisabled))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var isInLocalNetwork = _httpContextAccessor.HttpContext is not null
|
||||
&& _networkManager.IsInLocalNetwork(_httpContextAccessor.HttpContext.GetNormalizedRemoteIp());
|
||||
|
||||
// User cannot access remotely and user is remote
|
||||
if (!user.HasPermission(PermissionKind.EnableRemoteAccess) && !isInLocalNetwork)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (localAccessOnly && !isInLocalNetwork)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// User attempting to access out of parental control hours.
|
||||
if (!ignoreSchedule
|
||||
&& !user.HasPermission(PermissionKind.IsAdministrator)
|
||||
&& !user.IsParentalScheduleAllowed())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// User attempting to download without permission.
|
||||
if (requiredDownloadPermission
|
||||
&& !user.HasPermission(PermissionKind.EnableContentDownloading))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,8 @@
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@ -9,8 +13,12 @@ namespace Jellyfin.Api.Auth.DefaultAuthorizationPolicy
|
||||
/// <summary>
|
||||
/// Default authorization handler.
|
||||
/// </summary>
|
||||
public class DefaultAuthorizationHandler : BaseAuthorizationHandler<DefaultAuthorizationRequirement>
|
||||
public class DefaultAuthorizationHandler : AuthorizationHandler<DefaultAuthorizationRequirement>
|
||||
{
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly INetworkManager _networkManager;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DefaultAuthorizationHandler"/> class.
|
||||
/// </summary>
|
||||
@ -21,21 +29,56 @@ namespace Jellyfin.Api.Auth.DefaultAuthorizationPolicy
|
||||
IUserManager userManager,
|
||||
INetworkManager networkManager,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
: base(userManager, networkManager, httpContextAccessor)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_networkManager = networkManager;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DefaultAuthorizationRequirement requirement)
|
||||
{
|
||||
var validated = ValidateClaims(context.User);
|
||||
if (validated)
|
||||
var isApiKey = context.User.GetIsApiKey();
|
||||
var userId = context.User.GetUserId();
|
||||
// This likely only happens during the wizard, so skip the default checks and let any other handlers do it
|
||||
if (!isApiKey && userId.Equals(default))
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
else
|
||||
|
||||
var isInLocalNetwork = _httpContextAccessor.HttpContext is not null
|
||||
&& _networkManager.IsInLocalNetwork(_httpContextAccessor.HttpContext.GetNormalizedRemoteIp());
|
||||
var user = _userManager.GetUserById(userId);
|
||||
if (user is null)
|
||||
{
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
|
||||
// User cannot access remotely and user is remote
|
||||
if (!isInLocalNetwork && !user.HasPermission(PermissionKind.EnableRemoteAccess))
|
||||
{
|
||||
context.Fail();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Admins can do everything
|
||||
if (isApiKey || context.User.IsInRole(UserRoles.Administrator))
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// It's not great to have this check, but parental schedule must usually be honored except in a few rare cases
|
||||
if (requirement.ValidateParentalSchedule && !user.IsParentalScheduleAllowed())
|
||||
{
|
||||
context.Fail();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Only succeed if the requirement isn't a subclass as any subclassed requirement will handle success in its own handler
|
||||
if (requirement.GetType() == typeof(DefaultAuthorizationRequirement))
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
@ -7,5 +7,18 @@ namespace Jellyfin.Api.Auth.DefaultAuthorizationPolicy
|
||||
/// </summary>
|
||||
public class DefaultAuthorizationRequirement : IAuthorizationRequirement
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DefaultAuthorizationRequirement"/> class.
|
||||
/// </summary>
|
||||
/// <param name="validateParentalSchedule">A value indicating whether to validate parental schedule.</param>
|
||||
public DefaultAuthorizationRequirement(bool validateParentalSchedule = true)
|
||||
{
|
||||
ValidateParentalSchedule = validateParentalSchedule;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether to ignore parental schedule.
|
||||
/// </summary>
|
||||
public bool ValidateParentalSchedule { get; }
|
||||
}
|
||||
}
|
||||
|
@ -1,44 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Jellyfin.Api.Auth.DownloadPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Download authorization handler.
|
||||
/// </summary>
|
||||
public class DownloadHandler : BaseAuthorizationHandler<DownloadRequirement>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DownloadHandler"/> class.
|
||||
/// </summary>
|
||||
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
|
||||
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
|
||||
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
|
||||
public DownloadHandler(
|
||||
IUserManager userManager,
|
||||
INetworkManager networkManager,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
: base(userManager, networkManager, httpContextAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DownloadRequirement requirement)
|
||||
{
|
||||
var validated = ValidateClaims(context.User);
|
||||
if (validated)
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Fail();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace Jellyfin.Api.Auth.DownloadPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// The download permission requirement.
|
||||
/// </summary>
|
||||
public class DownloadRequirement : IAuthorizationRequirement
|
||||
{
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Jellyfin.Api.Auth.FirstTimeOrIgnoreParentalControlSetupPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Ignore parental control schedule and allow before startup wizard has been completed.
|
||||
/// </summary>
|
||||
public class FirstTimeOrIgnoreParentalControlSetupHandler : BaseAuthorizationHandler<FirstTimeOrIgnoreParentalControlSetupRequirement>
|
||||
{
|
||||
private readonly IConfigurationManager _configurationManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FirstTimeOrIgnoreParentalControlSetupHandler"/> class.
|
||||
/// </summary>
|
||||
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
|
||||
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
|
||||
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
|
||||
/// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
|
||||
public FirstTimeOrIgnoreParentalControlSetupHandler(
|
||||
IUserManager userManager,
|
||||
INetworkManager networkManager,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
IConfigurationManager configurationManager)
|
||||
: base(userManager, networkManager, httpContextAccessor)
|
||||
{
|
||||
_configurationManager = configurationManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, FirstTimeOrIgnoreParentalControlSetupRequirement requirement)
|
||||
{
|
||||
if (!_configurationManager.CommonConfiguration.IsStartupWizardCompleted)
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var validated = ValidateClaims(context.User, ignoreSchedule: true);
|
||||
if (validated)
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Fail();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace Jellyfin.Api.Auth.FirstTimeOrIgnoreParentalControlSetupPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// First time setup or ignore parental controls requirement.
|
||||
/// </summary>
|
||||
public class FirstTimeOrIgnoreParentalControlSetupRequirement : IAuthorizationRequirement
|
||||
{
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Jellyfin.Api.Auth.FirstTimeSetupOrDefaultPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Authorization handler for requiring first time setup or default privileges.
|
||||
/// </summary>
|
||||
public class FirstTimeSetupOrDefaultHandler : BaseAuthorizationHandler<FirstTimeSetupOrDefaultRequirement>
|
||||
{
|
||||
private readonly IConfigurationManager _configurationManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FirstTimeSetupOrDefaultHandler" /> class.
|
||||
/// </summary>
|
||||
/// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
|
||||
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
|
||||
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
|
||||
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
|
||||
public FirstTimeSetupOrDefaultHandler(
|
||||
IConfigurationManager configurationManager,
|
||||
IUserManager userManager,
|
||||
INetworkManager networkManager,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
: base(userManager, networkManager, httpContextAccessor)
|
||||
{
|
||||
_configurationManager = configurationManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, FirstTimeSetupOrDefaultRequirement requirement)
|
||||
{
|
||||
if (!_configurationManager.CommonConfiguration.IsStartupWizardCompleted)
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var validated = ValidateClaims(context.User);
|
||||
if (validated)
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Fail();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace Jellyfin.Api.Auth.FirstTimeSetupOrDefaultPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// The authorization requirement, requiring incomplete first time setup or default privileges, for the authorization handler.
|
||||
/// </summary>
|
||||
public class FirstTimeSetupOrDefaultRequirement : IAuthorizationRequirement
|
||||
{
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// The authorization requirement, requiring incomplete first time setup or elevated privileges, for the authorization handler.
|
||||
/// </summary>
|
||||
public class FirstTimeSetupOrElevatedRequirement : IAuthorizationRequirement
|
||||
{
|
||||
}
|
||||
}
|
@ -1,39 +1,36 @@
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy
|
||||
namespace Jellyfin.Api.Auth.FirstTimeSetupPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Authorization handler for requiring first time setup or elevated privileges.
|
||||
/// Authorization handler for requiring first time setup or default privileges.
|
||||
/// </summary>
|
||||
public class FirstTimeSetupOrElevatedHandler : BaseAuthorizationHandler<FirstTimeSetupOrElevatedRequirement>
|
||||
public class FirstTimeSetupHandler : AuthorizationHandler<FirstTimeSetupRequirement>
|
||||
{
|
||||
private readonly IConfigurationManager _configurationManager;
|
||||
private readonly IUserManager _userManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FirstTimeSetupOrElevatedHandler" /> class.
|
||||
/// Initializes a new instance of the <see cref="FirstTimeSetupHandler" /> class.
|
||||
/// </summary>
|
||||
/// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
|
||||
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
|
||||
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
|
||||
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
|
||||
public FirstTimeSetupOrElevatedHandler(
|
||||
public FirstTimeSetupHandler(
|
||||
IConfigurationManager configurationManager,
|
||||
IUserManager userManager,
|
||||
INetworkManager networkManager,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
: base(userManager, networkManager, httpContextAccessor)
|
||||
IUserManager userManager)
|
||||
{
|
||||
_configurationManager = configurationManager;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, FirstTimeSetupOrElevatedRequirement requirement)
|
||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, FirstTimeSetupRequirement requirement)
|
||||
{
|
||||
if (!_configurationManager.CommonConfiguration.IsStartupWizardCompleted)
|
||||
{
|
||||
@ -41,14 +38,27 @@ namespace Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var validated = ValidateClaims(context.User);
|
||||
if (validated && context.User.IsInRole(UserRoles.Administrator))
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
else
|
||||
if (requirement.RequireAdmin && !context.User.IsInRole(UserRoles.Administrator))
|
||||
{
|
||||
context.Fail();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
if (!requirement.ValidateParentalSchedule)
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var user = _userManager.GetUserById(context.User.GetUserId());
|
||||
if (user is null)
|
||||
{
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
|
||||
if (user.IsParentalScheduleAllowed())
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
@ -0,0 +1,25 @@
|
||||
using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
|
||||
|
||||
namespace Jellyfin.Api.Auth.FirstTimeSetupPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// The authorization requirement, requiring incomplete first time setup or default privileges, for the authorization handler.
|
||||
/// </summary>
|
||||
public class FirstTimeSetupRequirement : DefaultAuthorizationRequirement
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FirstTimeSetupRequirement"/> class.
|
||||
/// </summary>
|
||||
/// <param name="validateParentalSchedule">A value indicating whether to ignore parental schedule.</param>
|
||||
/// <param name="requireAdmin">A value indicating whether administrator role is required.</param>
|
||||
public FirstTimeSetupRequirement(bool validateParentalSchedule = false, bool requireAdmin = true) : base(validateParentalSchedule)
|
||||
{
|
||||
RequireAdmin = requireAdmin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether administrator role is required.
|
||||
/// </summary>
|
||||
public bool RequireAdmin { get; }
|
||||
}
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Jellyfin.Api.Auth.IgnoreParentalControlPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Escape schedule controls handler.
|
||||
/// </summary>
|
||||
public class IgnoreParentalControlHandler : BaseAuthorizationHandler<IgnoreParentalControlRequirement>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IgnoreParentalControlHandler"/> class.
|
||||
/// </summary>
|
||||
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
|
||||
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
|
||||
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
|
||||
public IgnoreParentalControlHandler(
|
||||
IUserManager userManager,
|
||||
INetworkManager networkManager,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
: base(userManager, networkManager, httpContextAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IgnoreParentalControlRequirement requirement)
|
||||
{
|
||||
var validated = ValidateClaims(context.User, ignoreSchedule: true);
|
||||
if (validated)
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Fail();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace Jellyfin.Api.Auth.IgnoreParentalControlPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Escape schedule controls requirement.
|
||||
/// </summary>
|
||||
public class IgnoreParentalControlRequirement : IAuthorizationRequirement
|
||||
{
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Constants;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
@ -10,27 +10,38 @@ namespace Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy
|
||||
/// <summary>
|
||||
/// Local access or require elevated privileges handler.
|
||||
/// </summary>
|
||||
public class LocalAccessOrRequiresElevationHandler : BaseAuthorizationHandler<LocalAccessOrRequiresElevationRequirement>
|
||||
public class LocalAccessOrRequiresElevationHandler : AuthorizationHandler<LocalAccessOrRequiresElevationRequirement>
|
||||
{
|
||||
private readonly INetworkManager _networkManager;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LocalAccessOrRequiresElevationHandler"/> class.
|
||||
/// </summary>
|
||||
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
|
||||
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
|
||||
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
|
||||
public LocalAccessOrRequiresElevationHandler(
|
||||
IUserManager userManager,
|
||||
INetworkManager networkManager,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
: base(userManager, networkManager, httpContextAccessor)
|
||||
{
|
||||
_networkManager = networkManager;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, LocalAccessOrRequiresElevationRequirement requirement)
|
||||
{
|
||||
var validated = ValidateClaims(context.User, localAccessOnly: true);
|
||||
if (validated || context.User.IsInRole(UserRoles.Administrator))
|
||||
var ip = _httpContextAccessor.HttpContext?.GetNormalizedRemoteIp();
|
||||
|
||||
// Loopback will be on LAN, so we can accept null.
|
||||
if (ip is null || _networkManager.IsInLocalNetwork(ip))
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
if (context.User.IsInRole(UserRoles.Administrator))
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy
|
||||
{
|
||||
|
@ -1,44 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Jellyfin.Api.Auth.LocalAccessPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Local access handler.
|
||||
/// </summary>
|
||||
public class LocalAccessHandler : BaseAuthorizationHandler<LocalAccessRequirement>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LocalAccessHandler"/> class.
|
||||
/// </summary>
|
||||
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
|
||||
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
|
||||
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
|
||||
public LocalAccessHandler(
|
||||
IUserManager userManager,
|
||||
INetworkManager networkManager,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
: base(userManager, networkManager, httpContextAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, LocalAccessRequirement requirement)
|
||||
{
|
||||
var validated = ValidateClaims(context.User, localAccessOnly: true);
|
||||
if (validated)
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Fail();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace Jellyfin.Api.Auth.LocalAccessPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// The local access authorization requirement.
|
||||
/// </summary>
|
||||
public class LocalAccessRequirement : IAuthorizationRequirement
|
||||
{
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Constants;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Jellyfin.Api.Auth.RequiresElevationPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Authorization handler for requiring elevated privileges.
|
||||
/// </summary>
|
||||
public class RequiresElevationHandler : BaseAuthorizationHandler<RequiresElevationRequirement>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequiresElevationHandler"/> class.
|
||||
/// </summary>
|
||||
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
|
||||
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
|
||||
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
|
||||
public RequiresElevationHandler(
|
||||
IUserManager userManager,
|
||||
INetworkManager networkManager,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
: base(userManager, networkManager, httpContextAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RequiresElevationRequirement requirement)
|
||||
{
|
||||
var validated = ValidateClaims(context.User);
|
||||
if (validated && context.User.IsInRole(UserRoles.Administrator))
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Fail();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace Jellyfin.Api.Auth.RequiresElevationPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// The authorization requirement for requiring elevated privileges in the authorization handler.
|
||||
/// </summary>
|
||||
public class RequiresElevationRequirement : IAuthorizationRequirement
|
||||
{
|
||||
}
|
||||
}
|
@ -1,19 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.SyncPlay;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Jellyfin.Api.Auth.SyncPlayAccessPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Default authorization handler.
|
||||
/// </summary>
|
||||
public class SyncPlayAccessHandler : BaseAuthorizationHandler<SyncPlayAccessRequirement>
|
||||
public class SyncPlayAccessHandler : AuthorizationHandler<SyncPlayAccessRequirement>
|
||||
{
|
||||
private readonly ISyncPlayManager _syncPlayManager;
|
||||
private readonly IUserManager _userManager;
|
||||
@ -23,14 +21,9 @@ namespace Jellyfin.Api.Auth.SyncPlayAccessPolicy
|
||||
/// </summary>
|
||||
/// <param name="syncPlayManager">Instance of the <see cref="ISyncPlayManager"/> interface.</param>
|
||||
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
|
||||
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
|
||||
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
|
||||
public SyncPlayAccessHandler(
|
||||
ISyncPlayManager syncPlayManager,
|
||||
IUserManager userManager,
|
||||
INetworkManager networkManager,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
: base(userManager, networkManager, httpContextAccessor)
|
||||
IUserManager userManager)
|
||||
{
|
||||
_syncPlayManager = syncPlayManager;
|
||||
_userManager = userManager;
|
||||
@ -39,27 +32,20 @@ namespace Jellyfin.Api.Auth.SyncPlayAccessPolicy
|
||||
/// <inheritdoc />
|
||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, SyncPlayAccessRequirement requirement)
|
||||
{
|
||||
if (!ValidateClaims(context.User))
|
||||
{
|
||||
context.Fail();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var userId = context.User.GetUserId();
|
||||
var user = _userManager.GetUserById(userId);
|
||||
if (user is null)
|
||||
{
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
|
||||
if (requirement.RequiredAccess == SyncPlayAccessRequirementType.HasAccess)
|
||||
{
|
||||
if (user.SyncPlayAccess == SyncPlayUserAccessType.CreateAndJoinGroups
|
||||
|| user.SyncPlayAccess == SyncPlayUserAccessType.JoinGroups
|
||||
if (user.SyncPlayAccess is SyncPlayUserAccessType.CreateAndJoinGroups or SyncPlayUserAccessType.JoinGroups
|
||||
|| _syncPlayManager.IsUserActive(userId))
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Fail();
|
||||
}
|
||||
}
|
||||
else if (requirement.RequiredAccess == SyncPlayAccessRequirementType.CreateGroup)
|
||||
{
|
||||
@ -67,10 +53,6 @@ namespace Jellyfin.Api.Auth.SyncPlayAccessPolicy
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Fail();
|
||||
}
|
||||
}
|
||||
else if (requirement.RequiredAccess == SyncPlayAccessRequirementType.JoinGroup)
|
||||
{
|
||||
@ -79,10 +61,6 @@ namespace Jellyfin.Api.Auth.SyncPlayAccessPolicy
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Fail();
|
||||
}
|
||||
}
|
||||
else if (requirement.RequiredAccess == SyncPlayAccessRequirementType.IsInGroup)
|
||||
{
|
||||
@ -90,14 +68,6 @@ namespace Jellyfin.Api.Auth.SyncPlayAccessPolicy
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Fail();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Fail();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
@ -1,12 +1,12 @@
|
||||
using Jellyfin.Data.Enums;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
|
||||
using Jellyfin.Data.Enums;
|
||||
|
||||
namespace Jellyfin.Api.Auth.SyncPlayAccessPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// The default authorization requirement.
|
||||
/// </summary>
|
||||
public class SyncPlayAccessRequirement : IAuthorizationRequirement
|
||||
public class SyncPlayAccessRequirement : DefaultAuthorizationRequirement
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SyncPlayAccessRequirement"/> class.
|
||||
|
@ -0,0 +1,42 @@
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace Jellyfin.Api.Auth.UserPermissionPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// User permission authorization handler.
|
||||
/// </summary>
|
||||
public class UserPermissionHandler : AuthorizationHandler<UserPermissionRequirement>
|
||||
{
|
||||
private readonly IUserManager _userManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UserPermissionHandler"/> class.
|
||||
/// </summary>
|
||||
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
|
||||
public UserPermissionHandler(IUserManager userManager)
|
||||
{
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, UserPermissionRequirement requirement)
|
||||
{
|
||||
var user = _userManager.GetUserById(context.User.GetUserId());
|
||||
if (user is null)
|
||||
{
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
|
||||
if (user.HasPermission(requirement.RequiredPermission))
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
|
||||
using Jellyfin.Data.Enums;
|
||||
|
||||
namespace Jellyfin.Api.Auth.UserPermissionPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// The user permission requirement.
|
||||
/// </summary>
|
||||
public class UserPermissionRequirement : DefaultAuthorizationRequirement
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UserPermissionRequirement"/> class.
|
||||
/// </summary>
|
||||
/// <param name="requiredPermission">The required <see cref="PermissionKind"/>.</param>
|
||||
/// <param name="validateParentalSchedule">Whether to validate the user's parental schedule.</param>
|
||||
public UserPermissionRequirement(PermissionKind requiredPermission, bool validateParentalSchedule = true) : base(validateParentalSchedule)
|
||||
{
|
||||
RequiredPermission = requiredPermission;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the required user permission.
|
||||
/// </summary>
|
||||
public PermissionKind RequiredPermission { get; }
|
||||
}
|
||||
}
|
@ -4,19 +4,19 @@ using Jellyfin.Api.Results;
|
||||
using Jellyfin.Extensions.Json;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Jellyfin.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// Base api controller for the API setting a default route.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
[Produces(
|
||||
namespace Jellyfin.Api;
|
||||
|
||||
/// <summary>
|
||||
/// Base api controller for the API setting a default route.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
[Produces(
|
||||
MediaTypeNames.Application.Json,
|
||||
JsonDefaults.CamelCaseMediaType,
|
||||
JsonDefaults.PascalCaseMediaType)]
|
||||
public class BaseJellyfinApiController : ControllerBase
|
||||
{
|
||||
public class BaseJellyfinApiController : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new <see cref="OkResult{T}"/>.
|
||||
/// </summary>
|
||||
@ -34,5 +34,4 @@ namespace Jellyfin.Api
|
||||
/// <returns>The <see cref="ActionResult{T}"/>.</returns>
|
||||
protected ActionResult<T> Ok<T>(T value)
|
||||
=> new OkResult<T>(value);
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
namespace Jellyfin.Api.Constants
|
||||
namespace Jellyfin.Api.Constants;
|
||||
|
||||
/// <summary>
|
||||
/// Authentication schemes for user authentication in the API.
|
||||
/// </summary>
|
||||
public static class AuthenticationSchemes
|
||||
{
|
||||
/// <summary>
|
||||
/// Authentication schemes for user authentication in the API.
|
||||
/// </summary>
|
||||
public static class AuthenticationSchemes
|
||||
{
|
||||
/// <summary>
|
||||
/// Scheme name for the custom legacy authentication.
|
||||
/// </summary>
|
||||
public const string CustomAuthentication = "CustomAuthentication";
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
namespace Jellyfin.Api.Constants
|
||||
namespace Jellyfin.Api.Constants;
|
||||
|
||||
/// <summary>
|
||||
/// Internal claim types for authorization.
|
||||
/// </summary>
|
||||
public static class InternalClaimTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal claim types for authorization.
|
||||
/// </summary>
|
||||
public static class InternalClaimTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// User Id.
|
||||
/// </summary>
|
||||
@ -39,5 +39,4 @@
|
||||
/// Is Api Key.
|
||||
/// </summary>
|
||||
public const string IsApiKey = "Jellyfin-IsApiKey";
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,10 @@
|
||||
namespace Jellyfin.Api.Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// Policies for the API authorization.
|
||||
/// </summary>
|
||||
public static class Policies
|
||||
{
|
||||
/// <summary>
|
||||
/// Policy name for default authorization.
|
||||
/// </summary>
|
||||
public const string DefaultAuthorization = "DefaultAuthorization";
|
||||
namespace Jellyfin.Api.Constants;
|
||||
|
||||
/// <summary>
|
||||
/// Policies for the API authorization.
|
||||
/// </summary>
|
||||
public static class Policies
|
||||
{
|
||||
/// <summary>
|
||||
/// Policy name for requiring first time setup or elevated privileges.
|
||||
/// </summary>
|
||||
@ -74,5 +69,19 @@ namespace Jellyfin.Api.Constants
|
||||
/// Policy name for accessing a SyncPlay group.
|
||||
/// </summary>
|
||||
public const string SyncPlayIsInGroup = "SyncPlayIsInGroup";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Policy name for accessing collection management.
|
||||
/// </summary>
|
||||
public const string CollectionManagement = "CollectionManagement";
|
||||
|
||||
/// <summary>
|
||||
/// Policy name for accessing LiveTV.
|
||||
/// </summary>
|
||||
public const string LiveTvAccess = "LiveTvAccess";
|
||||
|
||||
/// <summary>
|
||||
/// Policy name for managing LiveTV.
|
||||
/// </summary>
|
||||
public const string LiveTvManagement = "LiveTvManagement";
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
namespace Jellyfin.Api.Constants
|
||||
namespace Jellyfin.Api.Constants;
|
||||
|
||||
/// <summary>
|
||||
/// Constants for user roles used in the authentication and authorization for the API.
|
||||
/// </summary>
|
||||
public static class UserRoles
|
||||
{
|
||||
/// <summary>
|
||||
/// Constants for user roles used in the authentication and authorization for the API.
|
||||
/// </summary>
|
||||
public static class UserRoles
|
||||
{
|
||||
/// <summary>
|
||||
/// Guest user.
|
||||
/// </summary>
|
||||
@ -19,5 +19,4 @@ namespace Jellyfin.Api.Constants
|
||||
/// Administrator user with elevated privileges.
|
||||
/// </summary>
|
||||
public const string Administrator = "Administrator";
|
||||
}
|
||||
}
|
||||
|
@ -8,15 +8,15 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Activity log controller.
|
||||
/// </summary>
|
||||
[Route("System/ActivityLog")]
|
||||
[Authorize(Policy = Policies.RequiresElevation)]
|
||||
public class ActivityLogController : BaseJellyfinApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// Activity log controller.
|
||||
/// </summary>
|
||||
[Route("System/ActivityLog")]
|
||||
[Authorize(Policy = Policies.RequiresElevation)]
|
||||
public class ActivityLogController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly IActivityManager _activityManager;
|
||||
|
||||
/// <summary>
|
||||
@ -53,5 +53,4 @@ namespace Jellyfin.Api.Controllers
|
||||
HasUserId = hasUserId
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,14 +7,14 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Authentication controller.
|
||||
/// </summary>
|
||||
[Route("Auth")]
|
||||
public class ApiKeyController : BaseJellyfinApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// Authentication controller.
|
||||
/// </summary>
|
||||
[Route("Auth")]
|
||||
public class ApiKeyController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly IAuthenticationManager _authenticationManager;
|
||||
|
||||
/// <summary>
|
||||
@ -72,5 +72,4 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
@ -17,15 +16,15 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// The artists controller.
|
||||
/// </summary>
|
||||
[Route("Artists")]
|
||||
[Authorize]
|
||||
public class ArtistsController : BaseJellyfinApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// The artists controller.
|
||||
/// </summary>
|
||||
[Route("Artists")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
public class ArtistsController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IDtoService _dtoService;
|
||||
@ -476,5 +475,4 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
return _dtoService.GetBaseItemDto(item, dtoOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,14 +10,14 @@ using MediaBrowser.Model.Dlna;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// The audio controller.
|
||||
/// </summary>
|
||||
// TODO: In order to authenticate this in the future, Dlna playback will require updating
|
||||
public class AudioController : BaseJellyfinApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// The audio controller.
|
||||
/// </summary>
|
||||
// TODO: In order to authenticate this in the future, Dlna playback will require updating
|
||||
public class AudioController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly AudioHelper _audioHelper;
|
||||
|
||||
private readonly TranscodingJobType _transcodingJobType = TranscodingJobType.Progressive;
|
||||
@ -360,5 +360,4 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
return await _audioHelper.GetAudioStream(_transcodingJobType, streamingRequest).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,13 +4,13 @@ using MediaBrowser.Model.Branding;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Branding controller.
|
||||
/// </summary>
|
||||
public class BrandingController : BaseJellyfinApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// Branding controller.
|
||||
/// </summary>
|
||||
public class BrandingController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||
|
||||
/// <summary>
|
||||
@ -53,5 +53,4 @@ namespace Jellyfin.Api.Controllers
|
||||
var options = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding");
|
||||
return options.CustomCss ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Enums;
|
||||
@ -18,14 +17,14 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Channels Controller.
|
||||
/// </summary>
|
||||
[Authorize]
|
||||
public class ChannelsController : BaseJellyfinApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// Channels Controller.
|
||||
/// </summary>
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
public class ChannelsController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly IChannelManager _channelManager;
|
||||
private readonly IUserManager _userManager;
|
||||
|
||||
@ -247,5 +246,4 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
return await _channelManager.GetLatestChannelItems(query, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
using System.Net.Mime;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Attributes;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.Models.ClientLogDtos;
|
||||
using MediaBrowser.Controller.ClientEvent;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
@ -11,14 +9,14 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Client log controller.
|
||||
/// </summary>
|
||||
[Authorize]
|
||||
public class ClientLogController : BaseJellyfinApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// Client log controller.
|
||||
/// </summary>
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
public class ClientLogController : BaseJellyfinApiController
|
||||
{
|
||||
private const int MaxDocumentSize = 1_000_000;
|
||||
private readonly IClientEventLogger _clientEventLogger;
|
||||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||
@ -77,5 +75,4 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
return (clientName, clientVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,15 +11,15 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// The collection controller.
|
||||
/// </summary>
|
||||
[Route("Collections")]
|
||||
[Authorize(Policy = Policies.CollectionManagement)]
|
||||
public class CollectionController : BaseJellyfinApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// The collection controller.
|
||||
/// </summary>
|
||||
[Route("Collections")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
public class CollectionController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly ICollectionManager _collectionManager;
|
||||
private readonly IDtoService _dtoService;
|
||||
|
||||
@ -107,5 +107,4 @@ namespace Jellyfin.Api.Controllers
|
||||
await _collectionManager.RemoveFromCollectionAsync(collectionId, ids).ConfigureAwait(false);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,15 +13,15 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration Controller.
|
||||
/// </summary>
|
||||
[Route("System")]
|
||||
[Authorize]
|
||||
public class ConfigurationController : BaseJellyfinApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration Controller.
|
||||
/// </summary>
|
||||
[Route("System")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
public class ConfigurationController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly IServerConfigurationManager _configurationManager;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
|
||||
@ -132,5 +132,4 @@ namespace Jellyfin.Api.Controllers
|
||||
_mediaEncoder.UpdateEncoderPath(mediaEncoderPath.Path, mediaEncoderPath.PathType);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Mime;
|
||||
using Jellyfin.Api.Attributes;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Models;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Model.Net;
|
||||
@ -14,14 +13,14 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// The dashboard controller.
|
||||
/// </summary>
|
||||
[Route("")]
|
||||
public class DashboardController : BaseJellyfinApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// The dashboard controller.
|
||||
/// </summary>
|
||||
[Route("")]
|
||||
public class DashboardController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly ILogger<DashboardController> _logger;
|
||||
private readonly IPluginManager _pluginManager;
|
||||
|
||||
@ -48,7 +47,7 @@ namespace Jellyfin.Api.Controllers
|
||||
[HttpGet("web/ConfigurationPages")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
[Authorize]
|
||||
public ActionResult<IEnumerable<ConfigurationPageInfo>> GetConfigurationPages(
|
||||
[FromQuery] bool? enableInMainMenu)
|
||||
{
|
||||
@ -112,5 +111,4 @@ namespace Jellyfin.Api.Controllers
|
||||
{
|
||||
return _pluginManager.Plugins.SelectMany(GetPluginPages);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,14 +13,14 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Devices Controller.
|
||||
/// </summary>
|
||||
[Authorize(Policy = Policies.RequiresElevation)]
|
||||
public class DevicesController : BaseJellyfinApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// Devices Controller.
|
||||
/// </summary>
|
||||
[Authorize(Policy = Policies.RequiresElevation)]
|
||||
public class DevicesController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly IDeviceManager _deviceManager;
|
||||
private readonly ISessionManager _sessionManager;
|
||||
|
||||
@ -137,5 +137,4 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
@ -14,14 +13,14 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Display Preferences Controller.
|
||||
/// </summary>
|
||||
[Authorize]
|
||||
public class DisplayPreferencesController : BaseJellyfinApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// Display Preferences Controller.
|
||||
/// </summary>
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
public class DisplayPreferencesController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly IDisplayPreferencesManager _displayPreferencesManager;
|
||||
private readonly ILogger<DisplayPreferencesController> _logger;
|
||||
|
||||
@ -210,5 +209,4 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,14 +7,14 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Dlna Controller.
|
||||
/// </summary>
|
||||
[Authorize(Policy = Policies.RequiresElevation)]
|
||||
public class DlnaController : BaseJellyfinApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// Dlna Controller.
|
||||
/// </summary>
|
||||
[Authorize(Policy = Policies.RequiresElevation)]
|
||||
public class DlnaController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly IDlnaManager _dlnaManager;
|
||||
|
||||
/// <summary>
|
||||
@ -129,5 +129,4 @@ namespace Jellyfin.Api.Controllers
|
||||
_dlnaManager.UpdateProfile(profileId, deviceProfile);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,16 +14,16 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Dlna Server Controller.
|
||||
/// </summary>
|
||||
[Route("Dlna")]
|
||||
[DlnaEnabled]
|
||||
[Authorize(Policy = Policies.AnonymousLanAccessPolicy)]
|
||||
public class DlnaServerController : BaseJellyfinApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// Dlna Server Controller.
|
||||
/// </summary>
|
||||
[Route("Dlna")]
|
||||
[DlnaEnabled]
|
||||
[Authorize(Policy = Policies.AnonymousLanAccessPolicy)]
|
||||
public class DlnaServerController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly IDlnaManager _dlnaManager;
|
||||
private readonly IContentDirectory _contentDirectory;
|
||||
private readonly IConnectionManager _connectionManager;
|
||||
@ -320,5 +320,4 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
return dlnaEventManager.CancelEventSubscription(subscriptionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Attributes;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.Models.PlaybackDtos;
|
||||
using Jellyfin.Api.Models.StreamingDtos;
|
||||
@ -30,15 +29,15 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Dynamic hls controller.
|
||||
/// </summary>
|
||||
[Route("")]
|
||||
[Authorize]
|
||||
public class DynamicHlsController : BaseJellyfinApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// Dynamic hls controller.
|
||||
/// </summary>
|
||||
[Route("")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
public class DynamicHlsController : BaseJellyfinApiController
|
||||
{
|
||||
private const string DefaultVodEncoderPreset = "veryfast";
|
||||
private const string DefaultEventEncoderPreset = "superfast";
|
||||
private const TranscodingJobType TranscodingJobType = MediaBrowser.Controller.MediaEncoding.TranscodingJobType.Hls;
|
||||
@ -2051,5 +2050,4 @@ namespace Jellyfin.Api.Controllers
|
||||
_logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,14 +12,14 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Environment Controller.
|
||||
/// </summary>
|
||||
[Authorize(Policy = Policies.FirstTimeSetupOrElevated)]
|
||||
public class EnvironmentController : BaseJellyfinApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// Environment Controller.
|
||||
/// </summary>
|
||||
[Authorize(Policy = Policies.FirstTimeSetupOrElevated)]
|
||||
public class EnvironmentController : BaseJellyfinApiController
|
||||
{
|
||||
private const char UncSeparator = '\\';
|
||||
private const string UncStartPrefix = @"\\";
|
||||
|
||||
@ -193,5 +193,4 @@ namespace Jellyfin.Api.Controllers
|
||||
{
|
||||
return new DefaultDirectoryBrowserInfoDto();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
@ -12,15 +11,15 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Filters controller.
|
||||
/// </summary>
|
||||
[Route("")]
|
||||
[Authorize]
|
||||
public class FilterController : BaseJellyfinApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// Filters controller.
|
||||
/// </summary>
|
||||
[Route("")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
public class FilterController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IUserManager _userManager;
|
||||
|
||||
@ -212,5 +211,4 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
return filters;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
@ -18,14 +17,14 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Genre = MediaBrowser.Controller.Entities.Genre;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// The genres controller.
|
||||
/// </summary>
|
||||
[Authorize]
|
||||
public class GenresController : BaseJellyfinApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// The genres controller.
|
||||
/// </summary>
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
public class GenresController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IDtoService _dtoService;
|
||||
@ -207,5 +206,4 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Attributes;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
@ -15,14 +14,14 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// The hls segment controller.
|
||||
/// </summary>
|
||||
[Route("")]
|
||||
public class HlsSegmentController : BaseJellyfinApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// The hls segment controller.
|
||||
/// </summary>
|
||||
[Route("")]
|
||||
public class HlsSegmentController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||
private readonly TranscodingJobHelper _transcodingJobHelper;
|
||||
@ -80,7 +79,7 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <response code="200">Hls video playlist returned.</response>
|
||||
/// <returns>A <see cref="FileStreamResult"/> containing the playlist.</returns>
|
||||
[HttpGet("Videos/{itemId}/hls/{playlistId}/stream.m3u8")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
[Authorize]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesPlaylistFile]
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
|
||||
@ -106,7 +105,7 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <response code="204">Encoding stopped successfully.</response>
|
||||
/// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
|
||||
[HttpDelete("Videos/ActiveEncodings")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
[Authorize]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public ActionResult StopEncodingProcess(
|
||||
[FromQuery, Required] string deviceId,
|
||||
@ -188,5 +187,4 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
return FileStreamResponseHelpers.GetStaticFileResult(path, MimeTypes.GetMimeType(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,14 +30,14 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Image controller.
|
||||
/// </summary>
|
||||
[Route("")]
|
||||
public class ImageController : BaseJellyfinApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// Image controller.
|
||||
/// </summary>
|
||||
[Route("")]
|
||||
public class ImageController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IProviderManager _providerManager;
|
||||
@ -88,7 +88,7 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <response code="403">User does not have permission to delete the image.</response>
|
||||
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
||||
[HttpPost("Users/{userId}/Images/{imageType}")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
[Authorize]
|
||||
[AcceptsImageFile]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
@ -99,12 +99,17 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromRoute, Required] ImageType imageType,
|
||||
[FromQuery] int? index = null)
|
||||
{
|
||||
var user = _userManager.GetUserById(userId);
|
||||
if (user is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (!RequestHelpers.AssertCanUpdateUser(_userManager, HttpContext.User, userId, true))
|
||||
{
|
||||
return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the image.");
|
||||
}
|
||||
|
||||
var user = _userManager.GetUserById(userId);
|
||||
var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false);
|
||||
await using (memoryStream.ConfigureAwait(false))
|
||||
{
|
||||
@ -137,7 +142,7 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <response code="403">User does not have permission to delete the image.</response>
|
||||
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
||||
[HttpPost("Users/{userId}/Images/{imageType}/{index}")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
[Authorize]
|
||||
[AcceptsImageFile]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
@ -148,12 +153,17 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromRoute, Required] ImageType imageType,
|
||||
[FromRoute] int index)
|
||||
{
|
||||
var user = _userManager.GetUserById(userId);
|
||||
if (user is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (!RequestHelpers.AssertCanUpdateUser(_userManager, HttpContext.User, userId, true))
|
||||
{
|
||||
return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the image.");
|
||||
}
|
||||
|
||||
var user = _userManager.GetUserById(userId);
|
||||
var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false);
|
||||
await using (memoryStream.ConfigureAwait(false))
|
||||
{
|
||||
@ -186,7 +196,7 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <response code="403">User does not have permission to delete the image.</response>
|
||||
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
||||
[HttpDelete("Users/{userId}/Images/{imageType}")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
[Authorize]
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
@ -230,7 +240,7 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <response code="403">User does not have permission to delete the image.</response>
|
||||
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
||||
[HttpDelete("Users/{userId}/Images/{imageType}/{index}")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
[Authorize]
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
@ -432,7 +442,7 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <response code="404">Item not found.</response>
|
||||
/// <returns>The list of image infos on success, or <see cref="NotFoundResult"/> if item not found.</returns>
|
||||
[HttpGet("Items/{itemId}/Images")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
[Authorize]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<IEnumerable<ImageInfo>>> GetItemImageInfos([FromRoute, Required] Guid itemId)
|
||||
@ -505,7 +515,6 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
|
||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||
/// <param name="format">Optional. The <see cref="ImageFormat"/> of the returned image.</param>
|
||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||
/// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param>
|
||||
/// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param>
|
||||
/// <param name="blur">Optional. Blur image.</param>
|
||||
@ -536,7 +545,6 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] bool? cropWhitespace,
|
||||
[FromQuery] ImageFormat? format,
|
||||
[FromQuery] bool? addPlayedIndicator,
|
||||
[FromQuery] double? percentPlayed,
|
||||
[FromQuery] int? unplayedCount,
|
||||
[FromQuery] int? blur,
|
||||
@ -565,7 +573,6 @@ namespace Jellyfin.Api.Controllers
|
||||
quality,
|
||||
fillWidth,
|
||||
fillHeight,
|
||||
addPlayedIndicator,
|
||||
blur,
|
||||
backgroundColor,
|
||||
foregroundLayer,
|
||||
@ -589,7 +596,6 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
|
||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||
/// <param name="format">Optional. The <see cref="ImageFormat"/> of the returned image.</param>
|
||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||
/// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param>
|
||||
/// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param>
|
||||
/// <param name="blur">Optional. Blur image.</param>
|
||||
@ -620,7 +626,6 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] bool? cropWhitespace,
|
||||
[FromQuery] ImageFormat? format,
|
||||
[FromQuery] bool? addPlayedIndicator,
|
||||
[FromQuery] double? percentPlayed,
|
||||
[FromQuery] int? unplayedCount,
|
||||
[FromQuery] int? blur,
|
||||
@ -648,7 +653,6 @@ namespace Jellyfin.Api.Controllers
|
||||
quality,
|
||||
fillWidth,
|
||||
fillHeight,
|
||||
addPlayedIndicator,
|
||||
blur,
|
||||
backgroundColor,
|
||||
foregroundLayer,
|
||||
@ -671,7 +675,6 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
|
||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||
/// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param>
|
||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||
/// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param>
|
||||
/// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param>
|
||||
/// <param name="blur">Optional. Blur image.</param>
|
||||
@ -702,7 +705,6 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromRoute, Required] string tag,
|
||||
[FromQuery, ParameterObsolete] bool? cropWhitespace,
|
||||
[FromRoute, Required] ImageFormat format,
|
||||
[FromQuery] bool? addPlayedIndicator,
|
||||
[FromRoute, Required] double percentPlayed,
|
||||
[FromRoute, Required] int unplayedCount,
|
||||
[FromQuery] int? blur,
|
||||
@ -731,7 +733,6 @@ namespace Jellyfin.Api.Controllers
|
||||
quality,
|
||||
fillWidth,
|
||||
fillHeight,
|
||||
addPlayedIndicator,
|
||||
blur,
|
||||
backgroundColor,
|
||||
foregroundLayer,
|
||||
@ -756,7 +757,6 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <param name="fillWidth">Width of box to fill.</param>
|
||||
/// <param name="fillHeight">Height of box to fill.</param>
|
||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||
/// <param name="blur">Optional. Blur image.</param>
|
||||
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
|
||||
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
|
||||
@ -787,7 +787,6 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromQuery] int? fillWidth,
|
||||
[FromQuery] int? fillHeight,
|
||||
[FromQuery, ParameterObsolete] bool? cropWhitespace,
|
||||
[FromQuery] bool? addPlayedIndicator,
|
||||
[FromQuery] int? blur,
|
||||
[FromQuery] string? backgroundColor,
|
||||
[FromQuery] string? foregroundLayer,
|
||||
@ -814,7 +813,6 @@ namespace Jellyfin.Api.Controllers
|
||||
quality,
|
||||
fillWidth,
|
||||
fillHeight,
|
||||
addPlayedIndicator,
|
||||
blur,
|
||||
backgroundColor,
|
||||
foregroundLayer,
|
||||
@ -839,7 +837,6 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <param name="fillWidth">Width of box to fill.</param>
|
||||
/// <param name="fillHeight">Height of box to fill.</param>
|
||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||
/// <param name="blur">Optional. Blur image.</param>
|
||||
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
|
||||
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
|
||||
@ -870,7 +867,6 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromQuery] int? fillWidth,
|
||||
[FromQuery] int? fillHeight,
|
||||
[FromQuery, ParameterObsolete] bool? cropWhitespace,
|
||||
[FromQuery] bool? addPlayedIndicator,
|
||||
[FromQuery] int? blur,
|
||||
[FromQuery] string? backgroundColor,
|
||||
[FromQuery] string? foregroundLayer,
|
||||
@ -897,7 +893,6 @@ namespace Jellyfin.Api.Controllers
|
||||
quality,
|
||||
fillWidth,
|
||||
fillHeight,
|
||||
addPlayedIndicator,
|
||||
blur,
|
||||
backgroundColor,
|
||||
foregroundLayer,
|
||||
@ -923,7 +918,6 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <param name="fillWidth">Width of box to fill.</param>
|
||||
/// <param name="fillHeight">Height of box to fill.</param>
|
||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||
/// <param name="blur">Optional. Blur image.</param>
|
||||
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
|
||||
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
|
||||
@ -954,7 +948,6 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromQuery] int? fillWidth,
|
||||
[FromQuery] int? fillHeight,
|
||||
[FromQuery, ParameterObsolete] bool? cropWhitespace,
|
||||
[FromQuery] bool? addPlayedIndicator,
|
||||
[FromQuery] int? blur,
|
||||
[FromQuery] string? backgroundColor,
|
||||
[FromQuery] string? foregroundLayer)
|
||||
@ -980,7 +973,6 @@ namespace Jellyfin.Api.Controllers
|
||||
quality,
|
||||
fillWidth,
|
||||
fillHeight,
|
||||
addPlayedIndicator,
|
||||
blur,
|
||||
backgroundColor,
|
||||
foregroundLayer,
|
||||
@ -1005,7 +997,6 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <param name="fillWidth">Width of box to fill.</param>
|
||||
/// <param name="fillHeight">Height of box to fill.</param>
|
||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||
/// <param name="blur">Optional. Blur image.</param>
|
||||
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
|
||||
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
|
||||
@ -1036,7 +1027,6 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromQuery] int? fillWidth,
|
||||
[FromQuery] int? fillHeight,
|
||||
[FromQuery, ParameterObsolete] bool? cropWhitespace,
|
||||
[FromQuery] bool? addPlayedIndicator,
|
||||
[FromQuery] int? blur,
|
||||
[FromQuery] string? backgroundColor,
|
||||
[FromQuery] string? foregroundLayer,
|
||||
@ -1063,7 +1053,6 @@ namespace Jellyfin.Api.Controllers
|
||||
quality,
|
||||
fillWidth,
|
||||
fillHeight,
|
||||
addPlayedIndicator,
|
||||
blur,
|
||||
backgroundColor,
|
||||
foregroundLayer,
|
||||
@ -1089,7 +1078,6 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <param name="fillWidth">Width of box to fill.</param>
|
||||
/// <param name="fillHeight">Height of box to fill.</param>
|
||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||
/// <param name="blur">Optional. Blur image.</param>
|
||||
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
|
||||
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
|
||||
@ -1120,7 +1108,6 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromQuery] int? fillWidth,
|
||||
[FromQuery] int? fillHeight,
|
||||
[FromQuery, ParameterObsolete] bool? cropWhitespace,
|
||||
[FromQuery] bool? addPlayedIndicator,
|
||||
[FromQuery] int? blur,
|
||||
[FromQuery] string? backgroundColor,
|
||||
[FromQuery] string? foregroundLayer)
|
||||
@ -1146,7 +1133,6 @@ namespace Jellyfin.Api.Controllers
|
||||
quality,
|
||||
fillWidth,
|
||||
fillHeight,
|
||||
addPlayedIndicator,
|
||||
blur,
|
||||
backgroundColor,
|
||||
foregroundLayer,
|
||||
@ -1171,7 +1157,6 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <param name="fillWidth">Width of box to fill.</param>
|
||||
/// <param name="fillHeight">Height of box to fill.</param>
|
||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||
/// <param name="blur">Optional. Blur image.</param>
|
||||
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
|
||||
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
|
||||
@ -1202,7 +1187,6 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromQuery] int? fillWidth,
|
||||
[FromQuery] int? fillHeight,
|
||||
[FromQuery, ParameterObsolete] bool? cropWhitespace,
|
||||
[FromQuery] bool? addPlayedIndicator,
|
||||
[FromQuery] int? blur,
|
||||
[FromQuery] string? backgroundColor,
|
||||
[FromQuery] string? foregroundLayer,
|
||||
@ -1229,7 +1213,6 @@ namespace Jellyfin.Api.Controllers
|
||||
quality,
|
||||
fillWidth,
|
||||
fillHeight,
|
||||
addPlayedIndicator,
|
||||
blur,
|
||||
backgroundColor,
|
||||
foregroundLayer,
|
||||
@ -1255,7 +1238,6 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <param name="fillWidth">Width of box to fill.</param>
|
||||
/// <param name="fillHeight">Height of box to fill.</param>
|
||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||
/// <param name="blur">Optional. Blur image.</param>
|
||||
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
|
||||
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
|
||||
@ -1286,7 +1268,6 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromQuery] int? fillWidth,
|
||||
[FromQuery] int? fillHeight,
|
||||
[FromQuery, ParameterObsolete] bool? cropWhitespace,
|
||||
[FromQuery] bool? addPlayedIndicator,
|
||||
[FromQuery] int? blur,
|
||||
[FromQuery] string? backgroundColor,
|
||||
[FromQuery] string? foregroundLayer)
|
||||
@ -1312,7 +1293,6 @@ namespace Jellyfin.Api.Controllers
|
||||
quality,
|
||||
fillWidth,
|
||||
fillHeight,
|
||||
addPlayedIndicator,
|
||||
blur,
|
||||
backgroundColor,
|
||||
foregroundLayer,
|
||||
@ -1337,7 +1317,6 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <param name="fillWidth">Width of box to fill.</param>
|
||||
/// <param name="fillHeight">Height of box to fill.</param>
|
||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||
/// <param name="blur">Optional. Blur image.</param>
|
||||
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
|
||||
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
|
||||
@ -1368,7 +1347,6 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromQuery] int? fillWidth,
|
||||
[FromQuery] int? fillHeight,
|
||||
[FromQuery, ParameterObsolete] bool? cropWhitespace,
|
||||
[FromQuery] bool? addPlayedIndicator,
|
||||
[FromQuery] int? blur,
|
||||
[FromQuery] string? backgroundColor,
|
||||
[FromQuery] string? foregroundLayer,
|
||||
@ -1395,7 +1373,6 @@ namespace Jellyfin.Api.Controllers
|
||||
quality,
|
||||
fillWidth,
|
||||
fillHeight,
|
||||
addPlayedIndicator,
|
||||
blur,
|
||||
backgroundColor,
|
||||
foregroundLayer,
|
||||
@ -1421,7 +1398,6 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <param name="fillWidth">Width of box to fill.</param>
|
||||
/// <param name="fillHeight">Height of box to fill.</param>
|
||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||
/// <param name="blur">Optional. Blur image.</param>
|
||||
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
|
||||
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
|
||||
@ -1452,7 +1428,6 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromQuery] int? fillWidth,
|
||||
[FromQuery] int? fillHeight,
|
||||
[FromQuery, ParameterObsolete] bool? cropWhitespace,
|
||||
[FromQuery] bool? addPlayedIndicator,
|
||||
[FromQuery] int? blur,
|
||||
[FromQuery] string? backgroundColor,
|
||||
[FromQuery] string? foregroundLayer)
|
||||
@ -1478,7 +1453,6 @@ namespace Jellyfin.Api.Controllers
|
||||
quality,
|
||||
fillWidth,
|
||||
fillHeight,
|
||||
addPlayedIndicator,
|
||||
blur,
|
||||
backgroundColor,
|
||||
foregroundLayer,
|
||||
@ -1503,7 +1477,6 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <param name="fillWidth">Width of box to fill.</param>
|
||||
/// <param name="fillHeight">Height of box to fill.</param>
|
||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||
/// <param name="blur">Optional. Blur image.</param>
|
||||
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
|
||||
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
|
||||
@ -1534,7 +1507,6 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromQuery] int? fillWidth,
|
||||
[FromQuery] int? fillHeight,
|
||||
[FromQuery, ParameterObsolete] bool? cropWhitespace,
|
||||
[FromQuery] bool? addPlayedIndicator,
|
||||
[FromQuery] int? blur,
|
||||
[FromQuery] string? backgroundColor,
|
||||
[FromQuery] string? foregroundLayer,
|
||||
@ -1578,7 +1550,6 @@ namespace Jellyfin.Api.Controllers
|
||||
quality,
|
||||
fillWidth,
|
||||
fillHeight,
|
||||
addPlayedIndicator,
|
||||
blur,
|
||||
backgroundColor,
|
||||
foregroundLayer,
|
||||
@ -1605,7 +1576,6 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <param name="fillWidth">Width of box to fill.</param>
|
||||
/// <param name="fillHeight">Height of box to fill.</param>
|
||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||
/// <param name="blur">Optional. Blur image.</param>
|
||||
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
|
||||
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
|
||||
@ -1636,7 +1606,6 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromQuery] int? fillWidth,
|
||||
[FromQuery] int? fillHeight,
|
||||
[FromQuery, ParameterObsolete] bool? cropWhitespace,
|
||||
[FromQuery] bool? addPlayedIndicator,
|
||||
[FromQuery] int? blur,
|
||||
[FromQuery] string? backgroundColor,
|
||||
[FromQuery] string? foregroundLayer)
|
||||
@ -1679,7 +1648,6 @@ namespace Jellyfin.Api.Controllers
|
||||
quality,
|
||||
fillWidth,
|
||||
fillHeight,
|
||||
addPlayedIndicator,
|
||||
blur,
|
||||
backgroundColor,
|
||||
foregroundLayer,
|
||||
@ -1924,7 +1892,6 @@ namespace Jellyfin.Api.Controllers
|
||||
int? quality,
|
||||
int? fillWidth,
|
||||
int? fillHeight,
|
||||
bool? addPlayedIndicator,
|
||||
int? blur,
|
||||
string? backgroundColor,
|
||||
string? foregroundLayer,
|
||||
@ -1940,7 +1907,6 @@ namespace Jellyfin.Api.Controllers
|
||||
else if (percentPlayed.Value >= 100)
|
||||
{
|
||||
percentPlayed = null;
|
||||
addPlayedIndicator = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1997,7 +1963,6 @@ namespace Jellyfin.Api.Controllers
|
||||
FillWidth = fillWidth,
|
||||
Quality = quality ?? 100,
|
||||
Width = width,
|
||||
AddPlayedIndicator = addPlayedIndicator ?? false,
|
||||
PercentPlayed = percentPlayed ?? 0,
|
||||
UnplayedCount = unplayedCount,
|
||||
Blur = blur,
|
||||
@ -2141,5 +2106,4 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
return PhysicalFile(imagePath, imageContentType ?? MediaTypeNames.Text.Plain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
@ -16,15 +15,15 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// The instant mix controller.
|
||||
/// </summary>
|
||||
[Route("")]
|
||||
[Authorize]
|
||||
public class InstantMixController : BaseJellyfinApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// The instant mix controller.
|
||||
/// </summary>
|
||||
[Route("")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
public class InstantMixController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IDtoService _dtoService;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
@ -357,5 +356,4 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Constants;
|
||||
@ -18,15 +17,15 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Item lookup controller.
|
||||
/// </summary>
|
||||
[Route("")]
|
||||
[Authorize]
|
||||
public class ItemLookupController : BaseJellyfinApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// Item lookup controller.
|
||||
/// </summary>
|
||||
[Route("")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
public class ItemLookupController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly IProviderManager _providerManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
@ -270,5 +269,4 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,15 +9,15 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Item Refresh Controller.
|
||||
/// </summary>
|
||||
[Route("Items")]
|
||||
[Authorize(Policy = Policies.RequiresElevation)]
|
||||
public class ItemRefreshController : BaseJellyfinApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// Item Refresh Controller.
|
||||
/// </summary>
|
||||
[Route("Items")]
|
||||
[Authorize(Policy = Policies.RequiresElevation)]
|
||||
public class ItemRefreshController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IProviderManager _providerManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
@ -82,5 +82,4 @@ namespace Jellyfin.Api.Controllers
|
||||
_providerManager.QueueRefresh(item.Id, refreshOptions, RefreshPriority.High);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,15 +20,15 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
namespace Jellyfin.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Item update controller.
|
||||
/// </summary>
|
||||
[Route("")]
|
||||
[Authorize(Policy = Policies.RequiresElevation)]
|
||||
public class ItemUpdateController : BaseJellyfinApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// Item update controller.
|
||||
/// </summary>
|
||||
[Route("")]
|
||||
[Authorize(Policy = Policies.RequiresElevation)]
|
||||
public class ItemUpdateController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IProviderManager _providerManager;
|
||||
private readonly ILocalizationManager _localizationManager;
|
||||
@ -445,5 +445,4 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user