Merge branch 'master' into network-rewrite

pull/8147/head
Shadowghost 2 years ago
commit 3a91c37283

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

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

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

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

@ -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/)

@ -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)
{
@ -1271,3 +1196,4 @@ 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;
Channels = channels.ToArray();
}
internal IChannel[] Channels { get; private set; }
internal IChannel[] Channels { get; }
private static TimeSpan CacheLength => TimeSpan.FromHours(3);
/// <inheritdoc />
public void AddParts(IEnumerable<IChannel> channels)
{
Channels = channels.ToArray();
}
/// <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;
}
var assemblyLoadContext = new PluginLoadContext(plugin.Path);
_assemblyLoadContexts.Add(assemblyLoadContext);
var assemblies = new List<Assembly>(plugin.DllFiles.Count);
var loadedAll = true;
foreach (var file in plugin.DllFiles)
{
Assembly assembly;
try
{
var assemblyLoadContext = new PluginLoadContext(file);
_assemblyLoadContexts.Add(assemblyLoadContext);
assembly = assemblyLoadContext.LoadFromAssemblyPath(file);
// Load all required types to verify that the plugin will load
assembly.GetTypes();
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);
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);
ChangePluginState(plugin, PluginStatus.Malfunctioned);
loadedAll = false;
break;
}
}
if (!loadedAll)
{
continue;
}
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.", file);
_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);
continue;
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", assembly.Location);
ChangePluginState(plugin, PluginStatus.Malfunctioned);
continue;
break;
}
_logger.LogInformation("Loaded assembly {Assembly} from {Path}", assembly.FullName, file);
_logger.LogInformation("Loaded assembly {Assembly} from {Path}", assembly.FullName, assembly.Location);
yield return assembly;
}
}

@ -2,8 +2,8 @@
using System;
namespace Jellyfin.Api.Attributes
{
namespace Jellyfin.Api.Attributes;
/// <summary>
/// Internal produces image attribute.
/// </summary>
@ -27,4 +27,3 @@ namespace Jellyfin.Api.Attributes
/// <returns>the configured content types.</returns>
public string[] ContentTypes => _contentTypes;
}
}

@ -1,5 +1,5 @@
namespace Jellyfin.Api.Attributes
{
namespace Jellyfin.Api.Attributes;
/// <summary>
/// Produces file attribute of "image/*".
/// </summary>
@ -15,4 +15,3 @@
{
}
}
}

@ -2,8 +2,8 @@ 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>
@ -27,4 +27,3 @@ namespace Jellyfin.Api.Attributes
: base(_supportedMethods, template)
=> ArgumentNullException.ThrowIfNull(template);
}
}

@ -2,8 +2,8 @@ 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>
@ -27,4 +27,3 @@ namespace Jellyfin.Api.Attributes
: base(_supportedMethods, template)
=> ArgumentNullException.ThrowIfNull(template);
}
}

@ -1,7 +1,7 @@
using System;
namespace Jellyfin.Api.Attributes
{
namespace Jellyfin.Api.Attributes;
/// <summary>
/// Attribute to mark a parameter as obsolete.
/// </summary>
@ -9,4 +9,3 @@ namespace Jellyfin.Api.Attributes
public sealed class ParameterObsoleteAttribute : Attribute
{
}
}

@ -1,5 +1,5 @@
namespace Jellyfin.Api.Attributes
{
namespace Jellyfin.Api.Attributes;
/// <summary>
/// Produces file attribute of "image/*".
/// </summary>
@ -15,4 +15,3 @@
{
}
}
}

@ -2,8 +2,8 @@
using System;
namespace Jellyfin.Api.Attributes
{
namespace Jellyfin.Api.Attributes;
/// <summary>
/// Internal produces image attribute.
/// </summary>
@ -27,4 +27,3 @@ namespace Jellyfin.Api.Attributes
/// <returns>the configured content types.</returns>
public string[] ContentTypes => _contentTypes;
}
}

@ -1,5 +1,5 @@
namespace Jellyfin.Api.Attributes
{
namespace Jellyfin.Api.Attributes;
/// <summary>
/// Produces file attribute of "image/*".
/// </summary>
@ -15,4 +15,3 @@
{
}
}
}

@ -1,5 +1,5 @@
namespace Jellyfin.Api.Attributes
{
namespace Jellyfin.Api.Attributes;
/// <summary>
/// Produces file attribute of "image/*".
/// </summary>
@ -15,4 +15,3 @@
{
}
}
}

@ -1,5 +1,5 @@
namespace Jellyfin.Api.Attributes
{
namespace Jellyfin.Api.Attributes;
/// <summary>
/// Produces file attribute of "video/*".
/// </summary>
@ -15,4 +15,3 @@
{
}
}
}

@ -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))
{
return Task.CompletedTask;
}
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;
}
else
// 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))
if (requirement.RequireAdmin && !context.User.IsInRole(UserRoles.Administrator))
{
context.Fail();
return Task.CompletedTask;
}
if (!requirement.ValidateParentalSchedule)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
else
var user = _userManager.GetUserById(context.User.GetUserId());
if (user is null)
{
context.Fail();
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,8 +4,8 @@ using Jellyfin.Api.Results;
using Jellyfin.Extensions.Json;
using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Api
{
namespace Jellyfin.Api;
/// <summary>
/// Base api controller for the API setting a default route.
/// </summary>
@ -35,4 +35,3 @@ namespace Jellyfin.Api
protected ActionResult<T> Ok<T>(T value)
=> new OkResult<T>(value);
}
}

@ -1,5 +1,5 @@
namespace Jellyfin.Api.Constants
{
namespace Jellyfin.Api.Constants;
/// <summary>
/// Authentication schemes for user authentication in the API.
/// </summary>
@ -10,4 +10,3 @@ namespace Jellyfin.Api.Constants
/// </summary>
public const string CustomAuthentication = "CustomAuthentication";
}
}

@ -1,5 +1,5 @@
namespace Jellyfin.Api.Constants
{
namespace Jellyfin.Api.Constants;
/// <summary>
/// Internal claim types for authorization.
/// </summary>
@ -40,4 +40,3 @@
/// </summary>
public const string IsApiKey = "Jellyfin-IsApiKey";
}
}

@ -1,15 +1,10 @@
namespace Jellyfin.Api.Constants
{
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";
/// <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,5 +1,5 @@
namespace Jellyfin.Api.Constants
{
namespace Jellyfin.Api.Constants;
/// <summary>
/// Constants for user roles used in the authentication and authorization for the API.
/// </summary>
@ -20,4 +20,3 @@ namespace Jellyfin.Api.Constants
/// </summary>
public const string Administrator = "Administrator";
}
}

@ -8,8 +8,8 @@ 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>
@ -54,4 +54,3 @@ namespace Jellyfin.Api.Controllers
}).ConfigureAwait(false);
}
}
}

@ -7,8 +7,8 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Api.Controllers
{
namespace Jellyfin.Api.Controllers;
/// <summary>
/// Authentication controller.
/// </summary>
@ -73,4 +73,3 @@ 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,13 +16,13 @@ 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(Policy = Policies.DefaultAuthorization)]
[Authorize]
public class ArtistsController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
@ -477,4 +476,3 @@ namespace Jellyfin.Api.Controllers
return _dtoService.GetBaseItemDto(item, dtoOptions);
}
}
}

@ -10,8 +10,8 @@ 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>
@ -361,4 +361,3 @@ namespace Jellyfin.Api.Controllers
return await _audioHelper.GetAudioStream(_transcodingJobType, streamingRequest).ConfigureAwait(false);
}
}
}

@ -4,8 +4,8 @@ using MediaBrowser.Model.Branding;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Api.Controllers
{
namespace Jellyfin.Api.Controllers;
/// <summary>
/// Branding controller.
/// </summary>
@ -54,4 +54,3 @@ namespace Jellyfin.Api.Controllers
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,12 +17,12 @@ 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(Policy = Policies.DefaultAuthorization)]
[Authorize]
public class ChannelsController : BaseJellyfinApiController
{
private readonly IChannelManager _channelManager;
@ -248,4 +247,3 @@ 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,12 +9,12 @@ 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(Policy = Policies.DefaultAuthorization)]
[Authorize]
public class ClientLogController : BaseJellyfinApiController
{
private const int MaxDocumentSize = 1_000_000;
@ -78,4 +76,3 @@ namespace Jellyfin.Api.Controllers
return (clientName, clientVersion);
}
}
}

@ -11,13 +11,13 @@ 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.DefaultAuthorization)]
[Authorize(Policy = Policies.CollectionManagement)]
public class CollectionController : BaseJellyfinApiController
{
private readonly ICollectionManager _collectionManager;
@ -108,4 +108,3 @@ namespace Jellyfin.Api.Controllers
return NoContent();
}
}
}

@ -13,13 +13,13 @@ 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(Policy = Policies.DefaultAuthorization)]
[Authorize]
public class ConfigurationController : BaseJellyfinApiController
{
private readonly IServerConfigurationManager _configurationManager;
@ -133,4 +133,3 @@ namespace Jellyfin.Api.Controllers
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,8 +13,8 @@ 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>
@ -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)
{
@ -113,4 +112,3 @@ namespace Jellyfin.Api.Controllers
return _pluginManager.Plugins.SelectMany(GetPluginPages);
}
}
}

@ -13,8 +13,8 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Api.Controllers
{
namespace Jellyfin.Api.Controllers;
/// <summary>
/// Devices Controller.
/// </summary>
@ -138,4 +138,3 @@ 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,12 +13,12 @@ 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(Policy = Policies.DefaultAuthorization)]
[Authorize]
public class DisplayPreferencesController : BaseJellyfinApiController
{
private readonly IDisplayPreferencesManager _displayPreferencesManager;
@ -211,4 +210,3 @@ namespace Jellyfin.Api.Controllers
return NoContent();
}
}
}

@ -7,8 +7,8 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Api.Controllers
{
namespace Jellyfin.Api.Controllers;
/// <summary>
/// Dlna Controller.
/// </summary>
@ -130,4 +130,3 @@ namespace Jellyfin.Api.Controllers
return NoContent();
}
}
}

@ -14,8 +14,8 @@ 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>
@ -321,4 +321,3 @@ 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,13 +29,13 @@ 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(Policy = Policies.DefaultAuthorization)]
[Authorize]
public class DynamicHlsController : BaseJellyfinApiController
{
private const string DefaultVodEncoderPreset = "veryfast";
@ -2052,4 +2051,3 @@ namespace Jellyfin.Api.Controllers
}
}
}
}

@ -12,8 +12,8 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Api.Controllers
{
namespace Jellyfin.Api.Controllers;
/// <summary>
/// Environment Controller.
/// </summary>
@ -194,4 +194,3 @@ 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,13 +11,13 @@ 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(Policy = Policies.DefaultAuthorization)]
[Authorize]
public class FilterController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
@ -213,4 +212,3 @@ 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,12 +17,12 @@ 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(Policy = Policies.DefaultAuthorization)]
[Authorize]
public class GenresController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
@ -208,4 +207,3 @@ 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,8 +14,8 @@ 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>
@ -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,
@ -189,4 +188,3 @@ namespace Jellyfin.Api.Controllers
return FileStreamResponseHelpers.GetStaticFileResult(path, MimeTypes.GetMimeType(path));
}
}
}

@ -30,8 +30,8 @@ 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>
@ -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,
@ -2142,4 +2107,3 @@ 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,13 +15,13 @@ 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(Policy = Policies.DefaultAuthorization)]
[Authorize]
public class InstantMixController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
@ -358,4 +357,3 @@ 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,13 +17,13 @@ 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(Policy = Policies.DefaultAuthorization)]
[Authorize]
public class ItemLookupController : BaseJellyfinApiController
{
private readonly IProviderManager _providerManager;
@ -271,4 +270,3 @@ namespace Jellyfin.Api.Controllers
return NoContent();
}
}
}

@ -9,8 +9,8 @@ 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>
@ -83,4 +83,3 @@ namespace Jellyfin.Api.Controllers
return NoContent();
}
}
}

@ -20,8 +20,8 @@ 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>
@ -446,4 +446,3 @@ namespace Jellyfin.Api.Controllers
return list;
}
}
}

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

Loading…
Cancel
Save