diff options
Diffstat (limited to 'Jellyfin.Server')
10 files changed, 373 insertions, 113 deletions
diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 40cd5a044..0c6315c66 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -22,7 +22,7 @@ using MediaBrowser.Controller.Lyrics; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; using MediaBrowser.Model.Activity; -using Microsoft.EntityFrameworkCore; +using MediaBrowser.Providers.Lyric; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -94,6 +94,11 @@ namespace Jellyfin.Server serviceCollection.AddSingleton(typeof(ILyricProvider), type); } + foreach (var type in GetExportTypes<ILyricParser>()) + { + serviceCollection.AddSingleton(typeof(ILyricParser), type); + } + base.RegisterServices(serviceCollection); } diff --git a/Jellyfin.Server/Filters/AdditionalModelFilter.cs b/Jellyfin.Server/Filters/AdditionalModelFilter.cs index 645696e31..bf38f741c 100644 --- a/Jellyfin.Server/Filters/AdditionalModelFilter.cs +++ b/Jellyfin.Server/Filters/AdditionalModelFilter.cs @@ -1,12 +1,16 @@ using System; +using System.Collections.Generic; +using System.ComponentModel; using System.Linq; +using System.Reflection; using Jellyfin.Extensions; using Jellyfin.Server.Migrations; using MediaBrowser.Common.Plugins; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Net.WebSocketMessages; +using MediaBrowser.Controller.Net.WebSocketMessages.Outbound; using MediaBrowser.Model.ApiClient; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.Session; using MediaBrowser.Model.SyncPlay; using Microsoft.OpenApi.Any; @@ -36,17 +40,141 @@ namespace Jellyfin.Server.Filters /// <inheritdoc /> public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { - context.SchemaGenerator.GenerateSchema(typeof(LibraryUpdateInfo), context.SchemaRepository); context.SchemaGenerator.GenerateSchema(typeof(IPlugin), context.SchemaRepository); - context.SchemaGenerator.GenerateSchema(typeof(PlayRequest), context.SchemaRepository); - context.SchemaGenerator.GenerateSchema(typeof(PlaystateRequest), context.SchemaRepository); - context.SchemaGenerator.GenerateSchema(typeof(TimerEventInfo), context.SchemaRepository); - context.SchemaGenerator.GenerateSchema(typeof(SendCommand), context.SchemaRepository); - context.SchemaGenerator.GenerateSchema(typeof(GeneralCommandType), context.SchemaRepository); - context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate<object>), context.SchemaRepository); + var webSocketTypes = typeof(WebSocketMessage).Assembly.GetTypes() + .Where(t => t.IsSubclassOf(typeof(WebSocketMessage)) + && !t.IsGenericType + && t != typeof(WebSocketMessageInfo)) + .ToList(); + + var inboundWebSocketSchemas = new List<OpenApiSchema>(); + var inboundWebSocketDiscriminators = new Dictionary<string, string>(); + foreach (var type in webSocketTypes.Where(t => typeof(IInboundWebSocketMessage).IsAssignableFrom(t))) + { + var messageType = (SessionMessageType?)type.GetProperty(nameof(WebSocketMessage.MessageType))?.GetCustomAttribute<DefaultValueAttribute>()?.Value; + if (messageType is null) + { + continue; + } + + var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository); + inboundWebSocketSchemas.Add(schema); + inboundWebSocketDiscriminators[messageType.ToString()!] = schema.Reference.ReferenceV3; + } + + var inboundWebSocketMessageSchema = new OpenApiSchema + { + Type = "object", + Description = "Represents the list of possible inbound websocket types", + Reference = new OpenApiReference + { + Id = nameof(InboundWebSocketMessage), + Type = ReferenceType.Schema + }, + OneOf = inboundWebSocketSchemas, + Discriminator = new OpenApiDiscriminator + { + PropertyName = nameof(WebSocketMessage.MessageType), + Mapping = inboundWebSocketDiscriminators + } + }; + + context.SchemaRepository.AddDefinition(nameof(InboundWebSocketMessage), inboundWebSocketMessageSchema); + + var outboundWebSocketSchemas = new List<OpenApiSchema>(); + var outboundWebSocketDiscriminators = new Dictionary<string, string>(); + foreach (var type in webSocketTypes.Where(t => typeof(IOutboundWebSocketMessage).IsAssignableFrom(t))) + { + var messageType = (SessionMessageType?)type.GetProperty(nameof(WebSocketMessage.MessageType))?.GetCustomAttribute<DefaultValueAttribute>()?.Value; + if (messageType is null) + { + continue; + } + + // Additional discriminator needed for GroupUpdate models... + if (messageType == SessionMessageType.SyncPlayGroupUpdate && type != typeof(SyncPlayGroupUpdateCommandMessage)) + { + continue; + } + + var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository); + outboundWebSocketSchemas.Add(schema); + outboundWebSocketDiscriminators.Add(messageType.ToString()!, schema.Reference.ReferenceV3); + } + + var outboundWebSocketMessageSchema = new OpenApiSchema + { + Type = "object", + Description = "Represents the list of possible outbound websocket types", + Reference = new OpenApiReference + { + Id = nameof(OutboundWebSocketMessage), + Type = ReferenceType.Schema + }, + OneOf = outboundWebSocketSchemas, + Discriminator = new OpenApiDiscriminator + { + PropertyName = nameof(WebSocketMessage.MessageType), + Mapping = outboundWebSocketDiscriminators + } + }; + + context.SchemaRepository.AddDefinition(nameof(OutboundWebSocketMessage), outboundWebSocketMessageSchema); + context.SchemaRepository.AddDefinition( + nameof(WebSocketMessage), + new OpenApiSchema + { + Type = "object", + Description = "Represents the possible websocket types", + Reference = new OpenApiReference + { + Id = nameof(WebSocketMessage), + Type = ReferenceType.Schema + }, + OneOf = new[] + { + inboundWebSocketMessageSchema, + outboundWebSocketMessageSchema + } + }); + + // Manually generate sync play GroupUpdate messages. + if (!context.SchemaRepository.Schemas.TryGetValue(nameof(GroupUpdate), out var groupUpdateSchema)) + { + groupUpdateSchema = context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate), context.SchemaRepository); + } + + var groupUpdateOfGroupInfoSchema = context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate<GroupInfoDto>), context.SchemaRepository); + var groupUpdateOfGroupStateSchema = context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate<GroupStateUpdate>), context.SchemaRepository); + var groupUpdateOfStringSchema = context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate<string>), context.SchemaRepository); + var groupUpdateOfPlayQueueSchema = context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate<PlayQueueUpdate>), context.SchemaRepository); + + groupUpdateSchema.OneOf = new List<OpenApiSchema> + { + groupUpdateOfGroupInfoSchema, + groupUpdateOfGroupStateSchema, + groupUpdateOfStringSchema, + groupUpdateOfPlayQueueSchema + }; + + groupUpdateSchema.Discriminator = new OpenApiDiscriminator + { + PropertyName = nameof(GroupUpdate.Type), + Mapping = new Dictionary<string, string> + { + { GroupUpdateType.UserJoined.ToString(), groupUpdateOfStringSchema.Reference.ReferenceV3 }, + { GroupUpdateType.UserLeft.ToString(), groupUpdateOfStringSchema.Reference.ReferenceV3 }, + { GroupUpdateType.GroupJoined.ToString(), groupUpdateOfGroupInfoSchema.Reference.ReferenceV3 }, + { GroupUpdateType.GroupLeft.ToString(), groupUpdateOfStringSchema.Reference.ReferenceV3 }, + { GroupUpdateType.StateUpdate.ToString(), groupUpdateOfGroupStateSchema.Reference.ReferenceV3 }, + { GroupUpdateType.PlayQueue.ToString(), groupUpdateOfPlayQueueSchema.Reference.ReferenceV3 }, + { GroupUpdateType.NotInGroup.ToString(), groupUpdateOfStringSchema.Reference.ReferenceV3 }, + { GroupUpdateType.GroupDoesNotExist.ToString(), groupUpdateOfStringSchema.Reference.ReferenceV3 }, + { GroupUpdateType.LibraryAccessDenied.ToString(), groupUpdateOfStringSchema.Reference.ReferenceV3 } + } + }; - context.SchemaGenerator.GenerateSchema(typeof(SessionMessageType), context.SchemaRepository); context.SchemaGenerator.GenerateSchema(typeof(ServerDiscoveryInfo), context.SchemaRepository); foreach (var configuration in _serverConfigurationManager.GetConfigurationStores()) diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index d4bf81f10..abfdcd77d 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -22,8 +22,7 @@ namespace Jellyfin.Server.Migrations private static readonly Type[] _preStartupMigrationTypes = { typeof(PreStartupRoutines.CreateNetworkConfiguration), - typeof(PreStartupRoutines.MigrateMusicBrainzTimeout), - typeof(PreStartupRoutines.MigrateRatingLevels) + typeof(PreStartupRoutines.MigrateMusicBrainzTimeout) }; /// <summary> @@ -40,7 +39,9 @@ namespace Jellyfin.Server.Migrations typeof(Routines.ReaddDefaultPluginRepository), typeof(Routines.MigrateDisplayPreferencesDb), typeof(Routines.RemoveDownloadImagesInAdvance), - typeof(Routines.MigrateAuthenticationDb) + typeof(Routines.MigrateAuthenticationDb), + typeof(Routines.FixPlaylistOwner), + typeof(Routines.MigrateRatingLevels) }; /// <summary> diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs index 14b51bd4c..bee135efd 100644 --- a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs @@ -44,9 +44,7 @@ public class MigrateMusicBrainzTimeout : IMigrationRoutine return; } - var serverConfigSerializer = new XmlSerializer(typeof(OldMusicBrainzConfiguration), new XmlRootAttribute("PluginConfiguration")); - using var xmlReader = XmlReader.Create(path); - var oldPluginConfiguration = serverConfigSerializer.Deserialize(xmlReader) as OldMusicBrainzConfiguration; + var oldPluginConfiguration = ReadOld(path); if (oldPluginConfiguration is not null) { @@ -55,10 +53,25 @@ public class MigrateMusicBrainzTimeout : IMigrationRoutine newPluginConfiguration.ReplaceArtistName = oldPluginConfiguration.ReplaceArtistName; var newRateLimit = oldPluginConfiguration.RateLimit / 1000.0; newPluginConfiguration.RateLimit = newRateLimit < 1.0 ? 1.0 : newRateLimit; + WriteNew(path, newPluginConfiguration); + } + } - var pluginConfigurationSerializer = new XmlSerializer(typeof(PluginConfiguration), new XmlRootAttribute("PluginConfiguration")); - var xmlWriterSettings = new XmlWriterSettings { Indent = true }; - using var xmlWriter = XmlWriter.Create(path, xmlWriterSettings); + 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; + } + } + + 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); } } diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateRatingLevels.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateRatingLevels.cs deleted file mode 100644 index 465bbd7fe..000000000 --- a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateRatingLevels.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Globalization; -using System.IO; - -using Emby.Server.Implementations; -using MediaBrowser.Controller; -using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; - -namespace Jellyfin.Server.Migrations.PreStartupRoutines -{ - /// <summary> - /// Migrate rating levels to new rating level system. - /// </summary> - internal class MigrateRatingLevels : IMigrationRoutine - { - private const string DbFilename = "library.db"; - private readonly ILogger<MigrateRatingLevels> _logger; - private readonly IServerApplicationPaths _applicationPaths; - - public MigrateRatingLevels(ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory) - { - _applicationPaths = applicationPaths; - _logger = loggerFactory.CreateLogger<MigrateRatingLevels>(); - } - - /// <inheritdoc/> - public Guid Id => Guid.Parse("{67445D54-B895-4B24-9F4C-35CE0690EA07}"); - - /// <inheritdoc/> - public string Name => "MigrateRatingLevels"; - - /// <inheritdoc/> - public bool PerformOnNewInstall => false; - - /// <inheritdoc/> - public void Perform() - { - var dataPath = _applicationPaths.DataPath; - var dbPath = Path.Combine(dataPath, DbFilename); - using (var connection = SQLite3.Open( - dbPath, - ConnectionFlags.ReadWrite, - null)) - { - // Back up the database before deleting any entries - for (int i = 1; ; i++) - { - var bakPath = string.Format(CultureInfo.InvariantCulture, "{0}.bak{1}", dbPath, i); - if (!File.Exists(bakPath)) - { - try - { - File.Copy(dbPath, bakPath); - _logger.LogInformation("Library database backed up to {BackupPath}", bakPath); - break; - } - catch (Exception ex) - { - _logger.LogError(ex, "Cannot make a backup of {Library} at path {BackupPath}", DbFilename, bakPath); - throw; - } - } - } - - // Migrate parental rating levels to new schema - _logger.LogInformation("Migrating parental rating levels."); - connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = NULL WHERE OfficialRating = 'NR'"); - connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = NULL WHERE InheritedParentalRatingValue = ''"); - connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = NULL WHERE InheritedParentalRatingValue = 0"); - connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 1000 WHERE InheritedParentalRatingValue = 100"); - connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 1000 WHERE InheritedParentalRatingValue = 15"); - connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 18 WHERE InheritedParentalRatingValue = 10"); - connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 18 WHERE InheritedParentalRatingValue = 9"); - connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 16 WHERE InheritedParentalRatingValue = 8"); - connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 12 WHERE InheritedParentalRatingValue = 7"); - connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 12 WHERE InheritedParentalRatingValue = 6"); - connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 12 WHERE InheritedParentalRatingValue = 5"); - connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 7 WHERE InheritedParentalRatingValue = 4"); - connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 6 WHERE InheritedParentalRatingValue = 3"); - connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 6 WHERE InheritedParentalRatingValue = 2"); - connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 0 WHERE InheritedParentalRatingValue = 1"); - } - } - } -} diff --git a/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs b/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs new file mode 100644 index 000000000..cf3182003 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs @@ -0,0 +1,76 @@ +using System; +using System.Linq; +using System.Threading; + +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Playlists; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines; + +/// <summary> +/// Properly set playlist owner. +/// </summary> +internal class FixPlaylistOwner : IMigrationRoutine +{ + private readonly ILogger<RemoveDuplicateExtras> _logger; + private readonly ILibraryManager _libraryManager; + private readonly IPlaylistManager _playlistManager; + + public FixPlaylistOwner( + ILogger<RemoveDuplicateExtras> logger, + ILibraryManager libraryManager, + IPlaylistManager playlistManager) + { + _logger = logger; + _libraryManager = libraryManager; + _playlistManager = playlistManager; + } + + /// <inheritdoc/> + public Guid Id => Guid.Parse("{615DFA9E-2497-4DBB-A472-61938B752C5B}"); + + /// <inheritdoc/> + public string Name => "FixPlaylistOwner"; + + /// <inheritdoc/> + public bool PerformOnNewInstall => false; + + /// <inheritdoc/> + public void Perform() + { + var playlists = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = new[] { BaseItemKind.Playlist } + }) + .Cast<Playlist>() + .Where(x => x.OwnerUserId.Equals(Guid.Empty)) + .ToArray(); + + if (playlists.Length > 0) + { + foreach (var playlist in playlists) + { + var shares = playlist.Shares; + if (shares.Length > 0) + { + var firstEditShare = shares.First(x => x.CanEdit); + if (firstEditShare is not null && Guid.TryParse(firstEditShare.UserId, out var guid)) + { + playlist.OwnerUserId = guid; + playlist.Shares = shares.Where(x => x != firstEditShare).ToArray(); + playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult(); + _playlistManager.SavePlaylistFile(playlist); + } + } + else + { + playlist.OpenAccess = true; + playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult(); + } + } + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs index 7c4ffdbc0..8fe2b087d 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs @@ -133,9 +133,7 @@ namespace Jellyfin.Server.Migrations.Routines SkipBackwardLength = dto.CustomPrefs.TryGetValue("skipBackLength", out length) && int.TryParse(length, out var skipBackwardLength) ? skipBackwardLength : 10000, - EnableNextVideoInfoOverlay = dto.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enabled) && !string.IsNullOrEmpty(enabled) - ? bool.Parse(enabled) - : true, + EnableNextVideoInfoOverlay = !dto.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enabled) || string.IsNullOrEmpty(enabled) || bool.Parse(enabled), DashboardTheme = dto.CustomPrefs.TryGetValue("dashboardtheme", out var theme) ? theme : string.Empty, TvHome = dto.CustomPrefs.TryGetValue("tvhome", out var home) ? home : string.Empty }; diff --git a/Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs b/Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs new file mode 100644 index 000000000..9dee520a5 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs @@ -0,0 +1,103 @@ +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.Extensions.Logging; +using SQLitePCL.pretty; + +namespace Jellyfin.Server.Migrations.Routines +{ + /// <summary> + /// Migrate rating levels to new rating level system. + /// </summary> + internal class MigrateRatingLevels : IMigrationRoutine + { + private const string DbFilename = "library.db"; + 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) + { + _applicationPaths = applicationPaths; + _localizationManager = localizationManager; + _repository = repository; + _logger = loggerFactory.CreateLogger<MigrateRatingLevels>(); + } + + /// <inheritdoc/> + public Guid Id => Guid.Parse("{67445D54-B895-4B24-9F4C-35CE0690EA07}"); + + /// <inheritdoc/> + public string Name => "MigrateRatingLevels"; + + /// <inheritdoc/> + public bool PerformOnNewInstall => false; + + /// <inheritdoc/> + public void Perform() + { + var dbPath = Path.Combine(_applicationPaths.DataPath, DbFilename); + + // Back up the database before modifying any entries + for (int i = 1; ; i++) + { + var bakPath = string.Format(CultureInfo.InvariantCulture, "{0}.bak{1}", dbPath, i); + if (!File.Exists(bakPath)) + { + try + { + File.Copy(dbPath, bakPath); + _logger.LogInformation("Library database backed up to {BackupPath}", bakPath); + break; + } + catch (Exception ex) + { + _logger.LogError(ex, "Cannot make a backup of {Library} at path {BackupPath}", DbFilename, bakPath); + throw; + } + } + } + + // 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)) + { + var queryResult = connection.Query("SELECT DISTINCT OfficialRating FROM TypedBaseItems"); + foreach (var entry in queryResult) + { + var ratingString = entry[0].ToString(); + if (string.IsNullOrEmpty(ratingString)) + { + connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = NULL WHERE OfficialRating IS NULL OR OfficialRating='';"); + } + else + { + var ratingValue = _localizationManager.GetRatingLevel(ratingString).ToString(); + if (string.IsNullOrEmpty(ratingValue)) + { + ratingValue = "NULL"; + } + + var statement = connection.PrepareStatement("UPDATE TypedBaseItems SET InheritedParentalRatingValue = @Value WHERE OfficialRating = @Rating;"); + statement.TryBind("@Value", ratingValue); + statement.TryBind("@Rating", ratingString); + statement.ExecuteQuery(); + } + } + } + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index 9bf1e6b80..0186500a1 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -127,7 +127,6 @@ namespace Jellyfin.Server.Migrations.Routines RememberSubtitleSelections = config.RememberSubtitleSelections, SubtitleLanguagePreference = config.SubtitleLanguagePreference, Password = mockup.Password, - EasyPassword = mockup.EasyPassword, LastLoginDate = mockup.LastLoginDate, LastActivityDate = mockup.LastActivityDate }; diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs index 155f9fc8c..6394800f7 100644 --- a/Jellyfin.Server/Startup.cs +++ b/Jellyfin.Server/Startup.cs @@ -4,11 +4,11 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Net.Mime; -using System.Runtime.InteropServices; using System.Text; using Jellyfin.Api.Middleware; using Jellyfin.MediaEncoding.Hls.Extensions; using Jellyfin.Networking.Configuration; +using Jellyfin.Networking.HappyEyeballs; using Jellyfin.Server.Extensions; using Jellyfin.Server.HealthChecks; using Jellyfin.Server.Implementations; @@ -27,6 +27,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting; +using Microsoft.VisualBasic; using Prometheus; namespace Jellyfin.Server @@ -79,6 +80,13 @@ namespace Jellyfin.Server var acceptJsonHeader = new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json, 1.0); var acceptXmlHeader = new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Xml, 0.9); var acceptAnyHeader = new MediaTypeWithQualityHeaderValue("*/*", 0.8); + Func<IServiceProvider, HttpMessageHandler> eyeballsHttpClientHandlerDelegate = (_) => new SocketsHttpHandler() + { + AutomaticDecompression = DecompressionMethods.All, + RequestHeaderEncodingSelector = (_, _) => Encoding.UTF8, + ConnectCallback = HttpClientExtension.OnConnect + }; + Func<IServiceProvider, HttpMessageHandler> defaultHttpClientHandlerDelegate = (_) => new SocketsHttpHandler() { AutomaticDecompression = DecompressionMethods.All, @@ -92,7 +100,7 @@ namespace Jellyfin.Server c.DefaultRequestHeaders.Accept.Add(acceptXmlHeader); c.DefaultRequestHeaders.Accept.Add(acceptAnyHeader); }) - .ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate); + .ConfigurePrimaryHttpMessageHandler(eyeballsHttpClientHandlerDelegate); services.AddHttpClient(NamedClient.MusicBrainz, c => { @@ -101,6 +109,15 @@ namespace Jellyfin.Server c.DefaultRequestHeaders.Accept.Add(acceptXmlHeader); c.DefaultRequestHeaders.Accept.Add(acceptAnyHeader); }) + .ConfigurePrimaryHttpMessageHandler(eyeballsHttpClientHandlerDelegate); + + services.AddHttpClient(NamedClient.DirectIp, c => + { + c.DefaultRequestHeaders.UserAgent.Add(productHeader); + c.DefaultRequestHeaders.Accept.Add(acceptJsonHeader); + c.DefaultRequestHeaders.Accept.Add(acceptXmlHeader); + c.DefaultRequestHeaders.Accept.Add(acceptAnyHeader); + }) .ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate); services.AddHttpClient(NamedClient.Dlna, c => @@ -165,7 +182,7 @@ namespace Jellyfin.Server // This must be injected before any path related middleware. mainApp.UsePathTrim(); - mainApp.UseStaticFiles(); + if (appConfig.HostWebClient()) { var extensionProvider = new FileExtensionContentTypeProvider(); @@ -173,6 +190,11 @@ namespace Jellyfin.Server // subtitles octopus requires .data, .mem files. extensionProvider.Mappings.Add(".data", MediaTypeNames.Application.Octet); extensionProvider.Mappings.Add(".mem", MediaTypeNames.Application.Octet); + mainApp.UseDefaultFiles(new DefaultFilesOptions + { + FileProvider = new PhysicalFileProvider(_serverConfigurationManager.ApplicationPaths.WebPath), + RequestPath = "/web" + }); mainApp.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(_serverConfigurationManager.ApplicationPaths.WebPath), @@ -183,6 +205,7 @@ namespace Jellyfin.Server mainApp.UseRobotsRedirection(); } + mainApp.UseStaticFiles(); mainApp.UseAuthentication(); mainApp.UseJellyfinApiSwagger(_serverConfigurationManager); mainApp.UseQueryStringDecoding(); |
