Merge branch 'master' into ef-cleanup

# Conflicts:
#	Jellyfin.Data/Entities/Libraries/MediaFileStream.cs
#	Jellyfin.Data/Jellyfin.Data.csproj
This commit is contained in:
Patrick Barron 2021-03-14 10:19:35 -04:00
commit e5380c653b
211 changed files with 2503 additions and 1144 deletions

View File

@ -94,5 +94,5 @@ jobs:
displayName: 'Publish OpenAPI Artifact' displayName: 'Publish OpenAPI Artifact'
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
inputs: inputs:
targetPath: "tests/Jellyfin.Api.Tests/bin/Release/net5.0/openapi.json" targetPath: "tests/Jellyfin.Server.Integration.Tests/bin/Release/net5.0/openapi.json"
artifactName: 'OpenAPI Spec' artifactName: 'OpenAPI Spec'

View File

@ -49,6 +49,7 @@
- [h1nk](https://github.com/h1nk) - [h1nk](https://github.com/h1nk)
- [hawken93](https://github.com/hawken93) - [hawken93](https://github.com/hawken93)
- [HelloWorld017](https://github.com/HelloWorld017) - [HelloWorld017](https://github.com/HelloWorld017)
- [ikomhoog](https://github.com/ikomhoog)
- [jftuga](https://github.com/jftuga) - [jftuga](https://github.com/jftuga)
- [joern-h](https://github.com/joern-h) - [joern-h](https://github.com/joern-h)
- [joshuaboniface](https://github.com/joshuaboniface) - [joshuaboniface](https://github.com/joshuaboniface)

View File

@ -36,7 +36,7 @@ namespace Emby.Dlna
private readonly ILogger<DlnaManager> _logger; private readonly ILogger<DlnaManager> _logger;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private static readonly Assembly _assembly = typeof(DlnaManager).Assembly; private static readonly Assembly _assembly = typeof(DlnaManager).Assembly;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private readonly Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>> _profiles = new Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>>(StringComparer.Ordinal); private readonly Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>> _profiles = new Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>>(StringComparer.Ordinal);
@ -333,7 +333,12 @@ namespace Emby.Dlna
throw new ArgumentNullException(nameof(id)); throw new ArgumentNullException(nameof(id));
} }
var info = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, id, StringComparison.OrdinalIgnoreCase)); var info = GetProfileInfosInternal().FirstOrDefault(i => string.Equals(i.Info.Id, id, StringComparison.OrdinalIgnoreCase));
if (info == null)
{
return null;
}
return ParseProfileFile(info.Path, info.Info.Type); return ParseProfileFile(info.Path, info.Info.Type);
} }
@ -395,7 +400,8 @@ namespace Emby.Dlna
{ {
Directory.CreateDirectory(systemProfilesPath); Directory.CreateDirectory(systemProfilesPath);
using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read)) // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None))
{ {
await stream.CopyToAsync(fileStream).ConfigureAwait(false); await stream.CopyToAsync(fileStream).ConfigureAwait(false);
} }

View File

@ -25,7 +25,6 @@
<!-- Code Analyzers--> <!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />

View File

@ -316,7 +316,7 @@ namespace Emby.Dlna.Main
_logger.LogInformation("Registering publisher for {0} on {1}", fullService, address); _logger.LogInformation("Registering publisher for {0} on {1}", fullService, address);
var uri = new UriBuilder(_appHost.GetSmartApiUrl(address.Address) + descriptorUri); var uri = new UriBuilder(_appHost.GetSmartApiUrl(address.Address) + descriptorUri);
if (_appHost.PublishedServerUrl == null) if (!string.IsNullOrEmpty(_appHost.PublishedServerUrl))
{ {
// DLNA will only work over http, so we must reset to http:// : {port}. // DLNA will only work over http, so we must reset to http:// : {port}.
uri.Scheme = "http"; uri.Scheme = "http";

View File

@ -132,7 +132,7 @@ namespace Emby.Dlna.PlayTo
private async void OnDeviceMediaChanged(object sender, MediaChangedEventArgs e) private async void OnDeviceMediaChanged(object sender, MediaChangedEventArgs e)
{ {
if (_disposed) if (_disposed || string.IsNullOrEmpty(e.OldMediaInfo.Url))
{ {
return; return;
} }

View File

@ -45,10 +45,10 @@ namespace Emby.Dlna.PlayTo
cancellationToken) cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
using var reader = new StreamReader(stream, Encoding.UTF8); return await XDocument.LoadAsync(
return XDocument.Parse( stream,
await reader.ReadToEndAsync().ConfigureAwait(false), LoadOptions.PreserveWhitespace,
LoadOptions.PreserveWhitespace); cancellationToken).ConfigureAwait(false);
} }
private static string NormalizeServiceUrl(string baseUrl, string serviceUrl) private static string NormalizeServiceUrl(string baseUrl, string serviceUrl)
@ -94,10 +94,10 @@ namespace Emby.Dlna.PlayTo
options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName); options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
using var reader = new StreamReader(stream, Encoding.UTF8); return await XDocument.LoadAsync(
return XDocument.Parse( stream,
await reader.ReadToEndAsync().ConfigureAwait(false), LoadOptions.PreserveWhitespace,
LoadOptions.PreserveWhitespace); cancellationToken).ConfigureAwait(false);
} }
private async Task<HttpResponseMessage> PostSoapDataAsync( private async Task<HttpResponseMessage> PostSoapDataAsync(

View File

@ -13,12 +13,10 @@ namespace Emby.Dlna.PlayTo
public class TransportCommands public class TransportCommands
{ {
private const string CommandBase = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<SOAP-ENV:Body>" + "<m:{0} xmlns:m=\"{1}\">" + "{2}" + "</m:{0}>" + "</SOAP-ENV:Body></SOAP-ENV:Envelope>"; private const string CommandBase = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<SOAP-ENV:Body>" + "<m:{0} xmlns:m=\"{1}\">" + "{2}" + "</m:{0}>" + "</SOAP-ENV:Body></SOAP-ENV:Envelope>";
private List<StateVariable> _stateVariables = new List<StateVariable>();
private List<ServiceAction> _serviceActions = new List<ServiceAction>();
public List<StateVariable> StateVariables => _stateVariables; public List<StateVariable> StateVariables { get; } = new List<StateVariable>();
public List<ServiceAction> ServiceActions => _serviceActions; public List<ServiceAction> ServiceActions { get; } = new List<ServiceAction>();
public static TransportCommands Create(XDocument document) public static TransportCommands Create(XDocument document)
{ {

View File

@ -1,5 +1,7 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System;
using System.Globalization;
using System.Linq; using System.Linq;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
@ -10,6 +12,7 @@ namespace Emby.Dlna.Profiles
{ {
public DefaultProfile() public DefaultProfile()
{ {
Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
Name = "Generic Device"; Name = "Generic Device";
ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*"; ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*";

View File

@ -25,7 +25,6 @@
<!-- Code analysers--> <!-- Code analysers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />

View File

@ -44,7 +44,6 @@
<!-- Code Analyzers--> <!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<!-- TODO: <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" /> -->
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace Emby.Naming.Video namespace Emby.Naming.Video
@ -16,8 +17,14 @@ namespace Emby.Naming.Video
/// <param name="expressions">List of regex to parse name and year from.</param> /// <param name="expressions">List of regex to parse name and year from.</param>
/// <param name="newName">Parsing result string.</param> /// <param name="newName">Parsing result string.</param>
/// <returns>True if parsing was successful.</returns> /// <returns>True if parsing was successful.</returns>
public static bool TryClean(string name, IReadOnlyList<Regex> expressions, out ReadOnlySpan<char> newName) public static bool TryClean([NotNullWhen(true)] string? name, IReadOnlyList<Regex> expressions, out ReadOnlySpan<char> newName)
{ {
if (string.IsNullOrEmpty(name))
{
newName = ReadOnlySpan<char>.Empty;
return false;
}
var len = expressions.Count; var len = expressions.Count;
for (int i = 0; i < len; i++) for (int i = 0; i < len; i++)
{ {
@ -41,7 +48,7 @@ namespace Emby.Naming.Video
return true; return true;
} }
newName = string.Empty; newName = ReadOnlySpan<char>.Empty;
return false; return false;
} }
} }

View File

@ -221,20 +221,22 @@ namespace Emby.Naming.Video
string testFilename = Path.GetFileNameWithoutExtension(testFilePath); string testFilename = Path.GetFileNameWithoutExtension(testFilePath);
if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase)) if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase))
{ {
if (CleanStringParser.TryClean(testFilename, _options.CleanStringRegexes, out var cleanName)) // Remove the folder name before cleaning as we don't care about cleaning that part
{
testFilename = cleanName.ToString();
}
if (folderName.Length <= testFilename.Length) if (folderName.Length <= testFilename.Length)
{ {
testFilename = testFilename.Substring(folderName.Length).Trim(); testFilename = testFilename.Substring(folderName.Length).Trim();
} }
if (CleanStringParser.TryClean(testFilename, _options.CleanStringRegexes, out var cleanName))
{
testFilename = cleanName.Trim().ToString();
}
// The CleanStringParser should have removed common keywords etc.
return string.IsNullOrEmpty(testFilename) return string.IsNullOrEmpty(testFilename)
|| testFilename[0] == '-' || testFilename[0] == '-'
|| testFilename[0] == '_' || testFilename[0] == '_'
|| string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty)); || Regex.IsMatch(testFilename, @"^\[([^]]*)\]");
} }
return false; return false;

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Emby.Naming.Common; using Emby.Naming.Common;
@ -146,7 +147,7 @@ namespace Emby.Naming.Video
/// <param name="name">Raw name.</param> /// <param name="name">Raw name.</param>
/// <param name="newName">Clean name.</param> /// <param name="newName">Clean name.</param>
/// <returns>True if cleaning of name was successful.</returns> /// <returns>True if cleaning of name was successful.</returns>
public bool TryCleanString(string name, out ReadOnlySpan<char> newName) public bool TryCleanString([NotNullWhen(true)] string? name, out ReadOnlySpan<char> newName)
{ {
return CleanStringParser.TryClean(name, _options.CleanStringRegexes, out newName); return CleanStringParser.TryClean(name, _options.CleanStringRegexes, out newName);
} }

View File

@ -25,7 +25,6 @@
<!-- Code analyzers--> <!-- Code analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />

View File

@ -28,7 +28,6 @@
<!-- Code Analyzers--> <!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />

View File

@ -53,7 +53,8 @@ namespace Emby.Server.Implementations.AppBase
Directory.CreateDirectory(directory); Directory.CreateDirectory(directory);
// Save it after load in case we got new items // Save it after load in case we got new items
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read)) // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None))
{ {
fs.Write(newBytes, 0, newBytesLen); fs.Write(newBytes, 0, newBytesLen);
} }

View File

@ -10,8 +10,6 @@ using System.Net;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Dlna; using Emby.Dlna;
@ -43,6 +41,7 @@ using Emby.Server.Implementations.Serialization;
using Emby.Server.Implementations.Session; using Emby.Server.Implementations.Session;
using Emby.Server.Implementations.SyncPlay; using Emby.Server.Implementations.SyncPlay;
using Emby.Server.Implementations.TV; using Emby.Server.Implementations.TV;
using Emby.Server.Implementations.Udp;
using Emby.Server.Implementations.Updates; using Emby.Server.Implementations.Updates;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Networking.Configuration; using Jellyfin.Networking.Configuration;
@ -50,7 +49,6 @@ using Jellyfin.Networking.Manager;
using MediaBrowser.Common; using MediaBrowser.Common;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Events; using MediaBrowser.Common.Events;
using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates; using MediaBrowser.Common.Updates;
@ -99,6 +97,7 @@ using MediaBrowser.Providers.Subtitles;
using MediaBrowser.XbmcMetadata.Providers; using MediaBrowser.XbmcMetadata.Providers;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Prometheus.DotNetRuntime; using Prometheus.DotNetRuntime;
@ -118,6 +117,7 @@ namespace Emby.Server.Implementations
private static readonly string[] _relevantEnvVarPrefixes = { "JELLYFIN_", "DOTNET_", "ASPNETCORE_" }; private static readonly string[] _relevantEnvVarPrefixes = { "JELLYFIN_", "DOTNET_", "ASPNETCORE_" };
private readonly IFileSystem _fileSystemManager; private readonly IFileSystem _fileSystemManager;
private readonly IConfiguration _startupConfig;
private readonly IXmlSerializer _xmlSerializer; private readonly IXmlSerializer _xmlSerializer;
private readonly IStartupOptions _startupOptions; private readonly IStartupOptions _startupOptions;
private readonly IPluginManager _pluginManager; private readonly IPluginManager _pluginManager;
@ -126,7 +126,6 @@ namespace Emby.Server.Implementations
private IMediaEncoder _mediaEncoder; private IMediaEncoder _mediaEncoder;
private ISessionManager _sessionManager; private ISessionManager _sessionManager;
private string[] _urlPrefixes; private string[] _urlPrefixes;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
/// <summary> /// <summary>
/// Gets a value indicating whether this instance can self restart. /// Gets a value indicating whether this instance can self restart.
@ -135,9 +134,6 @@ namespace Emby.Server.Implementations
public bool CoreStartupHasCompleted { get; private set; } public bool CoreStartupHasCompleted { get; private set; }
/// <inheritdoc />
public Uri PublishedServerUrl => _startupOptions.PublishedServerUrl;
public virtual bool CanLaunchWebBrowser public virtual bool CanLaunchWebBrowser
{ {
get get
@ -231,6 +227,11 @@ namespace Emby.Server.Implementations
/// </summary> /// </summary>
public int HttpsPort { get; private set; } public int HttpsPort { get; private set; }
/// <summary>
/// Gets the value of the PublishedServerUrl setting.
/// </summary>
public string PublishedServerUrl => _startupOptions.PublishedServerUrl ?? _startupConfig[UdpServer.AddressOverrideConfigKey];
/// <summary> /// <summary>
/// Gets the server configuration manager. /// Gets the server configuration manager.
/// </summary> /// </summary>
@ -243,12 +244,14 @@ namespace Emby.Server.Implementations
/// <param name="applicationPaths">Instance of the <see cref="IServerApplicationPaths"/> interface.</param> /// <param name="applicationPaths">Instance of the <see cref="IServerApplicationPaths"/> interface.</param>
/// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param> /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
/// <param name="options">Instance of the <see cref="IStartupOptions"/> interface.</param> /// <param name="options">Instance of the <see cref="IStartupOptions"/> interface.</param>
/// <param name="startupConfig">The <see cref="IConfiguration" /> interface.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
/// <param name="serviceCollection">Instance of the <see cref="IServiceCollection"/> interface.</param> /// <param name="serviceCollection">Instance of the <see cref="IServiceCollection"/> interface.</param>
public ApplicationHost( public ApplicationHost(
IServerApplicationPaths applicationPaths, IServerApplicationPaths applicationPaths,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
IStartupOptions options, IStartupOptions options,
IConfiguration startupConfig,
IFileSystem fileSystem, IFileSystem fileSystem,
IServiceCollection serviceCollection) IServiceCollection serviceCollection)
{ {
@ -271,6 +274,7 @@ namespace Emby.Server.Implementations
Logger = LoggerFactory.CreateLogger<ApplicationHost>(); Logger = LoggerFactory.CreateLogger<ApplicationHost>();
_startupOptions = options; _startupOptions = options;
_startupConfig = startupConfig;
// Initialize runtime stat collection // Initialize runtime stat collection
if (ServerConfigurationManager.Configuration.EnableMetrics) if (ServerConfigurationManager.Configuration.EnableMetrics)
@ -463,7 +467,7 @@ namespace Emby.Server.Implementations
} }
/// <inheritdoc /> /// <inheritdoc />
public IReadOnlyCollection<T> GetExports<T>(CreationDelegate defaultFunc, bool manageLifetime = true) public IReadOnlyCollection<T> GetExports<T>(CreationDelegateFactory defaultFunc, bool manageLifetime = true)
{ {
// Convert to list so this isn't executed for each iteration // Convert to list so this isn't executed for each iteration
var parts = GetExportTypes<T>() var parts = GetExportTypes<T>()
@ -487,8 +491,9 @@ namespace Emby.Server.Implementations
/// Runs the startup tasks. /// Runs the startup tasks.
/// </summary> /// </summary>
/// <returns><see cref="Task" />.</returns> /// <returns><see cref="Task" />.</returns>
public async Task RunStartupTasksAsync() public async Task RunStartupTasksAsync(CancellationToken cancellationToken)
{ {
cancellationToken.ThrowIfCancellationRequested();
Logger.LogInformation("Running startup tasks"); Logger.LogInformation("Running startup tasks");
Resolve<ITaskManager>().AddTasks(GetExports<IScheduledTask>(false)); Resolve<ITaskManager>().AddTasks(GetExports<IScheduledTask>(false));
@ -502,14 +507,21 @@ namespace Emby.Server.Implementations
var entryPoints = GetExports<IServerEntryPoint>(); var entryPoints = GetExports<IServerEntryPoint>();
cancellationToken.ThrowIfCancellationRequested();
var stopWatch = new Stopwatch(); var stopWatch = new Stopwatch();
stopWatch.Start(); stopWatch.Start();
await Task.WhenAll(StartEntryPoints(entryPoints, true)).ConfigureAwait(false); await Task.WhenAll(StartEntryPoints(entryPoints, true)).ConfigureAwait(false);
Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed); Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed);
Logger.LogInformation("Core startup complete"); Logger.LogInformation("Core startup complete");
CoreStartupHasCompleted = true; CoreStartupHasCompleted = true;
cancellationToken.ThrowIfCancellationRequested();
stopWatch.Restart(); stopWatch.Restart();
await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false); await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false);
Logger.LogInformation("Executed all post-startup entry points in {Elapsed:g}", stopWatch.Elapsed); Logger.LogInformation("Executed all post-startup entry points in {Elapsed:g}", stopWatch.Elapsed);
stopWatch.Stop(); stopWatch.Stop();
@ -1148,10 +1160,10 @@ namespace Emby.Server.Implementations
public string GetSmartApiUrl(IPAddress ipAddress, int? port = null) public string GetSmartApiUrl(IPAddress ipAddress, int? port = null)
{ {
// Published server ends with a / // Published server ends with a /
if (_startupOptions.PublishedServerUrl != null) if (!string.IsNullOrEmpty(PublishedServerUrl))
{ {
// Published server ends with a '/', so we need to remove it. // Published server ends with a '/', so we need to remove it.
return _startupOptions.PublishedServerUrl.ToString().Trim('/'); return PublishedServerUrl.Trim('/');
} }
string smart = NetManager.GetBindInterface(ipAddress, out port); string smart = NetManager.GetBindInterface(ipAddress, out port);
@ -1168,10 +1180,10 @@ namespace Emby.Server.Implementations
public string GetSmartApiUrl(HttpRequest request, int? port = null) public string GetSmartApiUrl(HttpRequest request, int? port = null)
{ {
// Published server ends with a / // Published server ends with a /
if (_startupOptions.PublishedServerUrl != null) if (!string.IsNullOrEmpty(PublishedServerUrl))
{ {
// Published server ends with a '/', so we need to remove it. // Published server ends with a '/', so we need to remove it.
return _startupOptions.PublishedServerUrl.ToString().Trim('/'); return PublishedServerUrl.Trim('/');
} }
string smart = NetManager.GetBindInterface(request, out port); string smart = NetManager.GetBindInterface(request, out port);
@ -1188,10 +1200,10 @@ namespace Emby.Server.Implementations
public string GetSmartApiUrl(string hostname, int? port = null) public string GetSmartApiUrl(string hostname, int? port = null)
{ {
// Published server ends with a / // Published server ends with a /
if (_startupOptions.PublishedServerUrl != null) if (!string.IsNullOrEmpty(PublishedServerUrl))
{ {
// Published server ends with a '/', so we need to remove it. // Published server ends with a '/', so we need to remove it.
return _startupOptions.PublishedServerUrl.ToString().Trim('/'); return PublishedServerUrl.Trim('/');
} }
string smart = NetManager.GetBindInterface(hostname, out port); string smart = NetManager.GetBindInterface(hostname, out port);

View File

@ -49,7 +49,7 @@ namespace Emby.Server.Implementations.Channels
private readonly IProviderManager _providerManager; private readonly IProviderManager _providerManager;
private readonly IMemoryCache _memoryCache; private readonly IMemoryCache _memoryCache;
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ChannelManager"/> class. /// Initializes a new instance of the <see cref="ChannelManager"/> class.

View File

@ -343,11 +343,24 @@ namespace Emby.Server.Implementations.Collections
} }
} }
else else
{
var alreadyInResults = false;
foreach (var child in item.GetMediaSources(true))
{
if (Guid.TryParse(child.Id, out var id) && results.ContainsKey(id))
{
alreadyInResults = true;
break;
}
}
if (!alreadyInResults)
{ {
results[item.Id] = item; results[item.Id] = item;
} }
} }
} }
}
return results.Values; return results.Values;
} }

View File

@ -88,7 +88,7 @@ namespace Emby.Server.Implementations.Data
_imageProcessor = imageProcessor; _imageProcessor = imageProcessor;
_typeMapper = new TypeMapper(); _typeMapper = new TypeMapper();
_jsonOptions = JsonDefaults.GetOptions(); _jsonOptions = JsonDefaults.Options;
DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db"); DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db");
} }

View File

@ -50,7 +50,6 @@
<!-- Code Analyzers--> <!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />

View File

@ -1,5 +1,6 @@
#nullable enable #nullable enable
using System;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -51,6 +52,8 @@ namespace Emby.Server.Implementations.EntryPoints
/// <inheritdoc /> /// <inheritdoc />
public Task RunAsync() public Task RunAsync()
{ {
CheckDisposed();
try try
{ {
_udpServer = new UdpServer(_logger, _appHost, _config); _udpServer = new UdpServer(_logger, _appHost, _config);
@ -64,6 +67,14 @@ namespace Emby.Server.Implementations.EntryPoints
return Task.CompletedTask; return Task.CompletedTask;
} }
private void CheckDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(this.GetType().Name);
}
}
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{ {

View File

@ -56,7 +56,7 @@ namespace Emby.Server.Implementations.HttpServer
RemoteEndPoint = remoteEndPoint; RemoteEndPoint = remoteEndPoint;
QueryString = query; QueryString = query;
_jsonOptions = JsonDefaults.GetOptions(); _jsonOptions = JsonDefaults.Options;
LastActivityDate = DateTime.Now; LastActivityDate = DateTime.Now;
} }

View File

@ -1,5 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#nullable enable
using System; using System;
namespace Emby.Server.Implementations namespace Emby.Server.Implementations
@ -9,7 +9,7 @@ namespace Emby.Server.Implementations
/// <summary> /// <summary>
/// Gets the value of the --ffmpeg command line option. /// Gets the value of the --ffmpeg command line option.
/// </summary> /// </summary>
string FFmpegPath { get; } string? FFmpegPath { get; }
/// <summary> /// <summary>
/// Gets the value of the --service command line option. /// Gets the value of the --service command line option.
@ -19,21 +19,21 @@ namespace Emby.Server.Implementations
/// <summary> /// <summary>
/// Gets the value of the --package-name command line option. /// Gets the value of the --package-name command line option.
/// </summary> /// </summary>
string PackageName { get; } string? PackageName { get; }
/// <summary> /// <summary>
/// Gets the value of the --restartpath command line option. /// Gets the value of the --restartpath command line option.
/// </summary> /// </summary>
string RestartPath { get; } string? RestartPath { get; }
/// <summary> /// <summary>
/// Gets the value of the --restartargs command line option. /// Gets the value of the --restartargs command line option.
/// </summary> /// </summary>
string RestartArgs { get; } string? RestartArgs { get; }
/// <summary> /// <summary>
/// Gets the value of the --published-server-url command line option. /// Gets the value of the --published-server-url command line option.
/// </summary> /// </summary>
Uri PublishedServerUrl { get; } string? PublishedServerUrl { get; }
} }
} }

View File

@ -1247,7 +1247,7 @@ namespace Emby.Server.Implementations.Library
{ {
// TODO: @bond use a ReadOnlySpan<char> here when Enum.TryParse supports it // TODO: @bond use a ReadOnlySpan<char> here when Enum.TryParse supports it
// https://github.com/dotnet/runtime/issues/20008 // https://github.com/dotnet/runtime/issues/20008
if (Enum.TryParse<CollectionTypeOptions>(Path.GetExtension(file), true, out var res)) if (Enum.TryParse<CollectionTypeOptions>(Path.GetFileNameWithoutExtension(file), true, out var res))
{ {
return res; return res;
} }
@ -2776,6 +2776,7 @@ namespace Emby.Server.Implementations.Library
public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem) public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem)
{ {
string newPath;
if (ownerItem != null) if (ownerItem != null)
{ {
var libraryOptions = GetLibraryOptions(ownerItem); var libraryOptions = GetLibraryOptions(ownerItem);
@ -2783,15 +2784,9 @@ namespace Emby.Server.Implementations.Library
{ {
foreach (var pathInfo in libraryOptions.PathInfos) foreach (var pathInfo in libraryOptions.PathInfos)
{ {
if (string.IsNullOrWhiteSpace(pathInfo.Path) || string.IsNullOrWhiteSpace(pathInfo.NetworkPath)) if (path.TryReplaceSubPath(pathInfo.Path, pathInfo.NetworkPath, out newPath))
{ {
continue; return newPath;
}
var substitutionResult = SubstitutePathInternal(path, pathInfo.Path, pathInfo.NetworkPath);
if (substitutionResult.Item2)
{
return substitutionResult.Item1;
} }
} }
} }
@ -2800,24 +2795,16 @@ namespace Emby.Server.Implementations.Library
var metadataPath = _configurationManager.Configuration.MetadataPath; var metadataPath = _configurationManager.Configuration.MetadataPath;
var metadataNetworkPath = _configurationManager.Configuration.MetadataNetworkPath; var metadataNetworkPath = _configurationManager.Configuration.MetadataNetworkPath;
if (!string.IsNullOrWhiteSpace(metadataPath) && !string.IsNullOrWhiteSpace(metadataNetworkPath)) if (path.TryReplaceSubPath(metadataPath, metadataNetworkPath, out newPath))
{ {
var metadataSubstitutionResult = SubstitutePathInternal(path, metadataPath, metadataNetworkPath); return newPath;
if (metadataSubstitutionResult.Item2)
{
return metadataSubstitutionResult.Item1;
}
} }
foreach (var map in _configurationManager.Configuration.PathSubstitutions) foreach (var map in _configurationManager.Configuration.PathSubstitutions)
{ {
if (!string.IsNullOrWhiteSpace(map.From)) if (path.TryReplaceSubPath(map.From, map.To, out newPath))
{ {
var substitutionResult = SubstitutePathInternal(path, map.From, map.To); return newPath;
if (substitutionResult.Item2)
{
return substitutionResult.Item1;
}
} }
} }
@ -2826,47 +2813,12 @@ namespace Emby.Server.Implementations.Library
public string SubstitutePath(string path, string from, string to) public string SubstitutePath(string path, string from, string to)
{ {
return SubstitutePathInternal(path, from, to).Item1; if (path.TryReplaceSubPath(from, to, out var newPath))
{
return newPath;
} }
private Tuple<string, bool> SubstitutePathInternal(string path, string from, string to) return path;
{
if (string.IsNullOrWhiteSpace(path))
{
throw new ArgumentNullException(nameof(path));
}
if (string.IsNullOrWhiteSpace(from))
{
throw new ArgumentNullException(nameof(from));
}
if (string.IsNullOrWhiteSpace(to))
{
throw new ArgumentNullException(nameof(to));
}
from = from.Trim();
to = to.Trim();
var newPath = path.Replace(from, to, StringComparison.OrdinalIgnoreCase);
var changed = false;
if (!string.Equals(newPath, path, StringComparison.Ordinal))
{
if (to.IndexOf('/', StringComparison.Ordinal) != -1)
{
newPath = newPath.Replace('\\', '/');
}
else
{
newPath = newPath.Replace('/', '\\');
}
changed = true;
}
return new Tuple<string, bool>(newPath, changed);
} }
private void SetExtraTypeFromFilename(Video item) private void SetExtraTypeFromFilename(Video item)
@ -3001,7 +2953,7 @@ namespace Emby.Server.Implementations.Library
if (collectionType != null) if (collectionType != null)
{ {
var path = Path.Combine(virtualFolderPath, collectionType.ToString() + ".collection"); var path = Path.Combine(virtualFolderPath, collectionType.ToString().ToLowerInvariant() + ".collection");
File.WriteAllBytes(path, Array.Empty<byte>()); File.WriteAllBytes(path, Array.Empty<byte>());
} }

View File

@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Library
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger, IApplicationPaths appPaths) public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger, IApplicationPaths appPaths)
{ {

View File

@ -46,7 +46,7 @@ namespace Emby.Server.Implementations.Library
private readonly ConcurrentDictionary<string, ILiveStream> _openStreams = new ConcurrentDictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase); private readonly ConcurrentDictionary<string, ILiveStream> _openStreams = new ConcurrentDictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase);
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private IMediaSourceProvider[] _providers; private IMediaSourceProvider[] _providers;

View File

@ -1,6 +1,8 @@
#nullable enable #nullable enable
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace Emby.Server.Implementations.Library namespace Emby.Server.Implementations.Library
@ -47,5 +49,66 @@ namespace Emby.Server.Implementations.Library
return null; return null;
} }
/// <summary>
/// Replaces a sub path with another sub path and normalizes the final path.
/// </summary>
/// <param name="path">The original path.</param>
/// <param name="subPath">The original sub path.</param>
/// <param name="newSubPath">The new sub path.</param>
/// <param name="newPath">The result of the sub path replacement</param>
/// <returns>The path after replacing the sub path.</returns>
/// <exception cref="ArgumentNullException"><paramref name="path" />, <paramref name="newSubPath" /> or <paramref name="newSubPath" /> is empty.</exception>
public static bool TryReplaceSubPath(
[NotNullWhen(true)] this string? path,
[NotNullWhen(true)] string? subPath,
[NotNullWhen(true)] string? newSubPath,
[NotNullWhen(true)] out string? newPath)
{
newPath = null;
if (string.IsNullOrEmpty(path)
|| string.IsNullOrEmpty(subPath)
|| string.IsNullOrEmpty(newSubPath)
|| subPath.Length > path.Length)
{
return false;
}
char oldDirectorySeparatorChar;
char newDirectorySeparatorChar;
// True normalization is still not possible https://github.com/dotnet/runtime/issues/2162
// The reasoning behind this is that a forward slash likely means it's a Linux path and
// so the whole path should be normalized to use / and vice versa for Windows (although Windows doesn't care much).
if (newSubPath.Contains('/', StringComparison.Ordinal))
{
oldDirectorySeparatorChar = '\\';
newDirectorySeparatorChar = '/';
}
else
{
oldDirectorySeparatorChar = '/';
newDirectorySeparatorChar = '\\';
}
path = path.Replace(oldDirectorySeparatorChar, newDirectorySeparatorChar);
subPath = subPath.Replace(oldDirectorySeparatorChar, newDirectorySeparatorChar);
// We have to ensure that the sub path ends with a directory separator otherwise we'll get weird results
// when the sub path matches a similar but in-complete subpath
var oldSubPathEndsWithSeparator = subPath[^1] == newDirectorySeparatorChar;
if (!path.StartsWith(subPath, StringComparison.OrdinalIgnoreCase)
|| (!oldSubPathEndsWithSeparator && path[subPath.Length] != newDirectorySeparatorChar))
{
return false;
}
var newSubPathTrimmed = newSubPath.AsSpan().TrimEnd(newDirectorySeparatorChar);
// Ensure that the path with the old subpath removed starts with a leading dir separator
int idx = oldSubPathEndsWithSeparator ? subPath.Length - 1 : subPath.Length;
newPath = string.Concat(newSubPathTrimmed, path.AsSpan(idx));
return true;
}
} }
} }

View File

@ -45,7 +45,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
Directory.CreateDirectory(Path.GetDirectoryName(targetFile)); Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.Read)) // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None))
{ {
onStarted(); onStarted();
@ -70,7 +71,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
Directory.CreateDirectory(Path.GetDirectoryName(targetFile)); Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.Read); // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None);
onStarted(); onStarted();

View File

@ -1856,7 +1856,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return; return;
} }
using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.Read)) // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.None))
{ {
var settings = new XmlWriterSettings var settings = new XmlWriterSettings
{ {
@ -1920,7 +1921,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return; return;
} }
using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.Read)) // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.None))
{ {
var settings = new XmlWriterSettings var settings = new XmlWriterSettings
{ {

View File

@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly IServerApplicationPaths _appPaths; private readonly IServerApplicationPaths _appPaths;
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>(); private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private bool _hasExited; private bool _hasExited;
private Stream _logFileStream; private Stream _logFileStream;
private string _targetPath; private string _targetPath;

View File

@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
private readonly string _dataPath; private readonly string _dataPath;
private readonly object _fileDataLock = new object(); private readonly object _fileDataLock = new object();
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private T[] _items; private T[] _items;
public ItemDataProvider( public ItemDataProvider(

View File

@ -35,7 +35,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private readonly ICryptoProvider _cryptoProvider; private readonly ICryptoProvider _cryptoProvider;
private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>(); private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>();
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private DateTime _lastErrorResponse; private DateTime _lastErrorResponse;
public SchedulesDirect( public SchedulesDirect(

View File

@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_networkManager = networkManager; _networkManager = networkManager;
_streamHelper = streamHelper; _streamHelper = streamHelper;
_jsonOptions = JsonDefaults.GetOptions(); _jsonOptions = JsonDefaults.Options;
} }
public string Name => "HD Homerun"; public string Name => "HD Homerun";

View File

@ -2,6 +2,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Net; using System.Net;
@ -10,6 +11,7 @@ using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
@ -120,13 +122,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private static async Task<bool> CheckTunerAvailability(NetworkStream stream, int tuner, CancellationToken cancellationToken) private static async Task<bool> CheckTunerAvailability(NetworkStream stream, int tuner, CancellationToken cancellationToken)
{ {
var lockkeyMsg = CreateGetMessage(tuner, "lockkey");
await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false);
byte[] buffer = ArrayPool<byte>.Shared.Rent(8192); byte[] buffer = ArrayPool<byte>.Shared.Rent(8192);
try try
{ {
int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); var msgLen = WriteGetMessage(buffer, tuner, "lockkey");
await stream.WriteAsync(buffer.AsMemory(0, msgLen), cancellationToken).ConfigureAwait(false);
int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
ParseReturnMessage(buffer, receivedBytes, out string returnVal); ParseReturnMessage(buffer, receivedBytes, out string returnVal);
@ -166,9 +168,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_activeTuner = i; _activeTuner = i;
var lockKeyString = string.Format(CultureInfo.InvariantCulture, "{0:d}", lockKeyValue); var lockKeyString = string.Format(CultureInfo.InvariantCulture, "{0:d}", lockKeyValue);
var lockkeyMsg = CreateSetMessage(i, "lockkey", lockKeyString, null); var lockkeyMsgLen = WriteSetMessage(buffer, i, "lockkey", lockKeyString, null);
await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false); await stream.WriteAsync(buffer.AsMemory(0, lockkeyMsgLen), cancellationToken).ConfigureAwait(false);
int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked // parse response to make sure it worked
if (!ParseReturnMessage(buffer, receivedBytes, out _)) if (!ParseReturnMessage(buffer, receivedBytes, out _))
@ -178,9 +180,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
foreach (var command in commands.GetCommands()) foreach (var command in commands.GetCommands())
{ {
var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, lockKeyValue); var channelMsgLen = WriteSetMessage(buffer, i, command.Item1, command.Item2, lockKeyValue);
await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false); await stream.WriteAsync(buffer.AsMemory(0, channelMsgLen), cancellationToken).ConfigureAwait(false);
receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked // parse response to make sure it worked
if (!ParseReturnMessage(buffer, receivedBytes, out _)) if (!ParseReturnMessage(buffer, receivedBytes, out _))
@ -191,10 +193,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
} }
var targetValue = string.Format(CultureInfo.InvariantCulture, "rtp://{0}:{1}", localIp, localPort); var targetValue = string.Format(CultureInfo.InvariantCulture, "rtp://{0}:{1}", localIp, localPort);
var targetMsg = CreateSetMessage(i, "target", targetValue, lockKeyValue); var targetMsgLen = WriteSetMessage(buffer, i, "target", targetValue, lockKeyValue);
await stream.WriteAsync(targetMsg, 0, targetMsg.Length, cancellationToken).ConfigureAwait(false); await stream.WriteAsync(buffer.AsMemory(0, targetMsgLen), cancellationToken).ConfigureAwait(false);
receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked // parse response to make sure it worked
if (!ParseReturnMessage(buffer, receivedBytes, out _)) if (!ParseReturnMessage(buffer, receivedBytes, out _))
@ -232,9 +234,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{ {
foreach (var command in commandList) foreach (var command in commandList)
{ {
var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey); var channelMsgLen = WriteSetMessage(buffer, _activeTuner, command.Item1, command.Item2, _lockkey);
await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false); await stream.WriteAsync(buffer.AsMemory(0, channelMsgLen), cancellationToken).ConfigureAwait(false);
int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked // parse response to make sure it worked
if (!ParseReturnMessage(buffer, receivedBytes, out _)) if (!ParseReturnMessage(buffer, receivedBytes, out _))
@ -265,17 +267,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{ {
var stream = client.GetStream(); var stream = client.GetStream();
var releaseTarget = CreateSetMessage(_activeTuner, "target", "none", lockKeyValue);
await stream.WriteAsync(releaseTarget, 0, releaseTarget.Length).ConfigureAwait(false);
var buffer = ArrayPool<byte>.Shared.Rent(8192); var buffer = ArrayPool<byte>.Shared.Rent(8192);
try try
{ {
await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); var releaseTargetLen = WriteSetMessage(buffer, _activeTuner, "target", "none", lockKeyValue);
var releaseKeyMsg = CreateSetMessage(_activeTuner, "lockkey", "none", lockKeyValue); await stream.WriteAsync(buffer.AsMemory(0, releaseTargetLen)).ConfigureAwait(false);
await stream.ReadAsync(buffer).ConfigureAwait(false);
var releaseKeyMsgLen = WriteSetMessage(buffer, _activeTuner, "lockkey", "none", lockKeyValue);
_lockkey = null; _lockkey = null;
await stream.WriteAsync(releaseKeyMsg, 0, releaseKeyMsg.Length).ConfigureAwait(false); await stream.WriteAsync(buffer.AsMemory(0, releaseKeyMsgLen)).ConfigureAwait(false);
await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); await stream.ReadAsync(buffer).ConfigureAwait(false);
} }
finally finally
{ {
@ -283,109 +285,76 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
} }
} }
private static byte[] CreateGetMessage(int tuner, string name) internal static int WriteGetMessage(Span<byte> buffer, int tuner, string name)
{ {
var byteName = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}\0", tuner, name)); var byteName = string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}", tuner, name);
int messageLength = byteName.Length + 10; // 4 bytes for header + 4 bytes for crc + 2 bytes for tag name and length int offset = WriteHeaderAndPayload(buffer, byteName);
return FinishPacket(buffer, offset);
var message = new byte[messageLength];
int offset = InsertHeaderAndName(byteName, messageLength, message);
bool flipEndian = BitConverter.IsLittleEndian;
// calculate crc and insert at the end of the message
var crcBytes = BitConverter.GetBytes(HdHomerunCrc.GetCrc32(message, messageLength - 4));
if (flipEndian)
{
Array.Reverse(crcBytes);
} }
Buffer.BlockCopy(crcBytes, 0, message, offset, 4); private static int WriteSetMessage(Span<byte> buffer, int tuner, string name, string value, uint? lockkey)
return message;
}
private static byte[] CreateSetMessage(int tuner, string name, string value, uint? lockkey)
{ {
var byteName = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}\0", tuner, name)); var byteName = string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}", tuner, name);
var byteValue = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "{0}\0", value)); int offset = WriteHeaderAndPayload(buffer, byteName);
buffer[offset++] = GetSetValue;
offset += WriteNullTerminatedString(buffer.Slice(offset), value);
int messageLength = byteName.Length + byteValue.Length + 12;
if (lockkey.HasValue) if (lockkey.HasValue)
{ {
messageLength += 6; buffer[offset++] = GetSetLockkey;
} buffer[offset++] = 4;
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset), lockkey.Value);
var message = new byte[messageLength];
int offset = InsertHeaderAndName(byteName, messageLength, message);
bool flipEndian = BitConverter.IsLittleEndian;
message[offset++] = GetSetValue;
message[offset++] = Convert.ToByte(byteValue.Length);
Buffer.BlockCopy(byteValue, 0, message, offset, byteValue.Length);
offset += byteValue.Length;
if (lockkey.HasValue)
{
message[offset++] = GetSetLockkey;
message[offset++] = 4;
var lockKeyBytes = BitConverter.GetBytes(lockkey.Value);
if (flipEndian)
{
Array.Reverse(lockKeyBytes);
}
Buffer.BlockCopy(lockKeyBytes, 0, message, offset, 4);
offset += 4; offset += 4;
} }
// calculate crc and insert at the end of the message return FinishPacket(buffer, offset);
var crcBytes = BitConverter.GetBytes(HdHomerunCrc.GetCrc32(message, messageLength - 4));
if (flipEndian)
{
Array.Reverse(crcBytes);
} }
Buffer.BlockCopy(crcBytes, 0, message, offset, 4); internal static int WriteNullTerminatedString(Span<byte> buffer, ReadOnlySpan<char> payload)
{
int len = Encoding.UTF8.GetBytes(payload, buffer.Slice(1)) + 1;
return message; // TODO: variable length: this can be 2 bytes if len > 127
// Write length in front of value
buffer[0] = Convert.ToByte(len);
// null-terminate
buffer[len++] = 0;
return len;
} }
private static int InsertHeaderAndName(byte[] byteName, int messageLength, byte[] message) private static int WriteHeaderAndPayload(Span<byte> buffer, ReadOnlySpan<char> payload)
{ {
// check to see if we need to flip endiannes // Packet type
bool flipEndian = BitConverter.IsLittleEndian; BinaryPrimitives.WriteUInt16BigEndian(buffer, GetSetRequest);
int offset = 0;
// create header bytes // We write the payload length at the end
var getSetBytes = BitConverter.GetBytes(GetSetRequest); int offset = 4;
var msgLenBytes = BitConverter.GetBytes((ushort)(messageLength - 8)); // Subtrace 4 bytes for header and 4 bytes for crc
if (flipEndian) // Tag
{ buffer[offset++] = GetSetName;
Array.Reverse(getSetBytes);
Array.Reverse(msgLenBytes);
}
// insert header bytes into message // Payload length + data
Buffer.BlockCopy(getSetBytes, 0, message, offset, 2); int strLen = WriteNullTerminatedString(buffer.Slice(offset), payload);
offset += 2; offset += strLen;
Buffer.BlockCopy(msgLenBytes, 0, message, offset, 2);
offset += 2;
// insert tag name and length
message[offset++] = GetSetName;
message[offset++] = Convert.ToByte(byteName.Length);
// insert name string
Buffer.BlockCopy(byteName, 0, message, offset, byteName.Length);
offset += byteName.Length;
return offset; return offset;
} }
private static int FinishPacket(Span<byte> buffer, int offset)
{
// Payload length
BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), (ushort)(offset - 4));
// calculate crc and insert at the end of the message
var crc = Crc32.Compute(buffer.Slice(0, offset));
BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset), crc);
return offset + 4;
}
private static bool ParseReturnMessage(byte[] buf, int numBytes, out string returnVal) private static bool ParseReturnMessage(byte[] buf, int numBytes, out string returnVal)
{ {
returnVal = string.Empty; returnVal = string.Empty;
@ -442,90 +411,5 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
returnVal = Encoding.UTF8.GetString(buf, offset, valueLength - 1); // remove null terminator returnVal = Encoding.UTF8.GetString(buf, offset, valueLength - 1); // remove null terminator
return true; return true;
} }
private static class HdHomerunCrc
{
private static uint[] crc_table = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d };
public static uint GetCrc32(byte[] bytes, int numBytes)
{
var hash = 0xffffffff;
for (var i = 0; i < numBytes; i++)
{
hash = (hash >> 8) ^ crc_table[(hash ^ bytes[i]) & 0xff];
}
var tmp = ~hash & 0xffffffff;
var b0 = tmp & 0xff;
var b1 = (tmp >> 8) & 0xff;
var b2 = (tmp >> 16) & 0xff;
var b3 = (tmp >> 24) & 0xff;
return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
}
}
} }
} }

View File

@ -57,5 +57,27 @@
"DeviceOnlineWithName": "{0} conectouse", "DeviceOnlineWithName": "{0} conectouse",
"DeviceOfflineWithName": "{0} desconectouse", "DeviceOfflineWithName": "{0} desconectouse",
"Default": "Por defecto", "Default": "Por defecto",
"AppDeviceValues": "Aplicación: {0}, Dispositivo: {1}" "AppDeviceValues": "Aplicación: {0}, Dispositivo: {1}",
"TaskCleanLogs": "Limpar Carpeta de Rexistros",
"TaskCleanActivityLog": "Limpar Rexistro de Actividade",
"TasksChannelsCategory": "Canáis de Internet",
"TaskUpdatePlugins": "Actualizar Plugins",
"User": "Usuario",
"Undefined": "Sen definir",
"TvShows": "Programas de TV",
"System": "Sistema",
"Sync": "Sincronizar",
"SubtitleDownloadFailureFromForItem": "Fallou a descarga de subtítulos para {1} dende {0}",
"StartupEmbyServerIsLoading": "O Servidor Jellyfin está cargando. Por favor, reinténteo en breve.",
"Songs": "Cancións",
"Shows": "Programas",
"ServerNameNeedsToBeRestarted": "{0} precisa ser reiniciado",
"ScheduledTaskStartedWithName": "{0} comezou",
"ScheduledTaskFailedWithName": "{0} fallou",
"ProviderValue": "Provedor: {0}",
"PluginUpdatedWithName": "{0} foi actualizado",
"PluginUninstalledWithName": "{0} foi desinstalado",
"PluginInstalledWithName": "{0} foi instalado",
"Playlists": "Listas de reproducción",
"Photos": "Fotos"
} }

View File

@ -49,7 +49,7 @@
"NotificationOptionAudioPlayback": "Audió lejátszás elkezdve", "NotificationOptionAudioPlayback": "Audió lejátszás elkezdve",
"NotificationOptionAudioPlaybackStopped": "Audió lejátszás leállítva", "NotificationOptionAudioPlaybackStopped": "Audió lejátszás leállítva",
"NotificationOptionCameraImageUploaded": "Kamera kép feltöltve", "NotificationOptionCameraImageUploaded": "Kamera kép feltöltve",
"NotificationOptionInstallationFailed": "Telepítési hiba", "NotificationOptionInstallationFailed": "Telepítés sikertelen",
"NotificationOptionNewLibraryContent": "Új tartalom hozzáadva", "NotificationOptionNewLibraryContent": "Új tartalom hozzáadva",
"NotificationOptionPluginError": "Bővítmény hiba", "NotificationOptionPluginError": "Bővítmény hiba",
"NotificationOptionPluginInstalled": "Bővítmény telepítve", "NotificationOptionPluginInstalled": "Bővítmény telepítve",

View File

@ -109,14 +109,14 @@
"TasksMaintenanceCategory": "Qyzmet körsetu", "TasksMaintenanceCategory": "Qyzmet körsetu",
"Undefined": "Anyqtalmağan", "Undefined": "Anyqtalmağan",
"Forced": "Mäjbürlı", "Forced": "Mäjbürlı",
"TaskDownloadMissingSubtitlesDescription": "Metaderekter teŋşelımderı negızınde joq subtitrlerdı Internetten ızdeidı.", "TaskDownloadMissingSubtitlesDescription": "Metaderekter teŋşelımderı negızınde joq subtitrlerdı İnternetten ızdeidı.",
"TaskRefreshChannelsDescription": "Internet-arnalar mälımetterın jaŋğyrtady.", "TaskRefreshChannelsDescription": "Internet-arnalar mälımetterın jaŋğyrtady.",
"TaskCleanTranscodeDescription": "Bіr künnen asqan qaita kodtau faildaryn joiady.", "TaskCleanTranscodeDescription": "Bіr künnen asqan qaita kodtau faildaryn joiady.",
"TaskUpdatePluginsDescription": "Avtomatty türde jaŋartuğa teŋşelgen plaginder üşın jaŋartulardy jüktep alady jäne ornatady.", "TaskUpdatePluginsDescription": "Avtomatty türde jaŋartuğa teŋşelgen plaginder üşın jaŋartulardy jüktep alady jäne ornatady.",
"TaskRefreshPeopleDescription": "Tasyğyşhanadağy aktörler men rejisörler metaderekterın jaŋartady.", "TaskRefreshPeopleDescription": "Tasyğyşhanadağy aktörler men rejisörler metaderekterın jaŋartady.",
"TaskCleanLogsDescription": "{0} künnen asqan jūrnal faildaryn joiady.", "TaskCleanLogsDescription": "{0} künnen asqan jūrnal faildaryn joiady.",
"TaskRefreshLibraryDescription": "Tasyğyşhanadağy jaŋa faildardy skanerleidі jäne metaderekterdı jaŋğyrtady.", "TaskRefreshLibraryDescription": "Tasyğyşhanadağy jaŋa faildardy skanerleidі jäne metaderekterdı jaŋğyrtady.",
"TaskRefreshChapterImagesDescription": "Sahnalary bar beineler üşіn nobailar jasaidy.", "TaskRefreshChapterImagesDescription": "Sahnalary bar beineler üşın nobailar jasaidy.",
"TaskCleanCacheDescription": "Jüiede qajet emes keştelgen faildardy joiady.", "TaskCleanCacheDescription": "Jüiede qajet emes keştelgen faildardy joiady.",
"TaskCleanActivityLogDescription": "Äreket jūrnalyndağy teŋşelgen jasynan asqan jazbalary joiady." "TaskCleanActivityLogDescription": "Äreket jūrnalyndağy teŋşelgen jasynan asqan jazbalary joiady."
} }

View File

@ -50,7 +50,7 @@
"HeaderFavoriteArtists": "ศิลปินที่ชื่นชอบ", "HeaderFavoriteArtists": "ศิลปินที่ชื่นชอบ",
"HeaderFavoriteAlbums": "อัมบั้มที่ชื่นชอบ", "HeaderFavoriteAlbums": "อัมบั้มที่ชื่นชอบ",
"HeaderContinueWatching": "ดูต่อ", "HeaderContinueWatching": "ดูต่อ",
"HeaderAlbumArtists": "อัลบั้มศิลปิน", "HeaderAlbumArtists": "ศิลปินอัลบั้ม",
"Genres": "ประเภท", "Genres": "ประเภท",
"Folders": "โฟลเดอร์", "Folders": "โฟลเดอร์",
"Favorites": "รายการโปรด", "Favorites": "รายการโปรด",
@ -112,5 +112,6 @@
"System": "ระบบ", "System": "ระบบ",
"Sync": "ซิงค์", "Sync": "ซิงค์",
"SubtitleDownloadFailureFromForItem": "ไม่สามารถดาวน์โหลดคำบรรยายจาก {0} สำหรับ {1} ได้", "SubtitleDownloadFailureFromForItem": "ไม่สามารถดาวน์โหลดคำบรรยายจาก {0} สำหรับ {1} ได้",
"StartupEmbyServerIsLoading": "กำลังโหลดเซิร์ฟเวอร์ Jellyfin โปรดลองอีกครั้งในอีกสักครู่" "StartupEmbyServerIsLoading": "กำลังโหลดเซิร์ฟเวอร์ Jellyfin โปรดลองอีกครั้งในอีกสักครู่",
"Default": "ค่าเริ่มต้น"
} }

View File

@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Localization
private List<CultureDto> _cultures; private List<CultureDto> _cultures;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LocalizationManager" /> class. /// Initializes a new instance of the <see cref="LocalizationManager" /> class.

View File

@ -34,7 +34,7 @@ namespace Emby.Server.Implementations.Plugins
private readonly ILogger<PluginManager> _logger; private readonly ILogger<PluginManager> _logger;
private readonly IApplicationHost _appHost; private readonly IApplicationHost _appHost;
private readonly ServerConfiguration _config; private readonly ServerConfiguration _config;
private readonly IList<LocalPlugin> _plugins; private readonly List<LocalPlugin> _plugins;
private readonly Version _minimumVersion; private readonly Version _minimumVersion;
private IHttpClientFactory? _httpClientFactory; private IHttpClientFactory? _httpClientFactory;
@ -70,7 +70,7 @@ namespace Emby.Server.Implementations.Plugins
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
_pluginsPath = pluginsPath; _pluginsPath = pluginsPath;
_appVersion = appVersion ?? throw new ArgumentNullException(nameof(appVersion)); _appVersion = appVersion ?? throw new ArgumentNullException(nameof(appVersion));
_jsonOptions = new JsonSerializerOptions(JsonDefaults.GetOptions()) _jsonOptions = new JsonSerializerOptions(JsonDefaults.Options)
{ {
WriteIndented = true WriteIndented = true
}; };
@ -94,7 +94,7 @@ namespace Emby.Server.Implementations.Plugins
/// <summary> /// <summary>
/// Gets the Plugins. /// Gets the Plugins.
/// </summary> /// </summary>
public IList<LocalPlugin> Plugins => _plugins; public IReadOnlyList<LocalPlugin> Plugins => _plugins;
/// <summary> /// <summary>
/// Returns all the assemblies. /// Returns all the assemblies.
@ -678,7 +678,7 @@ namespace Emby.Server.Implementations.Plugins
var entry = versions[x]; var entry = versions[x];
if (!string.Equals(lastName, entry.Name, StringComparison.OrdinalIgnoreCase)) if (!string.Equals(lastName, entry.Name, StringComparison.OrdinalIgnoreCase))
{ {
entry.DllFiles.AddRange(Directory.EnumerateFiles(entry.Path, "*.dll", SearchOption.AllDirectories)); entry.DllFiles = Directory.GetFiles(entry.Path, "*.dll", SearchOption.AllDirectories);
if (entry.IsEnabledAndSupported) if (entry.IsEnabledAndSupported)
{ {
lastName = entry.Name; lastName = entry.Name;

View File

@ -69,7 +69,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// <summary> /// <summary>
/// The options for the json Serializer. /// The options for the json Serializer.
/// </summary> /// </summary>
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ScheduledTaskWorker" /> class. /// Initializes a new instance of the <see cref="ScheduledTaskWorker" /> class.

View File

@ -79,7 +79,7 @@ namespace Emby.Server.Implementations.Udp
/// Starts the specified port. /// Starts the specified port.
/// </summary> /// </summary>
/// <param name="port">The port.</param> /// <param name="port">The port.</param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken">The cancellation token to cancel operation.</param>
public void Start(int port, CancellationToken cancellationToken) public void Start(int port, CancellationToken cancellationToken)
{ {
_endpoint = new IPEndPoint(IPAddress.Any, port); _endpoint = new IPEndPoint(IPAddress.Any, port);

View File

@ -92,7 +92,7 @@ namespace Emby.Server.Implementations.Updates
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
_config = config; _config = config;
_zipClient = zipClient; _zipClient = zipClient;
_jsonSerializerOptions = JsonDefaults.GetOptions(); _jsonSerializerOptions = JsonDefaults.Options;
_pluginManager = pluginManager; _pluginManager = pluginManager;
} }

View File

@ -122,7 +122,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? height, [FromQuery] int? height,
[FromQuery] int? videoBitRate, [FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex, [FromQuery] int? subtitleStreamIndex,
[FromQuery] SubtitleDeliveryMethod subtitleMethod, [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
[FromQuery] int? maxRefFrames, [FromQuery] int? maxRefFrames,
[FromQuery] int? maxVideoBitDepth, [FromQuery] int? maxVideoBitDepth,
[FromQuery] bool? requireAvc, [FromQuery] bool? requireAvc,
@ -174,7 +174,7 @@ namespace Jellyfin.Api.Controllers
Height = height, Height = height,
VideoBitRate = videoBitRate, VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex, SubtitleStreamIndex = subtitleStreamIndex,
SubtitleMethod = subtitleMethod, SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
MaxRefFrames = maxRefFrames, MaxRefFrames = maxRefFrames,
MaxVideoBitDepth = maxVideoBitDepth, MaxVideoBitDepth = maxVideoBitDepth,
RequireAvc = requireAvc ?? true, RequireAvc = requireAvc ?? true,
@ -287,7 +287,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? height, [FromQuery] int? height,
[FromQuery] int? videoBitRate, [FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex, [FromQuery] int? subtitleStreamIndex,
[FromQuery] SubtitleDeliveryMethod subtitleMethod, [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
[FromQuery] int? maxRefFrames, [FromQuery] int? maxRefFrames,
[FromQuery] int? maxVideoBitDepth, [FromQuery] int? maxVideoBitDepth,
[FromQuery] bool? requireAvc, [FromQuery] bool? requireAvc,
@ -339,7 +339,7 @@ namespace Jellyfin.Api.Controllers
Height = height, Height = height,
VideoBitRate = videoBitRate, VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex, SubtitleStreamIndex = subtitleStreamIndex,
SubtitleMethod = subtitleMethod, SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
MaxRefFrames = maxRefFrames, MaxRefFrames = maxRefFrames,
MaxVideoBitDepth = maxVideoBitDepth, MaxVideoBitDepth = maxVideoBitDepth,
RequireAvc = requireAvc ?? true, RequireAvc = requireAvc ?? true,

View File

@ -25,7 +25,7 @@ namespace Jellyfin.Api.Controllers
private readonly IServerConfigurationManager _configurationManager; private readonly IServerConfigurationManager _configurationManager;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly JsonSerializerOptions _serializerOptions = JsonDefaults.GetOptions(); private readonly JsonSerializerOptions _serializerOptions = JsonDefaults.Options;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ConfigurationController"/> class. /// Initializes a new instance of the <see cref="ConfigurationController"/> class.

View File

@ -95,9 +95,9 @@ namespace Jellyfin.Api.Controllers
return GetPluginPages(plugin).Select(i => new ConfigurationPageInfo(plugin.Instance, i.Item1)); return GetPluginPages(plugin).Select(i => new ConfigurationPageInfo(plugin.Instance, i.Item1));
} }
private IEnumerable<Tuple<PluginPageInfo, IPlugin>> GetPluginPages(LocalPlugin? plugin) private IEnumerable<Tuple<PluginPageInfo, IPlugin>> GetPluginPages(LocalPlugin plugin)
{ {
if (plugin?.Instance is not IHasWebPages hasWebPages) if (plugin.Instance is not IHasWebPages hasWebPages)
{ {
return Enumerable.Empty<Tuple<PluginPageInfo, IPlugin>>(); return Enumerable.Empty<Tuple<PluginPageInfo, IPlugin>>();
} }

View File

@ -203,7 +203,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? height, [FromQuery] int? height,
[FromQuery] int? videoBitRate, [FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex, [FromQuery] int? subtitleStreamIndex,
[FromQuery] SubtitleDeliveryMethod subtitleMethod, [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
[FromQuery] int? maxRefFrames, [FromQuery] int? maxRefFrames,
[FromQuery] int? maxVideoBitDepth, [FromQuery] int? maxVideoBitDepth,
[FromQuery] bool? requireAvc, [FromQuery] bool? requireAvc,
@ -218,7 +218,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? transcodeReasons, [FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex, [FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex, [FromQuery] int? videoStreamIndex,
[FromQuery] EncodingContext context, [FromQuery] EncodingContext? context,
[FromQuery] Dictionary<string, string> streamOptions, [FromQuery] Dictionary<string, string> streamOptions,
[FromQuery] bool enableAdaptiveBitrateStreaming = true) [FromQuery] bool enableAdaptiveBitrateStreaming = true)
{ {
@ -255,7 +255,7 @@ namespace Jellyfin.Api.Controllers
Height = height, Height = height,
VideoBitRate = videoBitRate, VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex, SubtitleStreamIndex = subtitleStreamIndex,
SubtitleMethod = subtitleMethod, SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
MaxRefFrames = maxRefFrames, MaxRefFrames = maxRefFrames,
MaxVideoBitDepth = maxVideoBitDepth, MaxVideoBitDepth = maxVideoBitDepth,
RequireAvc = requireAvc ?? true, RequireAvc = requireAvc ?? true,
@ -270,7 +270,7 @@ namespace Jellyfin.Api.Controllers
TranscodeReasons = transcodeReasons, TranscodeReasons = transcodeReasons,
AudioStreamIndex = audioStreamIndex, AudioStreamIndex = audioStreamIndex,
VideoStreamIndex = videoStreamIndex, VideoStreamIndex = videoStreamIndex,
Context = context, Context = context ?? EncodingContext.Streaming,
StreamOptions = streamOptions, StreamOptions = streamOptions,
EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming
}; };
@ -370,7 +370,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? height, [FromQuery] int? height,
[FromQuery] int? videoBitRate, [FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex, [FromQuery] int? subtitleStreamIndex,
[FromQuery] SubtitleDeliveryMethod subtitleMethod, [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
[FromQuery] int? maxRefFrames, [FromQuery] int? maxRefFrames,
[FromQuery] int? maxVideoBitDepth, [FromQuery] int? maxVideoBitDepth,
[FromQuery] bool? requireAvc, [FromQuery] bool? requireAvc,
@ -385,7 +385,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? transcodeReasons, [FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex, [FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex, [FromQuery] int? videoStreamIndex,
[FromQuery] EncodingContext context, [FromQuery] EncodingContext? context,
[FromQuery] Dictionary<string, string> streamOptions, [FromQuery] Dictionary<string, string> streamOptions,
[FromQuery] bool enableAdaptiveBitrateStreaming = true) [FromQuery] bool enableAdaptiveBitrateStreaming = true)
{ {
@ -422,7 +422,7 @@ namespace Jellyfin.Api.Controllers
Height = height, Height = height,
VideoBitRate = videoBitRate, VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex, SubtitleStreamIndex = subtitleStreamIndex,
SubtitleMethod = subtitleMethod, SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
MaxRefFrames = maxRefFrames, MaxRefFrames = maxRefFrames,
MaxVideoBitDepth = maxVideoBitDepth, MaxVideoBitDepth = maxVideoBitDepth,
RequireAvc = requireAvc ?? true, RequireAvc = requireAvc ?? true,
@ -437,7 +437,7 @@ namespace Jellyfin.Api.Controllers
TranscodeReasons = transcodeReasons, TranscodeReasons = transcodeReasons,
AudioStreamIndex = audioStreamIndex, AudioStreamIndex = audioStreamIndex,
VideoStreamIndex = videoStreamIndex, VideoStreamIndex = videoStreamIndex,
Context = context, Context = context ?? EncodingContext.Streaming,
StreamOptions = streamOptions, StreamOptions = streamOptions,
EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming
}; };
@ -533,7 +533,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? height, [FromQuery] int? height,
[FromQuery] int? videoBitRate, [FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex, [FromQuery] int? subtitleStreamIndex,
[FromQuery] SubtitleDeliveryMethod subtitleMethod, [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
[FromQuery] int? maxRefFrames, [FromQuery] int? maxRefFrames,
[FromQuery] int? maxVideoBitDepth, [FromQuery] int? maxVideoBitDepth,
[FromQuery] bool? requireAvc, [FromQuery] bool? requireAvc,
@ -548,7 +548,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? transcodeReasons, [FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex, [FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex, [FromQuery] int? videoStreamIndex,
[FromQuery] EncodingContext context, [FromQuery] EncodingContext? context,
[FromQuery] Dictionary<string, string> streamOptions) [FromQuery] Dictionary<string, string> streamOptions)
{ {
var cancellationTokenSource = new CancellationTokenSource(); var cancellationTokenSource = new CancellationTokenSource();
@ -585,7 +585,7 @@ namespace Jellyfin.Api.Controllers
Height = height, Height = height,
VideoBitRate = videoBitRate, VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex, SubtitleStreamIndex = subtitleStreamIndex,
SubtitleMethod = subtitleMethod, SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
MaxRefFrames = maxRefFrames, MaxRefFrames = maxRefFrames,
MaxVideoBitDepth = maxVideoBitDepth, MaxVideoBitDepth = maxVideoBitDepth,
RequireAvc = requireAvc ?? true, RequireAvc = requireAvc ?? true,
@ -600,7 +600,7 @@ namespace Jellyfin.Api.Controllers
TranscodeReasons = transcodeReasons, TranscodeReasons = transcodeReasons,
AudioStreamIndex = audioStreamIndex, AudioStreamIndex = audioStreamIndex,
VideoStreamIndex = videoStreamIndex, VideoStreamIndex = videoStreamIndex,
Context = context, Context = context ?? EncodingContext.Streaming,
StreamOptions = streamOptions StreamOptions = streamOptions
}; };
@ -698,7 +698,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? height, [FromQuery] int? height,
[FromQuery] int? videoBitRate, [FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex, [FromQuery] int? subtitleStreamIndex,
[FromQuery] SubtitleDeliveryMethod subtitleMethod, [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
[FromQuery] int? maxRefFrames, [FromQuery] int? maxRefFrames,
[FromQuery] int? maxVideoBitDepth, [FromQuery] int? maxVideoBitDepth,
[FromQuery] bool? requireAvc, [FromQuery] bool? requireAvc,
@ -713,7 +713,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? transcodeReasons, [FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex, [FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex, [FromQuery] int? videoStreamIndex,
[FromQuery] EncodingContext context, [FromQuery] EncodingContext? context,
[FromQuery] Dictionary<string, string> streamOptions) [FromQuery] Dictionary<string, string> streamOptions)
{ {
var cancellationTokenSource = new CancellationTokenSource(); var cancellationTokenSource = new CancellationTokenSource();
@ -750,7 +750,7 @@ namespace Jellyfin.Api.Controllers
Height = height, Height = height,
VideoBitRate = videoBitRate, VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex, SubtitleStreamIndex = subtitleStreamIndex,
SubtitleMethod = subtitleMethod, SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
MaxRefFrames = maxRefFrames, MaxRefFrames = maxRefFrames,
MaxVideoBitDepth = maxVideoBitDepth, MaxVideoBitDepth = maxVideoBitDepth,
RequireAvc = requireAvc ?? true, RequireAvc = requireAvc ?? true,
@ -765,7 +765,7 @@ namespace Jellyfin.Api.Controllers
TranscodeReasons = transcodeReasons, TranscodeReasons = transcodeReasons,
AudioStreamIndex = audioStreamIndex, AudioStreamIndex = audioStreamIndex,
VideoStreamIndex = videoStreamIndex, VideoStreamIndex = videoStreamIndex,
Context = context, Context = context ?? EncodingContext.Streaming,
StreamOptions = streamOptions StreamOptions = streamOptions
}; };
@ -868,7 +868,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? height, [FromQuery] int? height,
[FromQuery] int? videoBitRate, [FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex, [FromQuery] int? subtitleStreamIndex,
[FromQuery] SubtitleDeliveryMethod subtitleMethod, [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
[FromQuery] int? maxRefFrames, [FromQuery] int? maxRefFrames,
[FromQuery] int? maxVideoBitDepth, [FromQuery] int? maxVideoBitDepth,
[FromQuery] bool? requireAvc, [FromQuery] bool? requireAvc,
@ -883,7 +883,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? transcodeReasons, [FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex, [FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex, [FromQuery] int? videoStreamIndex,
[FromQuery] EncodingContext context, [FromQuery] EncodingContext? context,
[FromQuery] Dictionary<string, string> streamOptions) [FromQuery] Dictionary<string, string> streamOptions)
{ {
var streamingRequest = new VideoRequestDto var streamingRequest = new VideoRequestDto
@ -920,7 +920,7 @@ namespace Jellyfin.Api.Controllers
Height = height, Height = height,
VideoBitRate = videoBitRate, VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex, SubtitleStreamIndex = subtitleStreamIndex,
SubtitleMethod = subtitleMethod, SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
MaxRefFrames = maxRefFrames, MaxRefFrames = maxRefFrames,
MaxVideoBitDepth = maxVideoBitDepth, MaxVideoBitDepth = maxVideoBitDepth,
RequireAvc = requireAvc ?? true, RequireAvc = requireAvc ?? true,
@ -935,7 +935,7 @@ namespace Jellyfin.Api.Controllers
TranscodeReasons = transcodeReasons, TranscodeReasons = transcodeReasons,
AudioStreamIndex = audioStreamIndex, AudioStreamIndex = audioStreamIndex,
VideoStreamIndex = videoStreamIndex, VideoStreamIndex = videoStreamIndex,
Context = context, Context = context ?? EncodingContext.Streaming,
StreamOptions = streamOptions StreamOptions = streamOptions
}; };
@ -1040,7 +1040,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? height, [FromQuery] int? height,
[FromQuery] int? videoBitRate, [FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex, [FromQuery] int? subtitleStreamIndex,
[FromQuery] SubtitleDeliveryMethod subtitleMethod, [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
[FromQuery] int? maxRefFrames, [FromQuery] int? maxRefFrames,
[FromQuery] int? maxVideoBitDepth, [FromQuery] int? maxVideoBitDepth,
[FromQuery] bool? requireAvc, [FromQuery] bool? requireAvc,
@ -1055,7 +1055,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? transcodeReasons, [FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex, [FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex, [FromQuery] int? videoStreamIndex,
[FromQuery] EncodingContext context, [FromQuery] EncodingContext? context,
[FromQuery] Dictionary<string, string> streamOptions) [FromQuery] Dictionary<string, string> streamOptions)
{ {
var streamingRequest = new StreamingRequestDto var streamingRequest = new StreamingRequestDto
@ -1092,7 +1092,7 @@ namespace Jellyfin.Api.Controllers
Height = height, Height = height,
VideoBitRate = videoBitRate, VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex, SubtitleStreamIndex = subtitleStreamIndex,
SubtitleMethod = subtitleMethod, SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
MaxRefFrames = maxRefFrames, MaxRefFrames = maxRefFrames,
MaxVideoBitDepth = maxVideoBitDepth, MaxVideoBitDepth = maxVideoBitDepth,
RequireAvc = requireAvc ?? true, RequireAvc = requireAvc ?? true,
@ -1107,7 +1107,7 @@ namespace Jellyfin.Api.Controllers
TranscodeReasons = transcodeReasons, TranscodeReasons = transcodeReasons,
AudioStreamIndex = audioStreamIndex, AudioStreamIndex = audioStreamIndex,
VideoStreamIndex = videoStreamIndex, VideoStreamIndex = videoStreamIndex,
Context = context, Context = context ?? EncodingContext.Streaming,
StreamOptions = streamOptions StreamOptions = streamOptions
}; };

View File

@ -96,7 +96,9 @@ namespace Jellyfin.Api.Controllers
[HttpDelete("Videos/ActiveEncodings")] [HttpDelete("Videos/ActiveEncodings")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult StopEncodingProcess([FromQuery] string deviceId, [FromQuery] string playSessionId) public ActionResult StopEncodingProcess(
[FromQuery, Required] string deviceId,
[FromQuery, Required] string playSessionId)
{ {
_transcodingJobHelper.KillTranscodingJobs(deviceId, playSessionId, path => true); _transcodingJobHelper.KillTranscodingJobs(deviceId, playSessionId, path => true);
return NoContent(); return NoContent();

View File

@ -402,7 +402,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] Guid itemId, [FromRoute, Required] Guid itemId,
[FromRoute, Required] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromRoute, Required] int imageIndex, [FromRoute, Required] int imageIndex,
[FromQuery] int newIndex) [FromQuery, Required] int newIndex)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById(itemId);
if (item == null) if (item == null)
@ -751,7 +751,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult> GetArtistImage( public async Task<ActionResult> GetArtistImage(
[FromRoute, Required] string name, [FromRoute, Required] string name,
[FromRoute, Required] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromQuery] string tag, [FromQuery] string? tag,
[FromQuery] ImageFormat? format, [FromQuery] ImageFormat? format,
[FromQuery] int? maxWidth, [FromQuery] int? maxWidth,
[FromQuery] int? maxHeight, [FromQuery] int? maxHeight,
@ -830,7 +830,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult> GetGenreImage( public async Task<ActionResult> GetGenreImage(
[FromRoute, Required] string name, [FromRoute, Required] string name,
[FromRoute, Required] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromQuery] string tag, [FromQuery] string? tag,
[FromQuery] ImageFormat? format, [FromQuery] ImageFormat? format,
[FromQuery] int? maxWidth, [FromQuery] int? maxWidth,
[FromQuery] int? maxHeight, [FromQuery] int? maxHeight,
@ -910,7 +910,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] string name, [FromRoute, Required] string name,
[FromRoute, Required] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromRoute, Required] int imageIndex, [FromRoute, Required] int imageIndex,
[FromQuery] string tag, [FromQuery] string? tag,
[FromQuery] ImageFormat? format, [FromQuery] ImageFormat? format,
[FromQuery] int? maxWidth, [FromQuery] int? maxWidth,
[FromQuery] int? maxHeight, [FromQuery] int? maxHeight,
@ -988,7 +988,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult> GetMusicGenreImage( public async Task<ActionResult> GetMusicGenreImage(
[FromRoute, Required] string name, [FromRoute, Required] string name,
[FromRoute, Required] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromQuery] string tag, [FromQuery] string? tag,
[FromQuery] ImageFormat? format, [FromQuery] ImageFormat? format,
[FromQuery] int? maxWidth, [FromQuery] int? maxWidth,
[FromQuery] int? maxHeight, [FromQuery] int? maxHeight,
@ -1068,7 +1068,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] string name, [FromRoute, Required] string name,
[FromRoute, Required] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromRoute, Required] int imageIndex, [FromRoute, Required] int imageIndex,
[FromQuery] string tag, [FromQuery] string? tag,
[FromQuery] ImageFormat? format, [FromQuery] ImageFormat? format,
[FromQuery] int? maxWidth, [FromQuery] int? maxWidth,
[FromQuery] int? maxHeight, [FromQuery] int? maxHeight,
@ -1146,7 +1146,7 @@ namespace Jellyfin.Api.Controllers
public async Task<ActionResult> GetPersonImage( public async Task<ActionResult> GetPersonImage(
[FromRoute, Required] string name, [FromRoute, Required] string name,
[FromRoute, Required] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromQuery] string tag, [FromQuery] string? tag,
[FromQuery] ImageFormat? format, [FromQuery] ImageFormat? format,
[FromQuery] int? maxWidth, [FromQuery] int? maxWidth,
[FromQuery] int? maxHeight, [FromQuery] int? maxHeight,
@ -1226,7 +1226,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] string name, [FromRoute, Required] string name,
[FromRoute, Required] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromRoute, Required] int imageIndex, [FromRoute, Required] int imageIndex,
[FromQuery] string tag, [FromQuery] string? tag,
[FromQuery] ImageFormat? format, [FromQuery] ImageFormat? format,
[FromQuery] int? maxWidth, [FromQuery] int? maxWidth,
[FromQuery] int? maxHeight, [FromQuery] int? maxHeight,

View File

@ -86,7 +86,7 @@ namespace Jellyfin.Api.Controllers
} }
/// <summary> /// <summary>
/// Creates an instant playlist based on a given song. /// Creates an instant playlist based on a given album.
/// </summary> /// </summary>
/// <param name="id">The item id.</param> /// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param> /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
@ -122,7 +122,7 @@ namespace Jellyfin.Api.Controllers
} }
/// <summary> /// <summary>
/// Creates an instant playlist based on a given song. /// Creates an instant playlist based on a given playlist.
/// </summary> /// </summary>
/// <param name="id">The item id.</param> /// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param> /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
@ -158,7 +158,7 @@ namespace Jellyfin.Api.Controllers
} }
/// <summary> /// <summary>
/// Creates an instant playlist based on a given song. /// Creates an instant playlist based on a given genre.
/// </summary> /// </summary>
/// <param name="name">The genre name.</param> /// <param name="name">The genre name.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param> /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
@ -172,7 +172,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns> /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("MusicGenres/{name}/InstantMix")] [HttpGet("MusicGenres/{name}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenre( public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenreByName(
[FromRoute, Required] string name, [FromRoute, Required] string name,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] int? limit, [FromQuery] int? limit,
@ -193,7 +193,7 @@ namespace Jellyfin.Api.Controllers
} }
/// <summary> /// <summary>
/// Creates an instant playlist based on a given song. /// Creates an instant playlist based on a given artist.
/// </summary> /// </summary>
/// <param name="id">The item id.</param> /// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param> /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
@ -229,7 +229,7 @@ namespace Jellyfin.Api.Controllers
} }
/// <summary> /// <summary>
/// Creates an instant playlist based on a given song. /// Creates an instant playlist based on a given genre.
/// </summary> /// </summary>
/// <param name="id">The item id.</param> /// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param> /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
@ -243,7 +243,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns> /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("MusicGenres/{id}/InstantMix")] [HttpGet("MusicGenres/{id}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenres( public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenreById(
[FromRoute, Required] Guid id, [FromRoute, Required] Guid id,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] int? limit, [FromQuery] int? limit,
@ -265,7 +265,7 @@ namespace Jellyfin.Api.Controllers
} }
/// <summary> /// <summary>
/// Creates an instant playlist based on a given song. /// Creates an instant playlist based on a given item.
/// </summary> /// </summary>
/// <param name="id">The item id.</param> /// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param> /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
@ -300,6 +300,80 @@ namespace Jellyfin.Api.Controllers
return GetResult(items, user, limit, dtoOptions); return GetResult(items, user, limit, dtoOptions);
} }
/// <summary>
/// Creates an instant playlist based on a given artist.
/// </summary>
/// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param>
/// <param name="limit">Optional. The maximum number of records to return.</param>
/// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="enableImages">Optional. Include image information in output.</param>
/// <param name="enableUserData">Optional. Include user data.</param>
/// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
/// <response code="200">Instant playlist returned.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("Artists/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
[Obsolete("Use GetInstantMixFromArtists")]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists2(
[FromQuery, Required] Guid id,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableImages,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{
return GetInstantMixFromArtists(
id,
userId,
limit,
fields,
enableImages,
enableUserData,
imageTypeLimit,
enableImageTypes);
}
/// <summary>
/// Creates an instant playlist based on a given genre.
/// </summary>
/// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param>
/// <param name="limit">Optional. The maximum number of records to return.</param>
/// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="enableImages">Optional. Include image information in output.</param>
/// <param name="enableUserData">Optional. Include user data.</param>
/// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
/// <response code="200">Instant playlist returned.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("MusicGenres/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
[Obsolete("Use GetInstantMixFromMusicGenres instead")]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenreById2(
[FromQuery, Required] Guid id,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] bool? enableImages,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{
return GetInstantMixFromMusicGenreById(
id,
userId,
limit,
fields,
enableImages,
enableUserData,
imageTypeLimit,
enableImageTypes);
}
private QueryResult<BaseItemDto> GetResult(List<BaseItem> items, User? user, int? limit, DtoOptions dtoOptions) private QueryResult<BaseItemDto> GetResult(List<BaseItem> items, User? user, int? limit, DtoOptions dtoOptions)
{ {
var list = items; var list = items;

View File

@ -344,11 +344,12 @@ namespace Jellyfin.Api.Controllers
Directory.CreateDirectory(directory); Directory.CreateDirectory(directory);
using (var stream = result.Content) using (var stream = result.Content)
{ {
// use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
await using var fileStream = new FileStream( await using var fileStream = new FileStream(
fullCachePath, fullCachePath,
FileMode.Create, FileMode.Create,
FileAccess.Write, FileAccess.Write,
FileShare.Read, FileShare.None,
IODefaults.FileStreamBufferSize, IODefaults.FileStreamBufferSize,
true); true);

View File

@ -195,7 +195,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Items/{itemId}/ContentType")] [HttpPost("Items/{itemId}/ContentType")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult UpdateItemContentType([FromRoute, Required] Guid itemId, [FromQuery] string contentType) public ActionResult UpdateItemContentType([FromRoute, Required] Guid itemId, [FromQuery] string? contentType)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById(itemId);
if (item == null) if (item == null)

View File

@ -303,7 +303,7 @@ namespace Jellyfin.Api.Controllers
/// </summary> /// </summary>
/// <response code="204">Library scan started.</response> /// <response code="204">Library scan started.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpGet("Library/Refresh")] [HttpPost("Library/Refresh")]
[Authorize(Policy = Policies.RequiresElevation)] [Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> RefreshLibrary() public async Task<ActionResult> RefreshLibrary()
@ -590,15 +590,15 @@ namespace Jellyfin.Api.Controllers
/// <summary> /// <summary>
/// Reports that new movies have been added by an external source. /// Reports that new movies have been added by an external source.
/// </summary> /// </summary>
/// <param name="updates">A list of updated media paths.</param> /// <param name="dto">The update paths.</param>
/// <response code="204">Report success.</response> /// <response code="204">Report success.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Library/Media/Updated")] [HttpPost("Library/Media/Updated")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult PostUpdatedMedia([FromBody, Required] MediaUpdateInfoDto[] updates) public ActionResult PostUpdatedMedia([FromBody, Required] MediaUpdateInfoDto dto)
{ {
foreach (var item in updates) foreach (var item in dto.Updates)
{ {
_libraryMonitor.ReportFileSystemChanged(item.Path); _libraryMonitor.ReportFileSystemChanged(item.Path);
} }
@ -777,7 +777,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<LibraryOptionsResultDto> GetLibraryOptionsInfo( public ActionResult<LibraryOptionsResultDto> GetLibraryOptionsInfo(
[FromQuery] string? libraryContentType, [FromQuery] string? libraryContentType,
[FromQuery] bool isNewLibrary) [FromQuery] bool isNewLibrary = false)
{ {
var result = new LibraryOptionsResultDto(); var result = new LibraryOptionsResultDto();

View File

@ -241,23 +241,20 @@ namespace Jellyfin.Api.Controllers
/// <summary> /// <summary>
/// Updates a media path. /// Updates a media path.
/// </summary> /// </summary>
/// <param name="name">The name of the library.</param> /// <param name="mediaPathRequestDto">The name of the library and path infos.</param>
/// <param name="pathInfo">The path info.</param>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
/// <response code="204">Media path updated.</response> /// <response code="204">Media path updated.</response>
/// <exception cref="ArgumentNullException">The name of the library may not be empty.</exception> /// <exception cref="ArgumentNullException">The name of the library may not be empty.</exception>
[HttpPost("Paths/Update")] [HttpPost("Paths/Update")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult UpdateMediaPath( public ActionResult UpdateMediaPath([FromBody, Required] UpdateMediaPathRequestDto mediaPathRequestDto)
[FromQuery] string? name,
[FromBody] MediaPathInfo? pathInfo)
{ {
if (string.IsNullOrWhiteSpace(name)) if (string.IsNullOrWhiteSpace(mediaPathRequestDto.Name))
{ {
throw new ArgumentNullException(nameof(name)); throw new ArgumentNullException(nameof(mediaPathRequestDto), "Name must not be null or empty");
} }
_libraryManager.UpdateMediaPath(name, pathInfo); _libraryManager.UpdateMediaPath(mediaPathRequestDto.Name, mediaPathRequestDto.PathInfo);
return NoContent(); return NoContent();
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
@ -86,26 +87,19 @@ namespace Jellyfin.Api.Controllers
/// <summary> /// <summary>
/// Sends a notification to all admins. /// Sends a notification to all admins.
/// </summary> /// </summary>
/// <param name="url">The URL of the notification.</param> /// <param name="notificationDto">The notification request.</param>
/// <param name="level">The level of the notification.</param>
/// <param name="name">The name of the notification.</param>
/// <param name="description">The description of the notification.</param>
/// <response code="204">Notification sent.</response> /// <response code="204">Notification sent.</response>
/// <returns>A <cref see="NoContentResult"/>.</returns> /// <returns>A <cref see="NoContentResult"/>.</returns>
[HttpPost("Admin")] [HttpPost("Admin")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult CreateAdminNotification( public ActionResult CreateAdminNotification([FromBody, Required] AdminNotificationDto notificationDto)
[FromQuery] string? url,
[FromQuery] NotificationLevel? level,
[FromQuery] string name = "",
[FromQuery] string description = "")
{ {
var notification = new NotificationRequest var notification = new NotificationRequest
{ {
Name = name, Name = notificationDto.Name,
Description = description, Description = notificationDto.Description,
Url = url, Url = notificationDto.Url,
Level = level ?? NotificationLevel.Normal, Level = notificationDto.NotificationLevel ?? NotificationLevel.Normal,
UserIds = _userManager.Users UserIds = _userManager.Users
.Where(user => user.HasPermission(PermissionKind.IsAdministrator)) .Where(user => user.HasPermission(PermissionKind.IsAdministrator))
.Select(user => user.Id) .Select(user => user.Id)
@ -114,7 +108,6 @@ namespace Jellyfin.Api.Controllers
}; };
_notificationManager.SendNotification(notification, CancellationToken.None); _notificationManager.SendNotification(notification, CancellationToken.None);
return NoContent(); return NoContent();
} }

View File

@ -152,7 +152,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/Playing/Ping")] [HttpPost("Sessions/Playing/Ping")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult PingPlaybackSession([FromQuery] string playSessionId) public ActionResult PingPlaybackSession([FromQuery, Required] string playSessionId)
{ {
_transcodingJobHelper.PingTranscodingJob(playSessionId, null); _transcodingJobHelper.PingTranscodingJob(playSessionId, null);
return NoContent(); return NoContent();
@ -202,9 +202,9 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? mediaSourceId, [FromQuery] string? mediaSourceId,
[FromQuery] int? audioStreamIndex, [FromQuery] int? audioStreamIndex,
[FromQuery] int? subtitleStreamIndex, [FromQuery] int? subtitleStreamIndex,
[FromQuery] PlayMethod playMethod, [FromQuery] PlayMethod? playMethod,
[FromQuery] string? liveStreamId, [FromQuery] string? liveStreamId,
[FromQuery] string playSessionId, [FromQuery] string? playSessionId,
[FromQuery] bool canSeek = false) [FromQuery] bool canSeek = false)
{ {
var playbackStartInfo = new PlaybackStartInfo var playbackStartInfo = new PlaybackStartInfo
@ -214,7 +214,7 @@ namespace Jellyfin.Api.Controllers
MediaSourceId = mediaSourceId, MediaSourceId = mediaSourceId,
AudioStreamIndex = audioStreamIndex, AudioStreamIndex = audioStreamIndex,
SubtitleStreamIndex = subtitleStreamIndex, SubtitleStreamIndex = subtitleStreamIndex,
PlayMethod = playMethod, PlayMethod = playMethod ?? PlayMethod.Transcode,
PlaySessionId = playSessionId, PlaySessionId = playSessionId,
LiveStreamId = liveStreamId LiveStreamId = liveStreamId
}; };
@ -254,10 +254,10 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? audioStreamIndex, [FromQuery] int? audioStreamIndex,
[FromQuery] int? subtitleStreamIndex, [FromQuery] int? subtitleStreamIndex,
[FromQuery] int? volumeLevel, [FromQuery] int? volumeLevel,
[FromQuery] PlayMethod playMethod, [FromQuery] PlayMethod? playMethod,
[FromQuery] string? liveStreamId, [FromQuery] string? liveStreamId,
[FromQuery] string playSessionId, [FromQuery] string? playSessionId,
[FromQuery] RepeatMode repeatMode, [FromQuery] RepeatMode? repeatMode,
[FromQuery] bool isPaused = false, [FromQuery] bool isPaused = false,
[FromQuery] bool isMuted = false) [FromQuery] bool isMuted = false)
{ {
@ -271,10 +271,10 @@ namespace Jellyfin.Api.Controllers
AudioStreamIndex = audioStreamIndex, AudioStreamIndex = audioStreamIndex,
SubtitleStreamIndex = subtitleStreamIndex, SubtitleStreamIndex = subtitleStreamIndex,
VolumeLevel = volumeLevel, VolumeLevel = volumeLevel,
PlayMethod = playMethod, PlayMethod = playMethod ?? PlayMethod.Transcode,
PlaySessionId = playSessionId, PlaySessionId = playSessionId,
LiveStreamId = liveStreamId, LiveStreamId = liveStreamId,
RepeatMode = repeatMode RepeatMode = repeatMode ?? RepeatMode.RepeatNone
}; };
playbackProgressInfo.PlayMethod = ValidatePlayMethod(playbackProgressInfo.PlayMethod, playbackProgressInfo.PlaySessionId); playbackProgressInfo.PlayMethod = ValidatePlayMethod(playbackProgressInfo.PlayMethod, playbackProgressInfo.PlaySessionId);
@ -352,7 +352,7 @@ namespace Jellyfin.Api.Controllers
return _userDataRepository.GetUserDataDto(item, user); return _userDataRepository.GetUserDataDto(item, user);
} }
private PlayMethod ValidatePlayMethod(PlayMethod method, string playSessionId) private PlayMethod ValidatePlayMethod(PlayMethod method, string? playSessionId)
{ {
if (method == PlayMethod.Transcode) if (method == PlayMethod.Transcode)
{ {

View File

@ -45,7 +45,7 @@ namespace Jellyfin.Api.Controllers
{ {
_installationManager = installationManager; _installationManager = installationManager;
_pluginManager = pluginManager; _pluginManager = pluginManager;
_serializerOptions = JsonDefaults.GetOptions(); _serializerOptions = JsonDefaults.Options;
_config = config; _config = config;
} }

View File

@ -259,7 +259,8 @@ namespace Jellyfin.Api.Controllers
var fullCacheDirectory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid."); var fullCacheDirectory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid.");
Directory.CreateDirectory(fullCacheDirectory); Directory.CreateDirectory(fullCacheDirectory);
await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, true);
await response.Content.CopyToAsync(fileStream).ConfigureAwait(false); await response.Content.CopyToAsync(fileStream).ConfigureAwait(false);
var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath)); var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath));

View File

@ -112,7 +112,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? maxAudioSampleRate, [FromQuery] int? maxAudioSampleRate,
[FromQuery] int? maxAudioBitDepth, [FromQuery] int? maxAudioBitDepth,
[FromQuery] bool? enableRemoteMedia, [FromQuery] bool? enableRemoteMedia,
[FromQuery] bool breakOnNonKeyFrames, [FromQuery] bool breakOnNonKeyFrames = false,
[FromQuery] bool enableRedirection = true) [FromQuery] bool enableRedirection = true)
{ {
var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels); var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels);

View File

@ -198,7 +198,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? height, [FromQuery] int? height,
[FromQuery] int? videoBitRate, [FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex, [FromQuery] int? subtitleStreamIndex,
[FromQuery] SubtitleDeliveryMethod subtitleMethod, [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
[FromQuery] int? maxRefFrames, [FromQuery] int? maxRefFrames,
[FromQuery] int? maxVideoBitDepth, [FromQuery] int? maxVideoBitDepth,
[FromQuery] bool? requireAvc, [FromQuery] bool? requireAvc,
@ -213,7 +213,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? transcodeReasons, [FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex, [FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex, [FromQuery] int? videoStreamIndex,
[FromQuery] EncodingContext context, [FromQuery] EncodingContext? context,
[FromQuery] Dictionary<string, string> streamOptions, [FromQuery] Dictionary<string, string> streamOptions,
[FromQuery] int? maxWidth, [FromQuery] int? maxWidth,
[FromQuery] int? maxHeight, [FromQuery] int? maxHeight,
@ -253,7 +253,7 @@ namespace Jellyfin.Api.Controllers
Height = height, Height = height,
VideoBitRate = videoBitRate, VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex, SubtitleStreamIndex = subtitleStreamIndex,
SubtitleMethod = subtitleMethod, SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
MaxRefFrames = maxRefFrames, MaxRefFrames = maxRefFrames,
MaxVideoBitDepth = maxVideoBitDepth, MaxVideoBitDepth = maxVideoBitDepth,
RequireAvc = requireAvc ?? true, RequireAvc = requireAvc ?? true,
@ -268,7 +268,7 @@ namespace Jellyfin.Api.Controllers
TranscodeReasons = transcodeReasons, TranscodeReasons = transcodeReasons,
AudioStreamIndex = audioStreamIndex, AudioStreamIndex = audioStreamIndex,
VideoStreamIndex = videoStreamIndex, VideoStreamIndex = videoStreamIndex,
Context = context, Context = context ?? EncodingContext.Streaming,
StreamOptions = streamOptions, StreamOptions = streamOptions,
MaxHeight = maxHeight, MaxHeight = maxHeight,
MaxWidth = maxWidth, MaxWidth = maxWidth,

View File

@ -217,9 +217,7 @@ namespace Jellyfin.Api.Controllers
return BadRequest("Please supply at least two videos to merge."); return BadRequest("Please supply at least two videos to merge.");
} }
var videosWithVersions = items.Where(i => i.MediaSourceCount > 1).ToList(); var primaryVersion = items.FirstOrDefault(i => i.MediaSourceCount > 1 && string.IsNullOrEmpty(i.PrimaryVersionId));
var primaryVersion = videosWithVersions.FirstOrDefault();
if (primaryVersion == null) if (primaryVersion == null)
{ {
primaryVersion = items primaryVersion = items
@ -364,7 +362,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? height, [FromQuery] int? height,
[FromQuery] int? videoBitRate, [FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex, [FromQuery] int? subtitleStreamIndex,
[FromQuery] SubtitleDeliveryMethod subtitleMethod, [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
[FromQuery] int? maxRefFrames, [FromQuery] int? maxRefFrames,
[FromQuery] int? maxVideoBitDepth, [FromQuery] int? maxVideoBitDepth,
[FromQuery] bool? requireAvc, [FromQuery] bool? requireAvc,
@ -379,7 +377,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? transcodeReasons, [FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex, [FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex, [FromQuery] int? videoStreamIndex,
[FromQuery] EncodingContext context, [FromQuery] EncodingContext? context,
[FromQuery] Dictionary<string, string> streamOptions) [FromQuery] Dictionary<string, string> streamOptions)
{ {
var isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head; var isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head;
@ -418,7 +416,7 @@ namespace Jellyfin.Api.Controllers
Height = height, Height = height,
VideoBitRate = videoBitRate, VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex, SubtitleStreamIndex = subtitleStreamIndex,
SubtitleMethod = subtitleMethod, SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
MaxRefFrames = maxRefFrames, MaxRefFrames = maxRefFrames,
MaxVideoBitDepth = maxVideoBitDepth, MaxVideoBitDepth = maxVideoBitDepth,
RequireAvc = requireAvc ?? true, RequireAvc = requireAvc ?? true,
@ -433,7 +431,7 @@ namespace Jellyfin.Api.Controllers
TranscodeReasons = transcodeReasons, TranscodeReasons = transcodeReasons,
AudioStreamIndex = audioStreamIndex, AudioStreamIndex = audioStreamIndex,
VideoStreamIndex = videoStreamIndex, VideoStreamIndex = videoStreamIndex,
Context = context, Context = context ?? EncodingContext.Streaming,
StreamOptions = streamOptions StreamOptions = streamOptions
}; };
@ -620,7 +618,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? height, [FromQuery] int? height,
[FromQuery] int? videoBitRate, [FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex, [FromQuery] int? subtitleStreamIndex,
[FromQuery] SubtitleDeliveryMethod subtitleMethod, [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
[FromQuery] int? maxRefFrames, [FromQuery] int? maxRefFrames,
[FromQuery] int? maxVideoBitDepth, [FromQuery] int? maxVideoBitDepth,
[FromQuery] bool? requireAvc, [FromQuery] bool? requireAvc,
@ -635,7 +633,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? transcodeReasons, [FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex, [FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex, [FromQuery] int? videoStreamIndex,
[FromQuery] EncodingContext context, [FromQuery] EncodingContext? context,
[FromQuery] Dictionary<string, string> streamOptions) [FromQuery] Dictionary<string, string> streamOptions)
{ {
return GetVideoStream( return GetVideoStream(

View File

@ -107,7 +107,8 @@ namespace Jellyfin.Api.Helpers
// Headers only // Headers only
if (isHeadRequest) if (isHeadRequest)
{ {
return new FileContentResult(Array.Empty<byte>(), contentType); httpContext.Response.Headers[HeaderNames.ContentType] = contentType;
return new OkResult();
} }
var transcodingLock = transcodingJobHelper.GetTranscodingLock(outputPath); var transcodingLock = transcodingJobHelper.GetTranscodingLock(outputPath);

View File

@ -508,17 +508,15 @@ namespace Jellyfin.Api.Helpers
private static void ApplyDeviceProfileSettings(StreamState state, IDlnaManager dlnaManager, IDeviceManager deviceManager, HttpRequest request, string? deviceProfileId, bool? @static) private static void ApplyDeviceProfileSettings(StreamState state, IDlnaManager dlnaManager, IDeviceManager deviceManager, HttpRequest request, string? deviceProfileId, bool? @static)
{ {
var headers = request.Headers;
if (!string.IsNullOrWhiteSpace(deviceProfileId)) if (!string.IsNullOrWhiteSpace(deviceProfileId))
{ {
state.DeviceProfile = dlnaManager.GetProfile(deviceProfileId); state.DeviceProfile = dlnaManager.GetProfile(deviceProfileId);
}
else if (!string.IsNullOrWhiteSpace(deviceProfileId)) if (state.DeviceProfile == null)
{ {
var caps = deviceManager.GetCapabilities(deviceProfileId); var caps = deviceManager.GetCapabilities(deviceProfileId);
state.DeviceProfile = caps == null ? dlnaManager.GetProfile(request.Headers) : caps.DeviceProfile;
state.DeviceProfile = caps == null ? dlnaManager.GetProfile(headers) : caps.DeviceProfile; }
} }
var profile = state.DeviceProfile; var profile = state.DeviceProfile;

View File

@ -28,7 +28,6 @@
<!-- Code Analyzers--> <!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />

View File

@ -1,4 +1,7 @@
namespace Jellyfin.Api.Models.LibraryDtos using System;
using System.Collections.Generic;
namespace Jellyfin.Api.Models.LibraryDtos
{ {
/// <summary> /// <summary>
/// Media Update Info Dto. /// Media Update Info Dto.
@ -6,14 +9,8 @@
public class MediaUpdateInfoDto public class MediaUpdateInfoDto
{ {
/// <summary> /// <summary>
/// Gets or sets media path. /// Gets or sets the list of updates.
/// </summary> /// </summary>
public string? Path { get; set; } public IReadOnlyList<MediaUpdateInfoPathDto> Updates { get; set; } = Array.Empty<MediaUpdateInfoPathDto>();
/// <summary>
/// Gets or sets media update type.
/// Created, Modified, Deleted.
/// </summary>
public string? UpdateType { get; set; }
} }
} }

View File

@ -0,0 +1,19 @@
namespace Jellyfin.Api.Models.LibraryDtos
{
/// <summary>
/// The media update info path.
/// </summary>
public class MediaUpdateInfoPathDto
{
/// <summary>
/// Gets or sets media path.
/// </summary>
public string? Path { get; set; }
/// <summary>
/// Gets or sets media update type.
/// Created, Modified, Deleted.
/// </summary>
public string? UpdateType { get; set; }
}
}

View File

@ -0,0 +1,23 @@
using System.ComponentModel.DataAnnotations;
using MediaBrowser.Model.Configuration;
namespace Jellyfin.Api.Models.LibraryStructureDto
{
/// <summary>
/// Update library options dto.
/// </summary>
public class UpdateMediaPathRequestDto
{
/// <summary>
/// Gets or sets the library name.
/// </summary>
[Required]
public string Name { get; set; } = null!;
/// <summary>
/// Gets or sets library folder path information.
/// </summary>
[Required]
public MediaPathInfo PathInfo { get; set; } = null!;
}
}

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using MediaBrowser.Common.Json.Converters; using MediaBrowser.Common.Json.Converters;

View File

@ -0,0 +1,30 @@
using MediaBrowser.Model.Notifications;
namespace Jellyfin.Api.Models.NotificationDtos
{
/// <summary>
/// The admin notification dto.
/// </summary>
public class AdminNotificationDto
{
/// <summary>
/// Gets or sets the notification name.
/// </summary>
public string? Name { get; set; }
/// <summary>
/// Gets or sets the notification description.
/// </summary>
public string? Description { get; set; }
/// <summary>
/// Gets or sets the notification level.
/// </summary>
public NotificationLevel? NotificationLevel { get; set; }
/// <summary>
/// Gets or sets the notification url.
/// </summary>
public string? Url { get; set; }
}
}

View File

@ -1,67 +1,21 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
using System.Collections.Generic;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
namespace Jellyfin.Data namespace Jellyfin.Data
{ {
public static class DayOfWeekHelper public static class DayOfWeekHelper
{ {
public static List<DayOfWeek> GetDaysOfWeek(DynamicDayOfWeek day) public static DayOfWeek[] GetDaysOfWeek(DynamicDayOfWeek day)
{ {
var days = new List<DayOfWeek>(7); return day switch
if (day == DynamicDayOfWeek.Sunday
|| day == DynamicDayOfWeek.Weekend
|| day == DynamicDayOfWeek.Everyday)
{ {
days.Add(DayOfWeek.Sunday); DynamicDayOfWeek.Everyday => new[] { DayOfWeek.Sunday, DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday, DayOfWeek.Saturday },
} DynamicDayOfWeek.Weekday => new[] { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday },
DynamicDayOfWeek.Weekend => new[] { DayOfWeek.Sunday, DayOfWeek.Saturday },
if (day == DynamicDayOfWeek.Monday _ => new[] { (DayOfWeek)day }
|| day == DynamicDayOfWeek.Weekday };
|| day == DynamicDayOfWeek.Everyday)
{
days.Add(DayOfWeek.Monday);
}
if (day == DynamicDayOfWeek.Tuesday
|| day == DynamicDayOfWeek.Weekday
|| day == DynamicDayOfWeek.Everyday)
{
days.Add(DayOfWeek.Tuesday);
}
if (day == DynamicDayOfWeek.Wednesday
|| day == DynamicDayOfWeek.Weekday
|| day == DynamicDayOfWeek.Everyday)
{
days.Add(DayOfWeek.Wednesday);
}
if (day == DynamicDayOfWeek.Thursday
|| day == DynamicDayOfWeek.Weekday
|| day == DynamicDayOfWeek.Everyday)
{
days.Add(DayOfWeek.Thursday);
}
if (day == DynamicDayOfWeek.Friday
|| day == DynamicDayOfWeek.Weekday
|| day == DynamicDayOfWeek.Everyday)
{
days.Add(DayOfWeek.Friday);
}
if (day == DynamicDayOfWeek.Saturday
|| day == DynamicDayOfWeek.Weekend
|| day == DynamicDayOfWeek.Everyday)
{
days.Add(DayOfWeek.Saturday);
}
return days;
} }
} }
} }

View File

@ -1,3 +1,4 @@
#pragma warning disable CA1711 // Identifiers should not have incorrect suffix
#pragma warning disable CA2227 #pragma warning disable CA2227
using System.Collections.Generic; using System.Collections.Generic;

View File

@ -1,3 +1,5 @@
#pragma warning disable CA1711 // Identifiers should not have incorrect suffix
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces; using Jellyfin.Data.Interfaces;

View File

@ -1,3 +1,5 @@
#pragma warning disable CA1711 // Identifiers should not have incorrect suffix
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;

View File

@ -5,6 +5,8 @@
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<PublishRepositoryUrl>true</PublishRepositoryUrl> <PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources> <EmbedUntrackedSources>true</EmbedUntrackedSources>
@ -25,17 +27,12 @@
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" /> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<!-- Code analysers--> <!-- Code analysers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />

View File

@ -11,6 +11,8 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -30,16 +32,16 @@
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" /> <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<!-- Needed for https://github.com/dotnet/roslyn-analyzers/issues/4382 which is in the SDK yet -->
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3" PrivateAssets="All" />
</ItemGroup>
<!-- Code analysers--> <!-- Code analysers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
</Project> </Project>

View File

@ -274,8 +274,8 @@ namespace Jellyfin.Drawing.Skia
if (requiresTransparencyHack || forceCleanBitmap) if (requiresTransparencyHack || forceCleanBitmap)
{ {
using var codec = SKCodec.Create(NormalizePath(path)); using SKCodec codec = SKCodec.Create(NormalizePath(path), out SKCodecResult res);
if (codec == null) if (res != SKCodecResult.Success)
{ {
origin = GetSKEncodedOrigin(orientation); origin = GetSKEncodedOrigin(orientation);
return null; return null;
@ -345,11 +345,6 @@ namespace Jellyfin.Drawing.Skia
private SKBitmap OrientImage(SKBitmap bitmap, SKEncodedOrigin origin) private SKBitmap OrientImage(SKBitmap bitmap, SKEncodedOrigin origin)
{ {
if (origin == SKEncodedOrigin.Default)
{
return bitmap;
}
var needsFlip = origin == SKEncodedOrigin.LeftBottom var needsFlip = origin == SKEncodedOrigin.LeftBottom
|| origin == SKEncodedOrigin.LeftTop || origin == SKEncodedOrigin.LeftTop
|| origin == SKEncodedOrigin.RightBottom || origin == SKEncodedOrigin.RightBottom
@ -447,7 +442,7 @@ namespace Jellyfin.Drawing.Skia
} }
/// <inheritdoc/> /// <inheritdoc/>
public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat)
{ {
if (inputPath.Length == 0) if (inputPath.Length == 0)
{ {
@ -459,7 +454,7 @@ namespace Jellyfin.Drawing.Skia
throw new ArgumentException("String can't be empty.", nameof(outputPath)); throw new ArgumentException("String can't be empty.", nameof(outputPath));
} }
var skiaOutputFormat = GetImageFormat(selectedOutputFormat); var skiaOutputFormat = GetImageFormat(outputFormat);
var hasBackgroundColor = !string.IsNullOrWhiteSpace(options.BackgroundColor); var hasBackgroundColor = !string.IsNullOrWhiteSpace(options.BackgroundColor);
var hasForegroundColor = !string.IsNullOrWhiteSpace(options.ForegroundLayer); var hasForegroundColor = !string.IsNullOrWhiteSpace(options.ForegroundLayer);

View File

@ -5,6 +5,8 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -13,16 +15,11 @@
<!-- Code Analyzers--> <!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" /> <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" /> <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />

View File

@ -285,14 +285,25 @@ namespace Jellyfin.Networking.Manager
// No bind address and no exclusions, so listen on all interfaces. // No bind address and no exclusions, so listen on all interfaces.
Collection<IPObject> result = new Collection<IPObject>(); Collection<IPObject> result = new Collection<IPObject>();
if (IsIP4Enabled) if (IsIP6Enabled && IsIP4Enabled)
{
// Kestrel source code shows it uses Sockets.DualMode - so this also covers IPAddress.Any
result.AddItem(IPAddress.IPv6Any);
}
else if (IsIP4Enabled)
{ {
result.AddItem(IPAddress.Any); result.AddItem(IPAddress.Any);
} }
else if (IsIP6Enabled)
if (IsIP6Enabled)
{ {
result.AddItem(IPAddress.IPv6Any); // Cannot use IPv6Any as Kestrel will bind to IPv4 addresses.
foreach (var iface in _interfaceAddresses)
{
if (iface.AddressFamily == AddressFamily.InterNetworkV6)
{
result.AddItem(iface.Address);
}
}
} }
return result; return result;
@ -414,7 +425,7 @@ namespace Jellyfin.Networking.Manager
} }
// There isn't any others, so we'll use the loopback. // There isn't any others, so we'll use the loopback.
result = IsIP6Enabled ? "::" : "127.0.0.1"; result = IsIP6Enabled ? "::1" : "127.0.0.1";
_logger.LogWarning("{Source}: GetBindInterface: Loopback {Result} returned.", source, result); _logger.LogWarning("{Source}: GetBindInterface: Loopback {Result} returned.", source, result);
return result; return result;
} }

View File

@ -29,20 +29,20 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Security
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task OnEvent(GenericEventArgs<AuthenticationResult> e) public async Task OnEvent(GenericEventArgs<AuthenticationResult> eventArgs)
{ {
await _activityManager.CreateAsync(new ActivityLog( await _activityManager.CreateAsync(new ActivityLog(
string.Format( string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localizationManager.GetLocalizedString("AuthenticationSucceededWithUserName"), _localizationManager.GetLocalizedString("AuthenticationSucceededWithUserName"),
e.Argument.User.Name), eventArgs.Argument.User.Name),
"AuthenticationSucceeded", "AuthenticationSucceeded",
e.Argument.User.Id) eventArgs.Argument.User.Id)
{ {
ShortOverview = string.Format( ShortOverview = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localizationManager.GetLocalizedString("LabelIpAddressValue"), _localizationManager.GetLocalizedString("LabelIpAddressValue"),
e.Argument.SessionInfo.RemoteEndPoint), eventArgs.Argument.SessionInfo.RemoteEndPoint),
}).ConfigureAwait(false); }).ConfigureAwait(false);
} }
} }

View File

@ -33,10 +33,10 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.System
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task OnEvent(TaskCompletionEventArgs e) public async Task OnEvent(TaskCompletionEventArgs eventArgs)
{ {
var result = e.Result; var result = eventArgs.Result;
var task = e.Task; var task = eventArgs.Task;
if (task.ScheduledTask is IConfigurableScheduledTask activityTask if (task.ScheduledTask is IConfigurableScheduledTask activityTask
&& !activityTask.IsLogged) && !activityTask.IsLogged)
@ -54,14 +54,14 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.System
{ {
var vals = new List<string>(); var vals = new List<string>();
if (!string.IsNullOrEmpty(e.Result.ErrorMessage)) if (!string.IsNullOrEmpty(eventArgs.Result.ErrorMessage))
{ {
vals.Add(e.Result.ErrorMessage); vals.Add(eventArgs.Result.ErrorMessage);
} }
if (!string.IsNullOrEmpty(e.Result.LongErrorMessage)) if (!string.IsNullOrEmpty(eventArgs.Result.LongErrorMessage))
{ {
vals.Add(e.Result.LongErrorMessage); vals.Add(eventArgs.Result.LongErrorMessage);
} }
await _activityManager.CreateAsync(new ActivityLog( await _activityManager.CreateAsync(new ActivityLog(

View File

@ -30,13 +30,13 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task OnEvent(PluginUninstalledEventArgs e) public async Task OnEvent(PluginUninstalledEventArgs eventArgs)
{ {
await _activityManager.CreateAsync(new ActivityLog( await _activityManager.CreateAsync(new ActivityLog(
string.Format( string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localizationManager.GetLocalizedString("PluginUninstalledWithName"), _localizationManager.GetLocalizedString("PluginUninstalledWithName"),
e.Argument.Name), eventArgs.Argument.Name),
NotificationType.PluginUninstalled.ToString(), NotificationType.PluginUninstalled.ToString(),
Guid.Empty)) Guid.Empty))
.ConfigureAwait(false); .ConfigureAwait(false);

View File

@ -30,12 +30,12 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Users
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task OnEvent(UserUpdatedEventArgs e) public async Task OnEvent(UserUpdatedEventArgs eventArgs)
{ {
await _sessionManager.SendMessageToUserSessions( await _sessionManager.SendMessageToUserSessions(
new List<Guid> { e.Argument.Id }, new List<Guid> { eventArgs.Argument.Id },
SessionMessageType.UserUpdated, SessionMessageType.UserUpdated,
_userManager.GetUserDto(e.Argument), _userManager.GetUserDto(eventArgs.Argument),
CancellationToken.None).ConfigureAwait(false); CancellationToken.None).ConfigureAwait(false);
} }
} }

View File

@ -6,6 +6,8 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
@ -14,7 +16,6 @@
<!-- Code analysers--> <!-- Code analysers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />

View File

@ -66,7 +66,7 @@ namespace Jellyfin.Server.Implementations.Users
else if (string.Equals( else if (string.Equals(
spr.Pin.Replace("-", string.Empty, StringComparison.Ordinal), spr.Pin.Replace("-", string.Empty, StringComparison.Ordinal),
pin.Replace("-", string.Empty, StringComparison.Ordinal), pin.Replace("-", string.Empty, StringComparison.Ordinal),
StringComparison.InvariantCultureIgnoreCase)) StringComparison.OrdinalIgnoreCase))
{ {
var resetUser = userManager.GetUserByName(spr.UserName) var resetUser = userManager.GetUserByName(spr.UserName)
?? throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found"); ?? throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found");

View File

@ -1,4 +1,5 @@
#pragma warning disable CA1307 #pragma warning disable CA1307
#pragma warning disable CA1309
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;

View File

@ -20,6 +20,7 @@ using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Activity; using MediaBrowser.Model.Activity;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -36,18 +37,21 @@ namespace Jellyfin.Server
/// <param name="applicationPaths">The <see cref="ServerApplicationPaths" /> to be used by the <see cref="CoreAppHost" />.</param> /// <param name="applicationPaths">The <see cref="ServerApplicationPaths" /> to be used by the <see cref="CoreAppHost" />.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory" /> to be used by the <see cref="CoreAppHost" />.</param> /// <param name="loggerFactory">The <see cref="ILoggerFactory" /> to be used by the <see cref="CoreAppHost" />.</param>
/// <param name="options">The <see cref="StartupOptions" /> to be used by the <see cref="CoreAppHost" />.</param> /// <param name="options">The <see cref="StartupOptions" /> to be used by the <see cref="CoreAppHost" />.</param>
/// <param name="startupConfig">The <see cref="IConfiguration" /> to be used by the <see cref="CoreAppHost" />.</param>
/// <param name="fileSystem">The <see cref="IFileSystem" /> to be used by the <see cref="CoreAppHost" />.</param> /// <param name="fileSystem">The <see cref="IFileSystem" /> to be used by the <see cref="CoreAppHost" />.</param>
/// <param name="collection">The <see cref="IServiceCollection"/> to be used by the <see cref="CoreAppHost"/>.</param> /// <param name="collection">The <see cref="IServiceCollection"/> to be used by the <see cref="CoreAppHost"/>.</param>
public CoreAppHost( public CoreAppHost(
IServerApplicationPaths applicationPaths, IServerApplicationPaths applicationPaths,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
IStartupOptions options, IStartupOptions options,
IConfiguration startupConfig,
IFileSystem fileSystem, IFileSystem fileSystem,
IServiceCollection collection) IServiceCollection collection)
: base( : base(
applicationPaths, applicationPaths,
loggerFactory, loggerFactory,
options, options,
startupConfig,
fileSystem, fileSystem,
collection) collection)
{ {

View File

@ -225,14 +225,13 @@ namespace Jellyfin.Server.Extensions
.AddJsonOptions(options => .AddJsonOptions(options =>
{ {
// Update all properties that are set in JsonDefaults // Update all properties that are set in JsonDefaults
var jsonOptions = JsonDefaults.GetPascalCaseOptions(); var jsonOptions = JsonDefaults.PascalCaseOptions;
// From JsonDefaults // From JsonDefaults
options.JsonSerializerOptions.ReadCommentHandling = jsonOptions.ReadCommentHandling; options.JsonSerializerOptions.ReadCommentHandling = jsonOptions.ReadCommentHandling;
options.JsonSerializerOptions.WriteIndented = jsonOptions.WriteIndented; options.JsonSerializerOptions.WriteIndented = jsonOptions.WriteIndented;
options.JsonSerializerOptions.DefaultIgnoreCondition = jsonOptions.DefaultIgnoreCondition; options.JsonSerializerOptions.DefaultIgnoreCondition = jsonOptions.DefaultIgnoreCondition;
options.JsonSerializerOptions.NumberHandling = jsonOptions.NumberHandling; options.JsonSerializerOptions.NumberHandling = jsonOptions.NumberHandling;
options.JsonSerializerOptions.PropertyNameCaseInsensitive = jsonOptions.PropertyNameCaseInsensitive;
options.JsonSerializerOptions.Converters.Clear(); options.JsonSerializerOptions.Converters.Clear();
foreach (var converter in jsonOptions.Converters) foreach (var converter in jsonOptions.Converters)

View File

@ -25,6 +25,8 @@ namespace Jellyfin.Server.Filters
context.SchemaGenerator.GenerateSchema(typeof(GeneralCommandType), context.SchemaRepository); context.SchemaGenerator.GenerateSchema(typeof(GeneralCommandType), context.SchemaRepository);
context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate<object>), context.SchemaRepository); context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate<object>), context.SchemaRepository);
context.SchemaGenerator.GenerateSchema(typeof(SessionMessageType), context.SchemaRepository);
} }
} }
} }

View File

@ -12,7 +12,7 @@ namespace Jellyfin.Server.Formatters
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CamelCaseJsonProfileFormatter"/> class. /// Initializes a new instance of the <see cref="CamelCaseJsonProfileFormatter"/> class.
/// </summary> /// </summary>
public CamelCaseJsonProfileFormatter() : base(JsonDefaults.GetCamelCaseOptions()) public CamelCaseJsonProfileFormatter() : base(JsonDefaults.CamelCaseOptions)
{ {
SupportedMediaTypes.Clear(); SupportedMediaTypes.Clear();
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(JsonDefaults.CamelCaseMediaType)); SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(JsonDefaults.CamelCaseMediaType));

View File

@ -13,7 +13,7 @@ namespace Jellyfin.Server.Formatters
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PascalCaseJsonProfileFormatter"/> class. /// Initializes a new instance of the <see cref="PascalCaseJsonProfileFormatter"/> class.
/// </summary> /// </summary>
public PascalCaseJsonProfileFormatter() : base(JsonDefaults.GetPascalCaseOptions()) public PascalCaseJsonProfileFormatter() : base(JsonDefaults.PascalCaseOptions)
{ {
SupportedMediaTypes.Clear(); SupportedMediaTypes.Clear();
// Add application/json for default formatter // Add application/json for default formatter

View File

@ -13,7 +13,9 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> <AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
<!-- <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> -->
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -26,16 +28,11 @@
<!-- Code Analyzers--> <!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" /> <PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" />

View File

@ -16,9 +16,12 @@ namespace Jellyfin.Server.Migrations
Applied = new List<(Guid Id, string Name)>(); Applied = new List<(Guid Id, string Name)>();
} }
// .Net xml serializer can't handle interfaces
#pragma warning disable CA1002 // Do not expose generic lists
/// <summary> /// <summary>
/// Gets the list of applied migration routine names. /// Gets the list of applied migration routine names.
/// </summary> /// </summary>
public List<(Guid Id, string Name)> Applied { get; } public List<(Guid Id, string Name)> Applied { get; }
#pragma warning restore CA1002
} }
} }

View File

@ -74,7 +74,7 @@ namespace Jellyfin.Server.Migrations.Routines
foreach (var entry in queryResult) foreach (var entry in queryResult)
{ {
UserMockup? mockup = JsonSerializer.Deserialize<UserMockup>(entry[2].ToBlob(), JsonDefaults.GetOptions()); UserMockup? mockup = JsonSerializer.Deserialize<UserMockup>(entry[2].ToBlob(), JsonDefaults.Options);
if (mockup == null) if (mockup == null)
{ {
continue; continue;

View File

@ -164,6 +164,7 @@ namespace Jellyfin.Server
appPaths, appPaths,
_loggerFactory, _loggerFactory,
options, options,
startupConfig,
new ManagedFileSystem(_loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths), new ManagedFileSystem(_loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths),
serviceCollection); serviceCollection);
@ -198,11 +199,11 @@ namespace Jellyfin.Server
} }
catch catch
{ {
_logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bind - correct your bind configuration in system.xml and try again."); _logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bind - correct your bind configuration in network.xml and try again.");
throw; throw;
} }
await appHost.RunStartupTasksAsync().ConfigureAwait(false); await appHost.RunStartupTasksAsync(_tokenSource.Token).ConfigureAwait(false);
stopWatch.Stop(); stopWatch.Stop();
@ -221,7 +222,7 @@ namespace Jellyfin.Server
} }
finally finally
{ {
appHost?.Dispose(); appHost.Dispose();
} }
if (_restartOnShutdown) if (_restartOnShutdown)
@ -280,7 +281,7 @@ namespace Jellyfin.Server
bool flagged = false; bool flagged = false;
foreach (IPObject netAdd in addresses) foreach (IPObject netAdd in addresses)
{ {
_logger.LogInformation("Kestrel listening on {0}", netAdd); _logger.LogInformation("Kestrel listening on {Address}", netAdd.Address == IPAddress.IPv6Any ? "All Addresses" : netAdd);
options.Listen(netAdd.Address, appHost.HttpPort); options.Listen(netAdd.Address, appHost.HttpPort);
if (appHost.ListenWithHttps) if (appHost.ListenWithHttps)
{ {
@ -622,7 +623,7 @@ namespace Jellyfin.Server
string commandLineArgsString; string commandLineArgsString;
if (options.RestartArgs != null) if (options.RestartArgs != null)
{ {
commandLineArgsString = options.RestartArgs ?? string.Empty; commandLineArgsString = options.RestartArgs;
} }
else else
{ {

View File

@ -21,4 +21,4 @@ using System.Runtime.InteropServices;
// COM, set the ComVisible attribute to true on that type. // COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)] [assembly: ComVisible(false)]
[assembly: InternalsVisibleTo("Jellyfin.Api.Tests")] [assembly: InternalsVisibleTo("Jellyfin.Server.Tests")]

View File

@ -77,7 +77,7 @@ namespace Jellyfin.Server
/// <inheritdoc /> /// <inheritdoc />
[Option("published-server-url", Required = false, HelpText = "Jellyfin Server URL to publish via auto discover process")] [Option("published-server-url", Required = false, HelpText = "Jellyfin Server URL to publish via auto discover process")]
public Uri? PublishedServerUrl { get; set; } public string? PublishedServerUrl { get; set; }
/// <summary> /// <summary>
/// Gets the command line options as a dictionary that can be used in the .NET configuration system. /// Gets the command line options as a dictionary that can be used in the .NET configuration system.
@ -94,7 +94,7 @@ namespace Jellyfin.Server
if (PublishedServerUrl != null) if (PublishedServerUrl != null)
{ {
config.Add(UdpServer.AddressOverrideConfigKey, PublishedServerUrl.ToString()); config.Add(UdpServer.AddressOverrideConfigKey, PublishedServerUrl);
} }
if (FFmpegPath != null) if (FFmpegPath != null)

View File

@ -68,14 +68,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Implementat
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking", "Jellyfin.Networking\Jellyfin.Networking.csproj", "{0A3FCC4D-C714-4072-B90F-E374A15F9FF9}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking", "Jellyfin.Networking\Jellyfin.Networking.csproj", "{0A3FCC4D-C714-4072-B90F-E374A15F9FF9}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\NetworkTesting\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Dlna.Tests", "tests\Jellyfin.Dlna.Tests\Jellyfin.Dlna.Tests.csproj", "{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Dlna.Tests", "tests\Jellyfin.Dlna.Tests\Jellyfin.Dlna.Tests.csproj", "{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Model.Tests", "tests\Jellyfin.Model.Tests\Jellyfin.Model.Tests.csproj", "{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Model.Tests", "tests\Jellyfin.Model.Tests\Jellyfin.Model.Tests.csproj", "{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -190,10 +194,6 @@ Global
{0A3FCC4D-C714-4072-B90F-E374A15F9FF9}.Debug|Any CPU.Build.0 = Debug|Any CPU {0A3FCC4D-C714-4072-B90F-E374A15F9FF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0A3FCC4D-C714-4072-B90F-E374A15F9FF9}.Release|Any CPU.ActiveCfg = Release|Any CPU {0A3FCC4D-C714-4072-B90F-E374A15F9FF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0A3FCC4D-C714-4072-B90F-E374A15F9FF9}.Release|Any CPU.Build.0 = Release|Any CPU {0A3FCC4D-C714-4072-B90F-E374A15F9FF9}.Release|Any CPU.Build.0 = Release|Any CPU
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.Build.0 = Release|Any CPU
{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}.Debug|Any CPU.Build.0 = Debug|Any CPU {B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}.Release|Any CPU.ActiveCfg = Release|Any CPU {B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -206,6 +206,18 @@ Global
{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}.Debug|Any CPU.Build.0 = Debug|Any CPU {FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}.Release|Any CPU.ActiveCfg = Release|Any CPU {FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}.Release|Any CPU.Build.0 = Release|Any CPU {FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}.Release|Any CPU.Build.0 = Release|Any CPU
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.Build.0 = Release|Any CPU
{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Release|Any CPU.Build.0 = Release|Any CPU
{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -217,10 +229,12 @@ Global
{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{462584F7-5023-4019-9EAC-B98CA458C0A0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {462584F7-5023-4019-9EAC-B98CA458C0A0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{30922383-D513-4F4D-B890-A940B57FA353} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {30922383-D513-4F4D-B890-A940B57FA353} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE}

View File

@ -0,0 +1,89 @@
#pragma warning disable CS1591
using System;
namespace MediaBrowser.Common
{
public static class Crc32
{
private static readonly uint[] _crcTable =
{
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};
public static uint Compute(ReadOnlySpan<byte> bytes)
{
var crc = 0xffffffff;
var len = bytes.Length;
for (var i = 0; i < len; i++)
{
crc = (crc >> 8) ^ _crcTable[(bytes[i] ^ crc) & 0xff];
}
return ~crc;
}
}
}

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