mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-05-27 10:58:44 +01:00
Merge branch 'master' into trickplay
This commit is contained in:
@@ -24,6 +24,7 @@ using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Security;
|
||||
using MediaBrowser.Controller.Trickplay;
|
||||
using MediaBrowser.Model.Activity;
|
||||
using MediaBrowser.Providers.Lyric;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -96,12 +97,14 @@ namespace Jellyfin.Server
|
||||
serviceCollection.AddSingleton(typeof(ILyricProvider), type);
|
||||
}
|
||||
|
||||
foreach (var type in GetExportTypes<ILyricParser>())
|
||||
{
|
||||
serviceCollection.AddSingleton(typeof(ILyricParser), type);
|
||||
}
|
||||
|
||||
base.RegisterServices(serviceCollection);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void RestartInternal() => Program.Restart();
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IEnumerable<Assembly> GetAssembliesWithPartsInternal()
|
||||
{
|
||||
@@ -111,8 +114,5 @@ namespace Jellyfin.Server
|
||||
// Jellyfin.Server.Implementations
|
||||
yield return typeof(JellyfinDbContext).Assembly;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void ShutdownInternal() => Program.Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,9 +63,9 @@ namespace Jellyfin.Server.Extensions
|
||||
/// </summary>
|
||||
/// <param name="appBuilder">The application builder.</param>
|
||||
/// <returns>The updated application builder.</returns>
|
||||
public static IApplicationBuilder UseIpBasedAccessValidation(this IApplicationBuilder appBuilder)
|
||||
public static IApplicationBuilder UseIPBasedAccessValidation(this IApplicationBuilder appBuilder)
|
||||
{
|
||||
return appBuilder.UseMiddleware<IpBasedAccessValidationMiddleware>();
|
||||
return appBuilder.UseMiddleware<IPBasedAccessValidationMiddleware>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -21,9 +21,10 @@ using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions.Json;
|
||||
using Jellyfin.Networking.Configuration;
|
||||
using Jellyfin.Networking.Constants;
|
||||
using Jellyfin.Networking.Extensions;
|
||||
using Jellyfin.Server.Configuration;
|
||||
using Jellyfin.Server.Filters;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
@@ -58,6 +59,7 @@ namespace Jellyfin.Server.Extensions
|
||||
serviceCollection.AddSingleton<IAuthorizationHandler, FirstTimeSetupHandler>();
|
||||
serviceCollection.AddSingleton<IAuthorizationHandler, AnonymousLanAccessHandler>();
|
||||
serviceCollection.AddSingleton<IAuthorizationHandler, SyncPlayAccessHandler>();
|
||||
serviceCollection.AddSingleton<IAuthorizationHandler, LocalAccessOrRequiresElevationHandler>();
|
||||
|
||||
return serviceCollection.AddAuthorizationCore(options =>
|
||||
{
|
||||
@@ -99,7 +101,7 @@ namespace Jellyfin.Server.Extensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension method for adding the jellyfin API to the service collection.
|
||||
/// Extension method for adding the Jellyfin API to the service collection.
|
||||
/// </summary>
|
||||
/// <param name="serviceCollection">The service collection.</param>
|
||||
/// <param name="pluginAssemblies">An IEnumerable containing all plugin assemblies with API controllers.</param>
|
||||
@@ -260,7 +262,7 @@ namespace Jellyfin.Server.Extensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets up the proxy configuration based on the addresses in <paramref name="allowedProxies"/>.
|
||||
/// Sets up the proxy configuration based on the addresses/subnets in <paramref name="allowedProxies"/>.
|
||||
/// </summary>
|
||||
/// <param name="config">The <see cref="NetworkConfiguration"/> containing the config settings.</param>
|
||||
/// <param name="allowedProxies">The string array to parse.</param>
|
||||
@@ -269,36 +271,40 @@ namespace Jellyfin.Server.Extensions
|
||||
{
|
||||
for (var i = 0; i < allowedProxies.Length; i++)
|
||||
{
|
||||
if (IPNetAddress.TryParse(allowedProxies[i], out var addr))
|
||||
if (IPAddress.TryParse(allowedProxies[i], out var addr))
|
||||
{
|
||||
AddIpAddress(config, options, addr.Address, addr.PrefixLength);
|
||||
AddIPAddress(config, options, addr, addr.AddressFamily == AddressFamily.InterNetwork ? Network.MinimumIPv4PrefixSize : Network.MinimumIPv6PrefixSize);
|
||||
}
|
||||
else if (IPHost.TryParse(allowedProxies[i], out var host))
|
||||
else if (NetworkExtensions.TryParseToSubnet(allowedProxies[i], out var subnet))
|
||||
{
|
||||
foreach (var address in host.GetAddresses())
|
||||
if (subnet is not null)
|
||||
{
|
||||
AddIpAddress(config, options, address, address.AddressFamily == AddressFamily.InterNetwork ? 32 : 128);
|
||||
AddIPAddress(config, options, subnet.Prefix, subnet.PrefixLength);
|
||||
}
|
||||
}
|
||||
else if (NetworkExtensions.TryParseHost(allowedProxies[i], out var addresses, config.EnableIPv4, config.EnableIPv6))
|
||||
{
|
||||
foreach (var address in addresses)
|
||||
{
|
||||
AddIPAddress(config, options, address, address.AddressFamily == AddressFamily.InterNetwork ? Network.MinimumIPv4PrefixSize : Network.MinimumIPv6PrefixSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddIpAddress(NetworkConfiguration config, ForwardedHeadersOptions options, IPAddress addr, int prefixLength)
|
||||
private static void AddIPAddress(NetworkConfiguration config, ForwardedHeadersOptions options, IPAddress addr, int prefixLength)
|
||||
{
|
||||
if ((!config.EnableIPV4 && addr.AddressFamily == AddressFamily.InterNetwork) || (!config.EnableIPV6 && addr.AddressFamily == AddressFamily.InterNetworkV6))
|
||||
if (addr.IsIPv4MappedToIPv6)
|
||||
{
|
||||
addr = addr.MapToIPv4();
|
||||
}
|
||||
|
||||
if ((!config.EnableIPv4 && addr.AddressFamily == AddressFamily.InterNetwork) || (!config.EnableIPv6 && addr.AddressFamily == AddressFamily.InterNetworkV6))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// In order for dual-mode sockets to be used, IP6 has to be enabled in JF and an interface has to have an IP6 address.
|
||||
if (addr.AddressFamily == AddressFamily.InterNetwork && config.EnableIPV6)
|
||||
{
|
||||
// If the server is using dual-mode sockets, IPv4 addresses are supplied in an IPv6 format.
|
||||
// https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-5.0 .
|
||||
addr = addr.MapToIPv6();
|
||||
}
|
||||
|
||||
if (prefixLength == 32)
|
||||
if (prefixLength == Network.MinimumIPv4PrefixSize)
|
||||
{
|
||||
options.KnownProxies.Add(addr);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.IO;
|
||||
using System.Net;
|
||||
using Jellyfin.Server.Helpers;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Extensions;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
@@ -36,12 +35,12 @@ public static class WebHostBuilderExtensions
|
||||
return builder
|
||||
.UseKestrel((builderContext, options) =>
|
||||
{
|
||||
var addresses = appHost.NetManager.GetAllBindInterfaces();
|
||||
var addresses = appHost.NetManager.GetAllBindInterfaces(true);
|
||||
|
||||
bool flagged = false;
|
||||
foreach (IPObject netAdd in addresses)
|
||||
foreach (var netAdd in addresses)
|
||||
{
|
||||
logger.LogInformation("Kestrel listening on {Address}", IPAddress.IPv6Any.Equals(netAdd.Address) ? "All Addresses" : netAdd);
|
||||
logger.LogInformation("Kestrel is listening on {Address}", IPAddress.IPv6Any.Equals(netAdd.Address) ? "All IPv6 addresses" : netAdd.Address);
|
||||
options.Listen(netAdd.Address, appHost.HttpPort);
|
||||
if (appHost.ListenWithHttps)
|
||||
{
|
||||
|
||||
@@ -15,7 +15,6 @@ using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
using SQLitePCL;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace Jellyfin.Server.Helpers;
|
||||
@@ -297,7 +296,5 @@ public static class StartupHelpers
|
||||
// Disable the "Expect: 100-Continue" header by default
|
||||
// http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c
|
||||
ServicePointManager.Expect100Continue = false;
|
||||
|
||||
Batteries_V2.Init();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
<PackageReference Include="Serilog.Sinks.Console" />
|
||||
<PackageReference Include="Serilog.Sinks.File" />
|
||||
<PackageReference Include="Serilog.Sinks.Graylog" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -22,7 +22,8 @@ namespace Jellyfin.Server.Migrations
|
||||
private static readonly Type[] _preStartupMigrationTypes =
|
||||
{
|
||||
typeof(PreStartupRoutines.CreateNetworkConfiguration),
|
||||
typeof(PreStartupRoutines.MigrateMusicBrainzTimeout)
|
||||
typeof(PreStartupRoutines.MigrateMusicBrainzTimeout),
|
||||
typeof(PreStartupRoutines.MigrateNetworkConfiguration)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -41,7 +42,8 @@ namespace Jellyfin.Server.Migrations
|
||||
typeof(Routines.RemoveDownloadImagesInAdvance),
|
||||
typeof(Routines.MigrateAuthenticationDb),
|
||||
typeof(Routines.FixPlaylistOwner),
|
||||
typeof(Routines.MigrateRatingLevels)
|
||||
typeof(Routines.MigrateRatingLevels),
|
||||
typeof(Routines.AddDefaultCastReceivers)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -92,7 +94,7 @@ namespace Jellyfin.Server.Migrations
|
||||
|
||||
private static void HandleStartupWizardCondition(IEnumerable<IMigrationRoutine> migrations, MigrationOptions migrationOptions, bool isStartWizardCompleted, ILogger logger)
|
||||
{
|
||||
if (isStartWizardCompleted || migrationOptions.Applied.Count != 0)
|
||||
if (isStartWizardCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -105,6 +107,8 @@ namespace Jellyfin.Server.Migrations
|
||||
|
||||
private static void PerformMigrations(IMigrationRoutine[] migrations, MigrationOptions migrationOptions, Action<MigrationOptions> saveConfiguration, ILogger logger)
|
||||
{
|
||||
// save already applied migrations, and skip them thereafter
|
||||
saveConfiguration(migrationOptions);
|
||||
var appliedMigrationIds = migrationOptions.Applied.Select(m => m.Id).ToHashSet();
|
||||
|
||||
for (var i = 0; i < migrations.Length; i++)
|
||||
|
||||
@@ -114,9 +114,7 @@ public class CreateNetworkConfiguration : IMigrationRoutine
|
||||
|
||||
public bool IgnoreVirtualInterfaces { get; set; } = true;
|
||||
|
||||
public string VirtualInterfaceNames { get; set; } = "vEthernet*";
|
||||
|
||||
public bool TrustAllIP6Interfaces { get; set; }
|
||||
public string[] VirtualInterfaceNames { get; set; } = new string[] { "veth" };
|
||||
|
||||
public string[] PublishedServerUriBySubnet { get; set; } = Array.Empty<string>();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
@@ -59,21 +59,17 @@ public class MigrateMusicBrainzTimeout : IMigrationRoutine
|
||||
|
||||
private OldMusicBrainzConfiguration? ReadOld(string path)
|
||||
{
|
||||
using (var xmlReader = XmlReader.Create(path))
|
||||
{
|
||||
var serverConfigSerializer = new XmlSerializer(typeof(OldMusicBrainzConfiguration), new XmlRootAttribute("PluginConfiguration"));
|
||||
return serverConfigSerializer.Deserialize(xmlReader) as OldMusicBrainzConfiguration;
|
||||
}
|
||||
using var xmlReader = XmlReader.Create(path);
|
||||
var serverConfigSerializer = new XmlSerializer(typeof(OldMusicBrainzConfiguration), new XmlRootAttribute("PluginConfiguration"));
|
||||
return serverConfigSerializer.Deserialize(xmlReader) as OldMusicBrainzConfiguration;
|
||||
}
|
||||
|
||||
private void WriteNew(string path, PluginConfiguration newPluginConfiguration)
|
||||
{
|
||||
var pluginConfigurationSerializer = new XmlSerializer(typeof(PluginConfiguration), new XmlRootAttribute("PluginConfiguration"));
|
||||
var xmlWriterSettings = new XmlWriterSettings { Indent = true };
|
||||
using (var xmlWriter = XmlWriter.Create(path, xmlWriterSettings))
|
||||
{
|
||||
pluginConfigurationSerializer.Serialize(xmlWriter, newPluginConfiguration);
|
||||
}
|
||||
using var xmlWriter = XmlWriter.Create(path, xmlWriterSettings);
|
||||
pluginConfigurationSerializer.Serialize(xmlWriter, newPluginConfiguration);
|
||||
}
|
||||
|
||||
#pragma warning disable
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
using Emby.Server.Implementations;
|
||||
using Jellyfin.Networking.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Server.Migrations.PreStartupRoutines;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class MigrateNetworkConfiguration : IMigrationRoutine
|
||||
{
|
||||
private readonly ServerApplicationPaths _applicationPaths;
|
||||
private readonly ILogger<MigrateNetworkConfiguration> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MigrateNetworkConfiguration"/> class.
|
||||
/// </summary>
|
||||
/// <param name="applicationPaths">An instance of <see cref="ServerApplicationPaths"/>.</param>
|
||||
/// <param name="loggerFactory">An instance of the <see cref="ILoggerFactory"/> interface.</param>
|
||||
public MigrateNetworkConfiguration(ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory)
|
||||
{
|
||||
_applicationPaths = applicationPaths;
|
||||
_logger = loggerFactory.CreateLogger<MigrateNetworkConfiguration>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Guid Id => Guid.Parse("4FB5C950-1991-11EE-9B4B-0800200C9A66");
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => nameof(MigrateNetworkConfiguration);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool PerformOnNewInstall => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Perform()
|
||||
{
|
||||
string path = Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "network.xml");
|
||||
var oldNetworkConfigSerializer = new XmlSerializer(typeof(OldNetworkConfiguration), new XmlRootAttribute("NetworkConfiguration"));
|
||||
OldNetworkConfiguration? oldNetworkConfiguration = null;
|
||||
|
||||
try
|
||||
{
|
||||
using var xmlReader = XmlReader.Create(path);
|
||||
oldNetworkConfiguration = (OldNetworkConfiguration?)oldNetworkConfigSerializer.Deserialize(xmlReader);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Migrate NetworkConfiguration deserialize Invalid Operation error");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Migrate NetworkConfiguration deserialize error");
|
||||
}
|
||||
|
||||
if (oldNetworkConfiguration is not null)
|
||||
{
|
||||
// Migrate network config values to new config schema
|
||||
var networkConfiguration = new NetworkConfiguration();
|
||||
networkConfiguration.AutoDiscovery = oldNetworkConfiguration.AutoDiscovery;
|
||||
networkConfiguration.BaseUrl = oldNetworkConfiguration.BaseUrl;
|
||||
networkConfiguration.CertificatePassword = oldNetworkConfiguration.CertificatePassword;
|
||||
networkConfiguration.CertificatePath = oldNetworkConfiguration.CertificatePath;
|
||||
networkConfiguration.EnableHttps = oldNetworkConfiguration.EnableHttps;
|
||||
networkConfiguration.EnableIPv4 = oldNetworkConfiguration.EnableIPV4;
|
||||
networkConfiguration.EnableIPv6 = oldNetworkConfiguration.EnableIPV6;
|
||||
networkConfiguration.EnablePublishedServerUriByRequest = oldNetworkConfiguration.EnablePublishedServerUriByRequest;
|
||||
networkConfiguration.EnableRemoteAccess = oldNetworkConfiguration.EnableRemoteAccess;
|
||||
networkConfiguration.EnableUPnP = oldNetworkConfiguration.EnableUPnP;
|
||||
networkConfiguration.IgnoreVirtualInterfaces = oldNetworkConfiguration.IgnoreVirtualInterfaces;
|
||||
networkConfiguration.InternalHttpPort = oldNetworkConfiguration.HttpServerPortNumber;
|
||||
networkConfiguration.InternalHttpsPort = oldNetworkConfiguration.HttpsPortNumber;
|
||||
networkConfiguration.IsRemoteIPFilterBlacklist = oldNetworkConfiguration.IsRemoteIPFilterBlacklist;
|
||||
networkConfiguration.KnownProxies = oldNetworkConfiguration.KnownProxies;
|
||||
networkConfiguration.LocalNetworkAddresses = oldNetworkConfiguration.LocalNetworkAddresses;
|
||||
networkConfiguration.LocalNetworkSubnets = oldNetworkConfiguration.LocalNetworkSubnets;
|
||||
networkConfiguration.PublicHttpPort = oldNetworkConfiguration.PublicPort;
|
||||
networkConfiguration.PublicHttpsPort = oldNetworkConfiguration.PublicHttpsPort;
|
||||
networkConfiguration.PublishedServerUriBySubnet = oldNetworkConfiguration.PublishedServerUriBySubnet;
|
||||
networkConfiguration.RemoteIPFilter = oldNetworkConfiguration.RemoteIPFilter;
|
||||
networkConfiguration.RequireHttps = oldNetworkConfiguration.RequireHttps;
|
||||
|
||||
// Migrate old virtual interface name schema
|
||||
var oldVirtualInterfaceNames = oldNetworkConfiguration.VirtualInterfaceNames;
|
||||
if (oldVirtualInterfaceNames.Equals("vEthernet*", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
networkConfiguration.VirtualInterfaceNames = new string[] { "veth" };
|
||||
}
|
||||
else
|
||||
{
|
||||
networkConfiguration.VirtualInterfaceNames = oldVirtualInterfaceNames.Replace("*", string.Empty, StringComparison.OrdinalIgnoreCase).Split(',');
|
||||
}
|
||||
|
||||
var networkConfigSerializer = new XmlSerializer(typeof(NetworkConfiguration));
|
||||
var xmlWriterSettings = new XmlWriterSettings { Indent = true };
|
||||
using var xmlWriter = XmlWriter.Create(path, xmlWriterSettings);
|
||||
networkConfigSerializer.Serialize(xmlWriter, networkConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable
|
||||
public sealed class OldNetworkConfiguration
|
||||
{
|
||||
public const int DefaultHttpPort = 8096;
|
||||
|
||||
public const int DefaultHttpsPort = 8920;
|
||||
|
||||
private string _baseUrl = string.Empty;
|
||||
|
||||
public bool RequireHttps { get; set; }
|
||||
|
||||
public string CertificatePath { get; set; } = string.Empty;
|
||||
|
||||
public string CertificatePassword { get; set; } = string.Empty;
|
||||
|
||||
public string BaseUrl
|
||||
{
|
||||
get => _baseUrl;
|
||||
set
|
||||
{
|
||||
// Normalize the start of the string
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
// If baseUrl is empty, set an empty prefix string
|
||||
_baseUrl = string.Empty;
|
||||
return;
|
||||
}
|
||||
|
||||
if (value[0] != '/')
|
||||
{
|
||||
// If baseUrl was not configured with a leading slash, append one for consistency
|
||||
value = "/" + value;
|
||||
}
|
||||
|
||||
// Normalize the end of the string
|
||||
if (value[^1] == '/')
|
||||
{
|
||||
// If baseUrl was configured with a trailing slash, remove it for consistency
|
||||
value = value.Remove(value.Length - 1);
|
||||
}
|
||||
|
||||
_baseUrl = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int PublicHttpsPort { get; set; } = DefaultHttpsPort;
|
||||
|
||||
public int HttpServerPortNumber { get; set; } = DefaultHttpPort;
|
||||
|
||||
public int HttpsPortNumber { get; set; } = DefaultHttpsPort;
|
||||
|
||||
public bool EnableHttps { get; set; }
|
||||
|
||||
public int PublicPort { get; set; } = DefaultHttpPort;
|
||||
|
||||
public bool UPnPCreateHttpPortMap { get; set; }
|
||||
|
||||
public string UDPPortRange { get; set; } = string.Empty;
|
||||
|
||||
public bool EnableIPV6 { get; set; }
|
||||
|
||||
public bool EnableIPV4 { get; set; } = true;
|
||||
|
||||
public bool EnableSSDPTracing { get; set; }
|
||||
|
||||
public string SSDPTracingFilter { get; set; } = string.Empty;
|
||||
|
||||
public int UDPSendCount { get; set; } = 2;
|
||||
|
||||
public int UDPSendDelay { get; set; } = 100;
|
||||
|
||||
public bool IgnoreVirtualInterfaces { get; set; } = true;
|
||||
|
||||
public string VirtualInterfaceNames { get; set; } = "vEthernet*";
|
||||
|
||||
public int GatewayMonitorPeriod { get; set; } = 60;
|
||||
|
||||
public bool EnableMultiSocketBinding { get; } = true;
|
||||
|
||||
public bool TrustAllIP6Interfaces { get; set; }
|
||||
|
||||
public string HDHomerunPortRange { get; set; } = string.Empty;
|
||||
|
||||
public string[] PublishedServerUriBySubnet { get; set; } = Array.Empty<string>();
|
||||
|
||||
public bool AutoDiscoveryTracing { get; set; }
|
||||
|
||||
public bool AutoDiscovery { get; set; } = true;
|
||||
|
||||
public string[] RemoteIPFilter { get; set; } = Array.Empty<string>();
|
||||
|
||||
public bool IsRemoteIPFilterBlacklist { get; set; }
|
||||
|
||||
public bool EnableUPnP { get; set; }
|
||||
|
||||
public bool EnableRemoteAccess { get; set; } = true;
|
||||
|
||||
public string[] LocalNetworkSubnets { get; set; } = Array.Empty<string>();
|
||||
|
||||
public string[] LocalNetworkAddresses { get; set; } = Array.Empty<string>();
|
||||
public string[] KnownProxies { get; set; } = Array.Empty<string>();
|
||||
|
||||
public bool EnablePublishedServerUriByRequest { get; set; } = false;
|
||||
}
|
||||
#pragma warning restore
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Model.System;
|
||||
|
||||
namespace Jellyfin.Server.Migrations.Routines;
|
||||
|
||||
/// <summary>
|
||||
/// Migration to add the default cast receivers to the system config.
|
||||
/// </summary>
|
||||
public class AddDefaultCastReceivers : IMigrationRoutine
|
||||
{
|
||||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddDefaultCastReceivers"/> class.
|
||||
/// </summary>
|
||||
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
|
||||
public AddDefaultCastReceivers(IServerConfigurationManager serverConfigurationManager)
|
||||
{
|
||||
_serverConfigurationManager = serverConfigurationManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Guid Id => new("34A1A1C4-5572-418E-A2F8-32CDFE2668E8");
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "AddDefaultCastReceivers";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool PerformOnNewInstall => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Perform()
|
||||
{
|
||||
// Only add if receiver list is empty.
|
||||
if (_serverConfigurationManager.Configuration.CastReceiverApplications.Length == 0)
|
||||
{
|
||||
_serverConfigurationManager.Configuration.CastReceiverApplications = new CastReceiverApplication[]
|
||||
{
|
||||
new()
|
||||
{
|
||||
Id = "F007D354",
|
||||
Name = "Stable"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = "6F511C87",
|
||||
Name = "Unstable"
|
||||
}
|
||||
};
|
||||
|
||||
_serverConfigurationManager.SaveConfiguration();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,9 @@ using Emby.Server.Implementations.Data;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Server.Implementations;
|
||||
using MediaBrowser.Controller;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SQLitePCL.pretty;
|
||||
|
||||
namespace Jellyfin.Server.Migrations.Routines
|
||||
{
|
||||
@@ -61,17 +61,15 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
};
|
||||
|
||||
var dataPath = _paths.DataPath;
|
||||
using (var connection = SQLite3.Open(
|
||||
Path.Combine(dataPath, DbFilename),
|
||||
ConnectionFlags.ReadOnly,
|
||||
null))
|
||||
using (var connection = new SqliteConnection($"Filename={Path.Combine(dataPath, DbFilename)}"))
|
||||
{
|
||||
using var userDbConnection = SQLite3.Open(Path.Combine(dataPath, "users.db"), ConnectionFlags.ReadOnly, null);
|
||||
connection.Open();
|
||||
|
||||
using var userDbConnection = new SqliteConnection($"Filename={Path.Combine(dataPath, "users.db")}");
|
||||
userDbConnection.Open();
|
||||
_logger.LogWarning("Migrating the activity database may take a while, do not stop Jellyfin.");
|
||||
using var dbContext = _provider.CreateDbContext();
|
||||
|
||||
var queryResult = connection.Query("SELECT * FROM ActivityLog ORDER BY Id");
|
||||
|
||||
// Make sure that the database is empty in case of failed migration due to power outages, etc.
|
||||
dbContext.ActivityLogs.RemoveRange(dbContext.ActivityLogs);
|
||||
dbContext.SaveChanges();
|
||||
@@ -81,51 +79,52 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
|
||||
var newEntries = new List<ActivityLog>();
|
||||
|
||||
var queryResult = connection.Query("SELECT * FROM ActivityLog ORDER BY Id");
|
||||
|
||||
foreach (var entry in queryResult)
|
||||
{
|
||||
if (!logLevelDictionary.TryGetValue(entry[8].ToString(), out var severity))
|
||||
if (!logLevelDictionary.TryGetValue(entry.GetString(8), out var severity))
|
||||
{
|
||||
severity = LogLevel.Trace;
|
||||
}
|
||||
|
||||
var guid = Guid.Empty;
|
||||
if (entry[6].SQLiteType != SQLiteType.Null && !Guid.TryParse(entry[6].ToString(), out guid))
|
||||
if (!entry.IsDBNull(6) && !entry.TryGetGuid(6, out guid))
|
||||
{
|
||||
var id = entry.GetString(6);
|
||||
// This is not a valid Guid, see if it is an internal ID from an old Emby schema
|
||||
_logger.LogWarning("Invalid Guid in UserId column: {Guid}", entry[6].ToString());
|
||||
_logger.LogWarning("Invalid Guid in UserId column: {Guid}", id);
|
||||
|
||||
using var statement = userDbConnection.PrepareStatement("SELECT guid FROM LocalUsersv2 WHERE Id=@Id");
|
||||
statement.TryBind("@Id", entry[6].ToString());
|
||||
statement.TryBind("@Id", id);
|
||||
|
||||
foreach (var row in statement.Query())
|
||||
using var reader = statement.ExecuteReader();
|
||||
if (reader.HasRows && reader.Read() && reader.TryGetGuid(0, out guid))
|
||||
{
|
||||
if (row.Count > 0 && Guid.TryParse(row[0].ToString(), out guid))
|
||||
{
|
||||
// Successfully parsed a Guid from the user table.
|
||||
break;
|
||||
}
|
||||
// Successfully parsed a Guid from the user table.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var newEntry = new ActivityLog(entry[1].ToString(), entry[4].ToString(), guid)
|
||||
var newEntry = new ActivityLog(entry.GetString(1), entry.GetString(4), guid)
|
||||
{
|
||||
DateCreated = entry[7].ReadDateTime(),
|
||||
DateCreated = entry.GetDateTime(7),
|
||||
LogSeverity = severity
|
||||
};
|
||||
|
||||
if (entry[2].SQLiteType != SQLiteType.Null)
|
||||
if (entry.TryGetString(2, out var result))
|
||||
{
|
||||
newEntry.Overview = entry[2].ToString();
|
||||
newEntry.Overview = result;
|
||||
}
|
||||
|
||||
if (entry[3].SQLiteType != SQLiteType.Null)
|
||||
if (entry.TryGetString(3, out result))
|
||||
{
|
||||
newEntry.ShortOverview = entry[3].ToString();
|
||||
newEntry.ShortOverview = result;
|
||||
}
|
||||
|
||||
if (entry[5].SQLiteType != SQLiteType.Null)
|
||||
if (entry.TryGetString(5, out result))
|
||||
{
|
||||
newEntry.ItemId = entry[5].ToString();
|
||||
newEntry.ItemId = result;
|
||||
}
|
||||
|
||||
newEntries.Add(newEntry);
|
||||
|
||||
@@ -6,9 +6,9 @@ using Jellyfin.Data.Entities.Security;
|
||||
using Jellyfin.Server.Implementations;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SQLitePCL.pretty;
|
||||
|
||||
namespace Jellyfin.Server.Migrations.Routines
|
||||
{
|
||||
@@ -56,34 +56,32 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
public void Perform()
|
||||
{
|
||||
var dataPath = _appPaths.DataPath;
|
||||
using (var connection = SQLite3.Open(
|
||||
Path.Combine(dataPath, DbFilename),
|
||||
ConnectionFlags.ReadOnly,
|
||||
null))
|
||||
using (var connection = new SqliteConnection($"Filename={Path.Combine(dataPath, DbFilename)}"))
|
||||
{
|
||||
connection.Open();
|
||||
using var dbContext = _dbProvider.CreateDbContext();
|
||||
|
||||
var authenticatedDevices = connection.Query("SELECT * FROM Tokens");
|
||||
|
||||
foreach (var row in authenticatedDevices)
|
||||
{
|
||||
var dateCreatedStr = row[9].ToString();
|
||||
var dateCreatedStr = row.GetString(9);
|
||||
_ = DateTime.TryParse(dateCreatedStr, out var dateCreated);
|
||||
var dateLastActivityStr = row[10].ToString();
|
||||
var dateLastActivityStr = row.GetString(10);
|
||||
_ = DateTime.TryParse(dateLastActivityStr, out var dateLastActivity);
|
||||
|
||||
if (row[6].IsDbNull())
|
||||
if (row.IsDBNull(6))
|
||||
{
|
||||
dbContext.ApiKeys.Add(new ApiKey(row[3].ToString())
|
||||
dbContext.ApiKeys.Add(new ApiKey(row.GetString(3))
|
||||
{
|
||||
AccessToken = row[1].ToString(),
|
||||
AccessToken = row.GetString(1),
|
||||
DateCreated = dateCreated,
|
||||
DateLastActivity = dateLastActivity
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var userId = new Guid(row[6].ToString());
|
||||
var userId = row.GetGuid(6);
|
||||
var user = _userManager.GetUserById(userId);
|
||||
if (user is null)
|
||||
{
|
||||
@@ -92,14 +90,14 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
}
|
||||
|
||||
dbContext.Devices.Add(new Device(
|
||||
new Guid(row[6].ToString()),
|
||||
row[3].ToString(),
|
||||
row[4].ToString(),
|
||||
row[5].ToString(),
|
||||
row[2].ToString())
|
||||
userId,
|
||||
row.GetString(3),
|
||||
row.GetString(4),
|
||||
row.GetString(5),
|
||||
row.GetString(2))
|
||||
{
|
||||
AccessToken = row[1].ToString(),
|
||||
IsActive = row[8].ToBool(),
|
||||
AccessToken = row.GetString(1),
|
||||
IsActive = row.GetBoolean(8),
|
||||
DateCreated = dateCreated,
|
||||
DateLastActivity = dateLastActivity
|
||||
});
|
||||
@@ -110,12 +108,12 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
var deviceIds = new HashSet<string>();
|
||||
foreach (var row in deviceOptions)
|
||||
{
|
||||
if (row[2].IsDbNull())
|
||||
if (row.IsDBNull(2))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var deviceId = row[2].ToString();
|
||||
var deviceId = row.GetString(2);
|
||||
if (deviceIds.Contains(deviceId))
|
||||
{
|
||||
continue;
|
||||
@@ -125,7 +123,7 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
|
||||
dbContext.DeviceOptions.Add(new DeviceOptions(deviceId)
|
||||
{
|
||||
CustomName = row[1].IsDbNull() ? null : row[1].ToString()
|
||||
CustomName = row.IsDBNull(1) ? null : row.GetString(1)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -4,15 +4,16 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Emby.Server.Implementations.Data;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Server.Implementations;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SQLitePCL.pretty;
|
||||
|
||||
namespace Jellyfin.Server.Migrations.Routines
|
||||
{
|
||||
@@ -83,22 +84,23 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
var displayPrefs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var customDisplayPrefs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var dbFilePath = Path.Combine(_paths.DataPath, DbFilename);
|
||||
using (var connection = SQLite3.Open(dbFilePath, ConnectionFlags.ReadOnly, null))
|
||||
using (var connection = new SqliteConnection($"Filename={dbFilePath}"))
|
||||
{
|
||||
connection.Open();
|
||||
using var dbContext = _provider.CreateDbContext();
|
||||
|
||||
var results = connection.Query("SELECT * FROM userdisplaypreferences");
|
||||
foreach (var result in results)
|
||||
{
|
||||
var dto = JsonSerializer.Deserialize<DisplayPreferencesDto>(result[3].ToBlob(), _jsonOptions);
|
||||
var dto = JsonSerializer.Deserialize<DisplayPreferencesDto>(result.GetStream(3), _jsonOptions);
|
||||
if (dto is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var itemId = new Guid(result[1].ToBlob());
|
||||
var dtoUserId = new Guid(result[1].ToBlob());
|
||||
var client = result[2].ToString();
|
||||
var itemId = result.GetGuid(1);
|
||||
var dtoUserId = itemId;
|
||||
var client = result.GetString(2);
|
||||
var displayPreferencesKey = $"{dtoUserId}|{itemId}|{client}";
|
||||
if (displayPrefs.Contains(displayPreferencesKey))
|
||||
{
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
||||
using Emby.Server.Implementations.Data;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SQLitePCL.pretty;
|
||||
|
||||
namespace Jellyfin.Server.Migrations.Routines
|
||||
{
|
||||
@@ -20,17 +18,14 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
private readonly ILogger<MigrateRatingLevels> _logger;
|
||||
private readonly IServerApplicationPaths _applicationPaths;
|
||||
private readonly ILocalizationManager _localizationManager;
|
||||
private readonly IItemRepository _repository;
|
||||
|
||||
public MigrateRatingLevels(
|
||||
IServerApplicationPaths applicationPaths,
|
||||
ILoggerFactory loggerFactory,
|
||||
ILocalizationManager localizationManager,
|
||||
IItemRepository repository)
|
||||
ILocalizationManager localizationManager)
|
||||
{
|
||||
_applicationPaths = applicationPaths;
|
||||
_localizationManager = localizationManager;
|
||||
_repository = repository;
|
||||
_logger = loggerFactory.CreateLogger<MigrateRatingLevels>();
|
||||
}
|
||||
|
||||
@@ -70,16 +65,14 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
|
||||
// Migrate parental rating strings to new levels
|
||||
_logger.LogInformation("Recalculating parental rating levels based on rating string.");
|
||||
using (var connection = SQLite3.Open(
|
||||
dbPath,
|
||||
ConnectionFlags.ReadWrite,
|
||||
null))
|
||||
using var connection = new SqliteConnection($"Filename={dbPath}");
|
||||
connection.Open();
|
||||
using (var transaction = connection.BeginTransaction())
|
||||
{
|
||||
var queryResult = connection.Query("SELECT DISTINCT OfficialRating FROM TypedBaseItems");
|
||||
foreach (var entry in queryResult)
|
||||
{
|
||||
var ratingString = entry[0].ToString();
|
||||
if (string.IsNullOrEmpty(ratingString))
|
||||
if (!entry.TryGetString(0, out var ratingString) || string.IsNullOrEmpty(ratingString))
|
||||
{
|
||||
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = NULL WHERE OfficialRating IS NULL OR OfficialRating='';");
|
||||
}
|
||||
@@ -91,12 +84,14 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
ratingValue = "NULL";
|
||||
}
|
||||
|
||||
var statement = connection.PrepareStatement("UPDATE TypedBaseItems SET InheritedParentalRatingValue = @Value WHERE OfficialRating = @Rating;");
|
||||
using var statement = connection.PrepareStatement("UPDATE TypedBaseItems SET InheritedParentalRatingValue = @Value WHERE OfficialRating = @Rating;");
|
||||
statement.TryBind("@Value", ratingValue);
|
||||
statement.TryBind("@Rating", ratingString);
|
||||
statement.ExecuteQuery();
|
||||
statement.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Users;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SQLitePCL.pretty;
|
||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
|
||||
namespace Jellyfin.Server.Migrations.Routines
|
||||
@@ -64,8 +64,9 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
var dataPath = _paths.DataPath;
|
||||
_logger.LogInformation("Migrating the user database may take a while, do not stop Jellyfin.");
|
||||
|
||||
using (var connection = SQLite3.Open(Path.Combine(dataPath, DbFilename), ConnectionFlags.ReadOnly, null))
|
||||
using (var connection = new SqliteConnection($"Filename={Path.Combine(dataPath, DbFilename)}"))
|
||||
{
|
||||
connection.Open();
|
||||
var dbContext = _provider.CreateDbContext();
|
||||
|
||||
var queryResult = connection.Query("SELECT * FROM LocalUsersv2");
|
||||
@@ -75,7 +76,7 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
|
||||
foreach (var entry in queryResult)
|
||||
{
|
||||
UserMockup? mockup = JsonSerializer.Deserialize<UserMockup>(entry[2].ToBlob(), JsonDefaults.Options);
|
||||
UserMockup? mockup = JsonSerializer.Deserialize<UserMockup>(entry.GetStream(2), JsonDefaults.Options);
|
||||
if (mockup is null)
|
||||
{
|
||||
continue;
|
||||
@@ -108,8 +109,8 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
|
||||
var user = new User(mockup.Name, policy.AuthenticationProviderId!, policy.PasswordResetProviderId!)
|
||||
{
|
||||
Id = entry[1].ReadGuidFromBlob(),
|
||||
InternalId = entry[0].ToInt64(),
|
||||
Id = entry.GetGuid(1),
|
||||
InternalId = entry.GetInt64(0),
|
||||
MaxParentalAgeRating = policy.MaxParentalRating,
|
||||
EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess,
|
||||
RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
||||
using System.Linq;
|
||||
using Emby.Server.Implementations.Data;
|
||||
using MediaBrowser.Controller;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SQLitePCL.pretty;
|
||||
|
||||
namespace Jellyfin.Server.Migrations.Routines
|
||||
{
|
||||
@@ -37,14 +38,13 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
{
|
||||
var dataPath = _paths.DataPath;
|
||||
var dbPath = Path.Combine(dataPath, DbFilename);
|
||||
using (var connection = SQLite3.Open(
|
||||
dbPath,
|
||||
ConnectionFlags.ReadWrite,
|
||||
null))
|
||||
using var connection = new SqliteConnection($"Filename={dbPath}");
|
||||
connection.Open();
|
||||
using (var transaction = connection.BeginTransaction())
|
||||
{
|
||||
// Query the database for the ids of duplicate extras
|
||||
var queryResult = connection.Query("SELECT t1.Path FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video'");
|
||||
var bads = string.Join(", ", queryResult.SelectScalarString());
|
||||
var bads = string.Join(", ", queryResult.Select(x => x.GetString(0)));
|
||||
|
||||
// Do nothing if no duplicate extras were detected
|
||||
if (bads.Length == 0)
|
||||
@@ -76,6 +76,7 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
// Delete all duplicate extras
|
||||
_logger.LogInformation("Removing found duplicated extras for the following items: {DuplicateExtras}", bads);
|
||||
connection.Execute("DELETE FROM TypedBaseItems WHERE rowid IN (SELECT t1.rowid FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video')");
|
||||
transaction.Commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommandLine;
|
||||
using Emby.Server.Implementations;
|
||||
@@ -42,7 +41,6 @@ namespace Jellyfin.Server
|
||||
public const string LoggingConfigFileSystem = "logging.json";
|
||||
|
||||
private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory();
|
||||
private static CancellationTokenSource _tokenSource = new();
|
||||
private static long _startTimestamp;
|
||||
private static ILogger _logger = NullLogger.Instance;
|
||||
private static bool _restartOnShutdown;
|
||||
@@ -65,36 +63,9 @@ namespace Jellyfin.Server
|
||||
.MapResult(StartApp, ErrorParsingArguments);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shuts down the application.
|
||||
/// </summary>
|
||||
internal static void Shutdown()
|
||||
{
|
||||
if (!_tokenSource.IsCancellationRequested)
|
||||
{
|
||||
_tokenSource.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restarts the application.
|
||||
/// </summary>
|
||||
internal static void Restart()
|
||||
{
|
||||
_restartOnShutdown = true;
|
||||
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
private static async Task StartApp(StartupOptions options)
|
||||
{
|
||||
_startTimestamp = Stopwatch.GetTimestamp();
|
||||
|
||||
// Log all uncaught exceptions to std error
|
||||
static void UnhandledExceptionToConsole(object sender, UnhandledExceptionEventArgs e) =>
|
||||
Console.Error.WriteLine("Unhandled Exception\n" + e.ExceptionObject);
|
||||
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionToConsole;
|
||||
|
||||
ServerApplicationPaths appPaths = StartupHelpers.CreateApplicationPaths(options);
|
||||
|
||||
// $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager
|
||||
@@ -112,38 +83,10 @@ namespace Jellyfin.Server
|
||||
StartupHelpers.InitializeLoggingFramework(startupConfig, appPaths);
|
||||
_logger = _loggerFactory.CreateLogger("Main");
|
||||
|
||||
// Log uncaught exceptions to the logging instead of std error
|
||||
AppDomain.CurrentDomain.UnhandledException -= UnhandledExceptionToConsole;
|
||||
// Use the logging framework for uncaught exceptions instead of std error
|
||||
AppDomain.CurrentDomain.UnhandledException += (_, e)
|
||||
=> _logger.LogCritical((Exception)e.ExceptionObject, "Unhandled Exception");
|
||||
|
||||
// Intercept Ctrl+C and Ctrl+Break
|
||||
Console.CancelKeyPress += (_, e) =>
|
||||
{
|
||||
if (_tokenSource.IsCancellationRequested)
|
||||
{
|
||||
return; // Already shutting down
|
||||
}
|
||||
|
||||
e.Cancel = true;
|
||||
_logger.LogInformation("Ctrl+C, shutting down");
|
||||
Environment.ExitCode = 128 + 2;
|
||||
Shutdown();
|
||||
};
|
||||
|
||||
// Register a SIGTERM handler
|
||||
AppDomain.CurrentDomain.ProcessExit += (_, _) =>
|
||||
{
|
||||
if (_tokenSource.IsCancellationRequested)
|
||||
{
|
||||
return; // Already shutting down
|
||||
}
|
||||
|
||||
_logger.LogInformation("Received a SIGTERM signal, shutting down");
|
||||
Environment.ExitCode = 128 + 15;
|
||||
Shutdown();
|
||||
};
|
||||
|
||||
_logger.LogInformation(
|
||||
"Jellyfin version: {Version}",
|
||||
Assembly.GetEntryAssembly()!.GetName().Version!.ToString(3));
|
||||
@@ -173,12 +116,10 @@ namespace Jellyfin.Server
|
||||
|
||||
do
|
||||
{
|
||||
_restartOnShutdown = false;
|
||||
await StartServer(appPaths, options, startupConfig).ConfigureAwait(false);
|
||||
|
||||
if (_restartOnShutdown)
|
||||
{
|
||||
_tokenSource = new CancellationTokenSource();
|
||||
_startTimestamp = Stopwatch.GetTimestamp();
|
||||
}
|
||||
} while (_restartOnShutdown);
|
||||
@@ -186,7 +127,7 @@ namespace Jellyfin.Server
|
||||
|
||||
private static async Task StartServer(IServerApplicationPaths appPaths, StartupOptions options, IConfiguration startupConfig)
|
||||
{
|
||||
var appHost = new CoreAppHost(
|
||||
using var appHost = new CoreAppHost(
|
||||
appPaths,
|
||||
_loggerFactory,
|
||||
options,
|
||||
@@ -196,6 +137,7 @@ namespace Jellyfin.Server
|
||||
try
|
||||
{
|
||||
host = Host.CreateDefaultBuilder()
|
||||
.UseConsoleLifetime()
|
||||
.ConfigureServices(services => appHost.Init(services))
|
||||
.ConfigureWebHostDefaults(webHostBuilder => webHostBuilder.ConfigureWebHostBuilder(appHost, startupConfig, appPaths, _logger))
|
||||
.ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(options, appPaths, startupConfig))
|
||||
@@ -210,7 +152,7 @@ namespace Jellyfin.Server
|
||||
|
||||
try
|
||||
{
|
||||
await host.StartAsync(_tokenSource.Token).ConfigureAwait(false);
|
||||
await host.StartAsync().ConfigureAwait(false);
|
||||
|
||||
if (!OperatingSystem.IsWindows() && startupConfig.UseUnixSocket())
|
||||
{
|
||||
@@ -219,22 +161,18 @@ namespace Jellyfin.Server
|
||||
StartupHelpers.SetUnixSocketPermissions(startupConfig, socketPath, _logger);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is not TaskCanceledException)
|
||||
catch (Exception)
|
||||
{
|
||||
_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;
|
||||
}
|
||||
|
||||
await appHost.RunStartupTasksAsync(_tokenSource.Token).ConfigureAwait(false);
|
||||
await appHost.RunStartupTasksAsync().ConfigureAwait(false);
|
||||
|
||||
_logger.LogInformation("Startup complete {Time:g}", Stopwatch.GetElapsedTime(_startTimestamp));
|
||||
|
||||
// Block main thread until shutdown
|
||||
await Task.Delay(-1, _tokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// Don't throw on cancellation
|
||||
await host.WaitForShutdownAsync().ConfigureAwait(false);
|
||||
_restartOnShutdown = appHost.ShouldRestart;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -257,7 +195,6 @@ namespace Jellyfin.Server
|
||||
}
|
||||
}
|
||||
|
||||
await appHost.DisposeAsync().ConfigureAwait(false);
|
||||
host?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
using Emby.Dlna.Extensions;
|
||||
using Jellyfin.Api.Middleware;
|
||||
using Jellyfin.MediaEncoding.Hls.Extensions;
|
||||
using Jellyfin.Networking.Configuration;
|
||||
@@ -27,7 +27,6 @@ using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.VisualBasic;
|
||||
using Prometheus;
|
||||
|
||||
namespace Jellyfin.Server
|
||||
@@ -120,26 +119,11 @@ namespace Jellyfin.Server
|
||||
})
|
||||
.ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate);
|
||||
|
||||
services.AddHttpClient(NamedClient.Dlna, c =>
|
||||
{
|
||||
c.DefaultRequestHeaders.UserAgent.ParseAdd(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0}/{1} UPnP/1.0 {2}/{3}",
|
||||
Environment.OSVersion.Platform,
|
||||
Environment.OSVersion,
|
||||
_serverApplicationHost.Name,
|
||||
_serverApplicationHost.ApplicationVersionString));
|
||||
|
||||
c.DefaultRequestHeaders.Add("CPFN.UPNP.ORG", _serverApplicationHost.FriendlyName); // Required for UPnP DeviceArchitecture v2.0
|
||||
c.DefaultRequestHeaders.Add("FriendlyName.DLNA.ORG", _serverApplicationHost.FriendlyName); // REVIEW: where does this come from?
|
||||
})
|
||||
.ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate);
|
||||
|
||||
services.AddHealthChecks()
|
||||
.AddCheck<DbContextFactoryHealthCheck<JellyfinDbContext>>(nameof(JellyfinDbContext));
|
||||
|
||||
services.AddHlsPlaylistGenerator();
|
||||
services.AddDlnaServices(_serverApplicationHost);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -213,7 +197,7 @@ namespace Jellyfin.Server
|
||||
mainApp.UseAuthorization();
|
||||
|
||||
mainApp.UseLanFiltering();
|
||||
mainApp.UseIpBasedAccessValidation();
|
||||
mainApp.UseIPBasedAccessValidation();
|
||||
mainApp.UseWebSocketHandler();
|
||||
mainApp.UseServerStartupMessage();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user