2024-08-30 15:29:48 +02:00
using System ;
2025-01-26 20:45:28 +00:00
using System.Collections.Generic ;
2025-06-04 01:53:37 +03:00
using System.IO ;
using System.Linq ;
2025-01-26 20:45:28 +00:00
using System.Reflection ;
2025-03-25 15:30:22 +00:00
using Jellyfin.Database.Implementations ;
using Jellyfin.Database.Implementations.DbConfiguration ;
2025-06-04 00:15:22 +03:00
using Jellyfin.Database.Implementations.Locking ;
2025-03-24 10:14:16 +00:00
using Jellyfin.Database.Providers.Sqlite ;
2022-10-21 11:55:32 +02:00
using MediaBrowser.Common.Configuration ;
2025-01-26 20:45:28 +00:00
using MediaBrowser.Controller.Configuration ;
2022-10-21 11:55:32 +02:00
using Microsoft.EntityFrameworkCore ;
2025-01-27 17:20:14 +00:00
using Microsoft.Extensions.Configuration ;
2022-10-21 11:55:32 +02:00
using Microsoft.Extensions.DependencyInjection ;
2025-03-25 15:30:22 +00:00
using JellyfinDbProviderFactory = System . Func < System . IServiceProvider , Jellyfin . Database . Implementations . IJellyfinDatabaseProvider > ;
2022-10-21 11:55:32 +02:00
namespace Jellyfin.Server.Implementations.Extensions ;
/// <summary>
/// Extensions for the <see cref="IServiceCollection"/> interface.
/// </summary>
public static class ServiceCollectionExtensions
{
2025-01-27 16:35:46 +00:00
private static IEnumerable < Type > DatabaseProviderTypes ( )
{
yield return typeof ( SqliteDatabaseProvider ) ;
}
2025-01-26 20:45:28 +00:00
private static IDictionary < string , JellyfinDbProviderFactory > GetSupportedDbProviders ( )
{
2025-03-24 10:07:52 +00:00
var items = new Dictionary < string , JellyfinDbProviderFactory > ( StringComparer . InvariantCultureIgnoreCase ) ;
2025-01-27 16:35:46 +00:00
foreach ( var providerType in DatabaseProviderTypes ( ) )
2025-01-26 20:45:28 +00:00
{
var keyAttribute = providerType . GetCustomAttribute < JellyfinDatabaseProviderKeyAttribute > ( ) ;
if ( keyAttribute is null | | string . IsNullOrWhiteSpace ( keyAttribute . DatabaseProviderKey ) )
{
continue ;
}
var provider = providerType ;
2025-03-24 10:07:52 +00:00
items [ keyAttribute . DatabaseProviderKey ] = ( services ) = > ( IJellyfinDatabaseProvider ) ActivatorUtilities . CreateInstance ( services , providerType ) ;
2025-01-26 20:45:28 +00:00
}
return items ;
}
2025-06-04 01:53:37 +03:00
private static JellyfinDbProviderFactory ? LoadDatabasePlugin ( CustomDatabaseOptions customProviderOptions , IApplicationPaths applicationPaths )
{
var plugin = Directory . EnumerateDirectories ( applicationPaths . PluginsPath )
. Where ( e = > Path . GetFileName ( e ) ! . StartsWith ( customProviderOptions . PluginName , StringComparison . OrdinalIgnoreCase ) )
. Order ( )
. FirstOrDefault ( )
? ? throw new InvalidOperationException ( $"The requested custom database plugin with the name '{customProviderOptions.PluginName}' could not been found in '{applicationPaths.PluginsPath}'" ) ;
var dbProviderAssembly = Path . Combine ( plugin , Path . ChangeExtension ( customProviderOptions . PluginAssembly , "dll" ) ) ;
if ( ! File . Exists ( dbProviderAssembly ) )
{
throw new InvalidOperationException ( $"Could not find the requested assembly at '{dbProviderAssembly}'" ) ;
}
// we have to load the assembly without proxy to ensure maximum performance for this.
var assembly = Assembly . LoadFrom ( dbProviderAssembly ) ;
var dbProviderType = assembly . GetExportedTypes ( ) . FirstOrDefault ( f = > f . IsAssignableTo ( typeof ( IJellyfinDatabaseProvider ) ) )
? ? throw new InvalidOperationException ( $"Could not find any type implementing the '{nameof(IJellyfinDatabaseProvider)}' interface." ) ;
return ( services ) = > ( IJellyfinDatabaseProvider ) ActivatorUtilities . CreateInstance ( services , dbProviderType ) ;
}
2022-10-21 11:55:32 +02:00
/// <summary>
/// Adds the <see cref="IDbContextFactory{TContext}"/> interface to the service collection with second level caching enabled.
/// </summary>
/// <param name="serviceCollection">An instance of the <see cref="IServiceCollection"/> interface.</param>
2025-01-26 20:45:28 +00:00
/// <param name="configurationManager">The server configuration manager.</param>
2025-01-27 17:20:14 +00:00
/// <param name="configuration">The startup Configuration.</param>
2022-10-21 11:55:32 +02:00
/// <returns>The updated service collection.</returns>
2025-01-27 17:20:14 +00:00
public static IServiceCollection AddJellyfinDbContext (
this IServiceCollection serviceCollection ,
IServerConfigurationManager configurationManager ,
IConfiguration configuration )
2022-10-21 11:55:32 +02:00
{
2025-01-26 20:45:28 +00:00
var efCoreConfiguration = configurationManager . GetConfiguration < DatabaseConfigurationOptions > ( "database" ) ;
JellyfinDbProviderFactory ? providerFactory = null ;
2025-01-27 16:35:46 +00:00
if ( efCoreConfiguration ? . DatabaseType is null )
2025-01-26 20:45:28 +00:00
{
2025-01-27 17:20:14 +00:00
var cmdMigrationArgument = configuration . GetValue < string > ( "migration-provider" ) ;
if ( ! string . IsNullOrWhiteSpace ( cmdMigrationArgument ) )
2025-01-26 20:45:28 +00:00
{
2025-01-27 17:20:14 +00:00
efCoreConfiguration = new DatabaseConfigurationOptions ( )
{
DatabaseType = cmdMigrationArgument ,
} ;
}
else
{
2025-02-03 20:15:36 +00:00
// when nothing is setup via new Database configuration, fallback to SQLite with default settings.
2025-01-27 17:20:14 +00:00
efCoreConfiguration = new DatabaseConfigurationOptions ( )
{
2025-02-03 20:15:36 +00:00
DatabaseType = "Jellyfin-SQLite" ,
2025-06-04 00:15:22 +03:00
LockingBehavior = DatabaseLockingBehaviorTypes . NoLock
2025-01-27 17:20:14 +00:00
} ;
2025-01-27 18:21:47 +00:00
configurationManager . SaveConfiguration ( "database" , efCoreConfiguration ) ;
2025-01-27 17:20:14 +00:00
}
2025-01-26 20:45:28 +00:00
}
2025-01-27 16:35:46 +00:00
2025-06-04 01:53:37 +03:00
if ( efCoreConfiguration . DatabaseType . Equals ( "PLUGIN_PROVIDER" , StringComparison . OrdinalIgnoreCase ) )
2025-01-26 20:45:28 +00:00
{
2025-06-04 01:53:37 +03:00
if ( efCoreConfiguration . CustomProviderOptions is null )
{
throw new InvalidOperationException ( "The custom database provider must declare the custom provider options to work" ) ;
}
providerFactory = LoadDatabasePlugin ( efCoreConfiguration . CustomProviderOptions , configurationManager . ApplicationPaths ) ;
}
else
{
var providers = GetSupportedDbProviders ( ) ;
if ( ! providers . TryGetValue ( efCoreConfiguration . DatabaseType . ToUpperInvariant ( ) , out providerFactory ! ) )
{
throw new InvalidOperationException ( $"Jellyfin cannot find the database provider of type '{efCoreConfiguration.DatabaseType}'. Supported types are {string.Join(" , ", providers.Keys)}" ) ;
}
2025-01-26 20:45:28 +00:00
}
serviceCollection . AddSingleton < IJellyfinDatabaseProvider > ( providerFactory ! ) ;
2025-06-04 00:15:22 +03:00
switch ( efCoreConfiguration . LockingBehavior )
{
case DatabaseLockingBehaviorTypes . NoLock :
serviceCollection . AddSingleton < IEntityFrameworkCoreLockingBehavior , NoLockBehavior > ( ) ;
break ;
case DatabaseLockingBehaviorTypes . Pessimistic :
serviceCollection . AddSingleton < IEntityFrameworkCoreLockingBehavior , PessimisticLockBehavior > ( ) ;
break ;
case DatabaseLockingBehaviorTypes . Optimistic :
serviceCollection . AddSingleton < IEntityFrameworkCoreLockingBehavior , OptimisticLockBehavior > ( ) ;
break ;
}
2023-01-16 12:14:44 -05:00
serviceCollection . AddPooledDbContextFactory < JellyfinDbContext > ( ( serviceProvider , opt ) = >
2022-10-21 11:55:32 +02:00
{
2025-01-26 20:45:28 +00:00
var provider = serviceProvider . GetRequiredService < IJellyfinDatabaseProvider > ( ) ;
2025-07-15 03:39:43 +03:00
provider . Initialise ( opt , efCoreConfiguration ) ;
2025-06-04 00:15:22 +03:00
var lockingBehavior = serviceProvider . GetRequiredService < IEntityFrameworkCoreLockingBehavior > ( ) ;
lockingBehavior . Initialise ( opt ) ;
2022-10-21 11:55:32 +02:00
} ) ;
return serviceCollection ;
}
}