123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- using System;
- using System.Collections.Generic;
- using System.Globalization;
- using System.IO;
- using System.Linq;
- using System.Threading;
- using System.Threading.Tasks;
- using Jellyfin.Database.Implementations;
- using Jellyfin.Database.Implementations.DbConfiguration;
- using MediaBrowser.Common.Configuration;
- using Microsoft.Data.Sqlite;
- using Microsoft.EntityFrameworkCore;
- using Microsoft.EntityFrameworkCore.Diagnostics;
- using Microsoft.Extensions.Logging;
- namespace Jellyfin.Database.Providers.Sqlite;
- /// <summary>
- /// Configures jellyfin to use an SQLite database.
- /// </summary>
- [JellyfinDatabaseProviderKey("Jellyfin-SQLite")]
- public sealed class SqliteDatabaseProvider : IJellyfinDatabaseProvider
- {
- private const string BackupFolderName = "SQLiteBackups";
- private readonly IApplicationPaths _applicationPaths;
- private readonly ILogger<SqliteDatabaseProvider> _logger;
- /// <summary>
- /// Initializes a new instance of the <see cref="SqliteDatabaseProvider"/> class.
- /// </summary>
- /// <param name="applicationPaths">Service to construct the fallback when the old data path configuration is used.</param>
- /// <param name="logger">A logger.</param>
- public SqliteDatabaseProvider(IApplicationPaths applicationPaths, ILogger<SqliteDatabaseProvider> logger)
- {
- _applicationPaths = applicationPaths;
- _logger = logger;
- }
- /// <inheritdoc/>
- public IDbContextFactory<JellyfinDbContext>? DbContextFactory { get; set; }
- /// <inheritdoc/>
- public void Initialise(DbContextOptionsBuilder options, DatabaseConfigurationOptions databaseConfiguration)
- {
- var sqliteConnectionBuilder = new SqliteConnectionStringBuilder();
- sqliteConnectionBuilder.DataSource = Path.Combine(_applicationPaths.DataPath, "jellyfin.db");
- sqliteConnectionBuilder.Cache = Enum.Parse<SqliteCacheMode>(databaseConfiguration.CustomProviderOptions?.Options.FirstOrDefault(e => e.Key.Equals("cache", StringComparison.OrdinalIgnoreCase))?.Value ?? nameof(SqliteCacheMode.Default));
- sqliteConnectionBuilder.Pooling = (databaseConfiguration.CustomProviderOptions?.Options.FirstOrDefault(e => e.Key.Equals("pooling", StringComparison.OrdinalIgnoreCase))?.Value ?? bool.FalseString).Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase);
- options
- .UseSqlite(
- sqliteConnectionBuilder.ToString(),
- sqLiteOptions => sqLiteOptions.MigrationsAssembly(GetType().Assembly))
- // TODO: Remove when https://github.com/dotnet/efcore/pull/35873 is merged & released
- .ConfigureWarnings(warnings =>
- warnings.Ignore(RelationalEventId.NonTransactionalMigrationOperationWarning));
- }
- /// <inheritdoc/>
- public async Task RunScheduledOptimisation(CancellationToken cancellationToken)
- {
- var context = await DbContextFactory!.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
- await using (context.ConfigureAwait(false))
- {
- if (context.Database.IsSqlite())
- {
- await context.Database.ExecuteSqlRawAsync("PRAGMA optimize", cancellationToken).ConfigureAwait(false);
- await context.Database.ExecuteSqlRawAsync("VACUUM", cancellationToken).ConfigureAwait(false);
- _logger.LogInformation("jellyfin.db optimized successfully!");
- }
- else
- {
- _logger.LogInformation("This database doesn't support optimization");
- }
- }
- }
- /// <inheritdoc/>
- public void OnModelCreating(ModelBuilder modelBuilder)
- {
- modelBuilder.SetDefaultDateTimeKind(DateTimeKind.Utc);
- }
- /// <inheritdoc/>
- public async Task RunShutdownTask(CancellationToken cancellationToken)
- {
- if (DbContextFactory is null)
- {
- return;
- }
- // Run before disposing the application
- var context = await DbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
- await using (context.ConfigureAwait(false))
- {
- await context.Database.ExecuteSqlRawAsync("PRAGMA optimize", cancellationToken).ConfigureAwait(false);
- }
- SqliteConnection.ClearAllPools();
- }
- /// <inheritdoc/>
- public void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
- {
- configurationBuilder.Conventions.Add(_ => new DoNotUseReturningClauseConvention());
- }
- /// <inheritdoc />
- public Task<string> MigrationBackupFast(CancellationToken cancellationToken)
- {
- var key = DateTime.UtcNow.ToString("yyyyMMddhhmmss", CultureInfo.InvariantCulture);
- var path = Path.Combine(_applicationPaths.DataPath, "jellyfin.db");
- var backupFile = Path.Combine(_applicationPaths.DataPath, BackupFolderName);
- Directory.CreateDirectory(backupFile);
- backupFile = Path.Combine(backupFile, $"{key}_jellyfin.db");
- File.Copy(path, backupFile);
- return Task.FromResult(key);
- }
- /// <inheritdoc />
- public Task RestoreBackupFast(string key, CancellationToken cancellationToken)
- {
- // ensure there are absolutly no dangling Sqlite connections.
- SqliteConnection.ClearAllPools();
- var path = Path.Combine(_applicationPaths.DataPath, "jellyfin.db");
- var backupFile = Path.Combine(_applicationPaths.DataPath, BackupFolderName, $"{key}_jellyfin.db");
- if (!File.Exists(backupFile))
- {
- _logger.LogCritical("Tried to restore a backup that does not exist: {Key}", key);
- return Task.CompletedTask;
- }
- File.Copy(backupFile, path, true);
- return Task.CompletedTask;
- }
- /// <inheritdoc />
- public Task DeleteBackup(string key)
- {
- var backupFile = Path.Combine(_applicationPaths.DataPath, BackupFolderName, $"{key}_jellyfin.db");
- if (!File.Exists(backupFile))
- {
- _logger.LogCritical("Tried to delete a backup that does not exist: {Key}", key);
- return Task.CompletedTask;
- }
- File.Delete(backupFile);
- return Task.CompletedTask;
- }
- /// <inheritdoc/>
- public async Task PurgeDatabase(JellyfinDbContext dbContext, IEnumerable<string>? tableNames)
- {
- ArgumentNullException.ThrowIfNull(tableNames);
- var deleteQueries = new List<string>();
- foreach (var tableName in tableNames)
- {
- deleteQueries.Add($"DELETE FROM \"{tableName}\";");
- }
- var deleteAllQuery =
- $"""
- PRAGMA foreign_keys = OFF;
- {string.Join('\n', deleteQueries)}
- PRAGMA foreign_keys = ON;
- """;
- await dbContext.Database.ExecuteSqlRawAsync(deleteAllQuery).ConfigureAwait(false);
- }
- }
|