| | 1 | | using System; |
| | 2 | | using System.Collections.Generic; |
| | 3 | | using System.IO; |
| | 4 | | using System.Linq; |
| | 5 | | using System.Reflection; |
| | 6 | | using Jellyfin.Database.Implementations; |
| | 7 | | using Jellyfin.Database.Implementations.DbConfiguration; |
| | 8 | | using Jellyfin.Database.Implementations.Locking; |
| | 9 | | using Jellyfin.Database.Providers.Sqlite; |
| | 10 | | using MediaBrowser.Common.Configuration; |
| | 11 | | using MediaBrowser.Controller.Configuration; |
| | 12 | | using Microsoft.EntityFrameworkCore; |
| | 13 | | using Microsoft.Extensions.Configuration; |
| | 14 | | using Microsoft.Extensions.DependencyInjection; |
| | 15 | | using JellyfinDbProviderFactory = System.Func<System.IServiceProvider, Jellyfin.Database.Implementations.IJellyfinDataba |
| | 16 | |
|
| | 17 | | namespace Jellyfin.Server.Implementations.Extensions; |
| | 18 | |
|
| | 19 | | /// <summary> |
| | 20 | | /// Extensions for the <see cref="IServiceCollection"/> interface. |
| | 21 | | /// </summary> |
| | 22 | | public static class ServiceCollectionExtensions |
| | 23 | | { |
| | 24 | | private static IEnumerable<Type> DatabaseProviderTypes() |
| | 25 | | { |
| | 26 | | yield return typeof(SqliteDatabaseProvider); |
| | 27 | | } |
| | 28 | |
|
| | 29 | | private static IDictionary<string, JellyfinDbProviderFactory> GetSupportedDbProviders() |
| | 30 | | { |
| 42 | 31 | | var items = new Dictionary<string, JellyfinDbProviderFactory>(StringComparer.InvariantCultureIgnoreCase); |
| 168 | 32 | | foreach (var providerType in DatabaseProviderTypes()) |
| | 33 | | { |
| 42 | 34 | | var keyAttribute = providerType.GetCustomAttribute<JellyfinDatabaseProviderKeyAttribute>(); |
| 42 | 35 | | if (keyAttribute is null || string.IsNullOrWhiteSpace(keyAttribute.DatabaseProviderKey)) |
| | 36 | | { |
| | 37 | | continue; |
| | 38 | | } |
| | 39 | |
|
| 42 | 40 | | var provider = providerType; |
| 42 | 41 | | items[keyAttribute.DatabaseProviderKey] = (services) => (IJellyfinDatabaseProvider)ActivatorUtilities.Create |
| | 42 | | } |
| | 43 | |
|
| 42 | 44 | | return items; |
| | 45 | | } |
| | 46 | |
|
| | 47 | | private static JellyfinDbProviderFactory? LoadDatabasePlugin(CustomDatabaseOptions customProviderOptions, IApplicati |
| | 48 | | { |
| 0 | 49 | | var plugin = Directory.EnumerateDirectories(applicationPaths.PluginsPath) |
| 0 | 50 | | .Where(e => Path.GetFileName(e)!.StartsWith(customProviderOptions.PluginName, StringComparison.OrdinalIgnore |
| 0 | 51 | | .Order() |
| 0 | 52 | | .FirstOrDefault() |
| 0 | 53 | | ?? throw new InvalidOperationException($"The requested custom database plugin with the name '{customProvider |
| | 54 | |
|
| 0 | 55 | | var dbProviderAssembly = Path.Combine(plugin, Path.ChangeExtension(customProviderOptions.PluginAssembly, "dll")) |
| 0 | 56 | | if (!File.Exists(dbProviderAssembly)) |
| | 57 | | { |
| 0 | 58 | | throw new InvalidOperationException($"Could not find the requested assembly at '{dbProviderAssembly}'"); |
| | 59 | | } |
| | 60 | |
|
| | 61 | | // we have to load the assembly without proxy to ensure maximum performance for this. |
| 0 | 62 | | var assembly = Assembly.LoadFrom(dbProviderAssembly); |
| 0 | 63 | | var dbProviderType = assembly.GetExportedTypes().FirstOrDefault(f => f.IsAssignableTo(typeof(IJellyfinDatabasePr |
| 0 | 64 | | ?? throw new InvalidOperationException($"Could not find any type implementing the '{nameof(IJellyfinDatabase |
| | 65 | |
|
| 0 | 66 | | return (services) => (IJellyfinDatabaseProvider)ActivatorUtilities.CreateInstance(services, dbProviderType); |
| | 67 | | } |
| | 68 | |
|
| | 69 | | /// <summary> |
| | 70 | | /// Adds the <see cref="IDbContextFactory{TContext}"/> interface to the service collection with second level caching |
| | 71 | | /// </summary> |
| | 72 | | /// <param name="serviceCollection">An instance of the <see cref="IServiceCollection"/> interface.</param> |
| | 73 | | /// <param name="configurationManager">The server configuration manager.</param> |
| | 74 | | /// <param name="configuration">The startup Configuration.</param> |
| | 75 | | /// <returns>The updated service collection.</returns> |
| | 76 | | public static IServiceCollection AddJellyfinDbContext( |
| | 77 | | this IServiceCollection serviceCollection, |
| | 78 | | IServerConfigurationManager configurationManager, |
| | 79 | | IConfiguration configuration) |
| | 80 | | { |
| 42 | 81 | | var efCoreConfiguration = configurationManager.GetConfiguration<DatabaseConfigurationOptions>("database"); |
| 42 | 82 | | JellyfinDbProviderFactory? providerFactory = null; |
| | 83 | |
|
| 42 | 84 | | if (efCoreConfiguration?.DatabaseType is null) |
| | 85 | | { |
| 21 | 86 | | var cmdMigrationArgument = configuration.GetValue<string>("migration-provider"); |
| 21 | 87 | | if (!string.IsNullOrWhiteSpace(cmdMigrationArgument)) |
| | 88 | | { |
| 0 | 89 | | efCoreConfiguration = new DatabaseConfigurationOptions() |
| 0 | 90 | | { |
| 0 | 91 | | DatabaseType = cmdMigrationArgument, |
| 0 | 92 | | }; |
| | 93 | | } |
| | 94 | | else |
| | 95 | | { |
| | 96 | | // when nothing is setup via new Database configuration, fallback to SQLite with default settings. |
| 21 | 97 | | efCoreConfiguration = new DatabaseConfigurationOptions() |
| 21 | 98 | | { |
| 21 | 99 | | DatabaseType = "Jellyfin-SQLite", |
| 21 | 100 | | LockingBehavior = DatabaseLockingBehaviorTypes.NoLock |
| 21 | 101 | | }; |
| 21 | 102 | | configurationManager.SaveConfiguration("database", efCoreConfiguration); |
| | 103 | | } |
| | 104 | | } |
| | 105 | |
|
| 42 | 106 | | if (efCoreConfiguration.DatabaseType.Equals("PLUGIN_PROVIDER", StringComparison.OrdinalIgnoreCase)) |
| | 107 | | { |
| 0 | 108 | | if (efCoreConfiguration.CustomProviderOptions is null) |
| | 109 | | { |
| 0 | 110 | | throw new InvalidOperationException("The custom database provider must declare the custom provider optio |
| | 111 | | } |
| | 112 | |
|
| 0 | 113 | | providerFactory = LoadDatabasePlugin(efCoreConfiguration.CustomProviderOptions, configurationManager.Applica |
| | 114 | | } |
| | 115 | | else |
| | 116 | | { |
| 42 | 117 | | var providers = GetSupportedDbProviders(); |
| 42 | 118 | | if (!providers.TryGetValue(efCoreConfiguration.DatabaseType.ToUpperInvariant(), out providerFactory!)) |
| | 119 | | { |
| 0 | 120 | | throw new InvalidOperationException($"Jellyfin cannot find the database provider of type '{efCoreConfigu |
| | 121 | | } |
| | 122 | | } |
| | 123 | |
|
| 42 | 124 | | serviceCollection.AddSingleton<IJellyfinDatabaseProvider>(providerFactory!); |
| | 125 | |
|
| 42 | 126 | | switch (efCoreConfiguration.LockingBehavior) |
| | 127 | | { |
| | 128 | | case DatabaseLockingBehaviorTypes.NoLock: |
| 42 | 129 | | serviceCollection.AddSingleton<IEntityFrameworkCoreLockingBehavior, NoLockBehavior>(); |
| 42 | 130 | | break; |
| | 131 | | case DatabaseLockingBehaviorTypes.Pessimistic: |
| 0 | 132 | | serviceCollection.AddSingleton<IEntityFrameworkCoreLockingBehavior, PessimisticLockBehavior>(); |
| 0 | 133 | | break; |
| | 134 | | case DatabaseLockingBehaviorTypes.Optimistic: |
| 0 | 135 | | serviceCollection.AddSingleton<IEntityFrameworkCoreLockingBehavior, OptimisticLockBehavior>(); |
| | 136 | | break; |
| | 137 | | } |
| | 138 | |
|
| 42 | 139 | | serviceCollection.AddPooledDbContextFactory<JellyfinDbContext>((serviceProvider, opt) => |
| 42 | 140 | | { |
| 42 | 141 | | var provider = serviceProvider.GetRequiredService<IJellyfinDatabaseProvider>(); |
| 42 | 142 | | provider.Initialise(opt); |
| 42 | 143 | | var lockingBehavior = serviceProvider.GetRequiredService<IEntityFrameworkCoreLockingBehavior>(); |
| 42 | 144 | | lockingBehavior.Initialise(opt); |
| 42 | 145 | | }); |
| | 146 | |
|
| 42 | 147 | | return serviceCollection; |
| | 148 | | } |
| | 149 | | } |