Merge branch 'master' into trickplay

This commit is contained in:
Nick
2023-10-18 19:27:05 -07:00
462 changed files with 8749 additions and 10207 deletions

View File

@@ -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();
}
}

View File

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

View File

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

View File

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

View File

@@ -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();
}
}

View File

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

View File

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

View File

@@ -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>();

View File

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

View File

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

View File

@@ -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();
}
}
}

View File

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

View File

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

View File

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

View File

@@ -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();
}
}
}

View File

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

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}

View File

@@ -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();