< Summary - Jellyfin

Information
Class: Jellyfin.Database.Providers.Sqlite.SqliteDatabaseProvider
Assembly: Jellyfin.Database.Providers.Sqlite
File(s): /srv/git/jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/SqliteDatabaseProvider.cs
Line coverage
47%
Covered lines: 19
Uncovered lines: 21
Coverable lines: 40
Total lines: 174
Line coverage: 47.5%
Branch coverage
37%
Covered branches: 6
Total branches: 16
Branch coverage: 37.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
Initialise(...)50%1212100%
OnModelCreating(...)100%11100%
ConfigureConventions(...)100%11100%
MigrationBackupFast(...)100%210%
RestoreBackupFast(...)0%620%
DeleteBackup(...)0%620%

File(s)

/srv/git/jellyfin/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/SqliteDatabaseProvider.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Globalization;
 4using System.IO;
 5using System.Linq;
 6using System.Threading;
 7using System.Threading.Tasks;
 8using Jellyfin.Database.Implementations;
 9using Jellyfin.Database.Implementations.DbConfiguration;
 10using MediaBrowser.Common.Configuration;
 11using Microsoft.Data.Sqlite;
 12using Microsoft.EntityFrameworkCore;
 13using Microsoft.EntityFrameworkCore.Diagnostics;
 14using Microsoft.Extensions.Logging;
 15
 16namespace Jellyfin.Database.Providers.Sqlite;
 17
 18/// <summary>
 19/// Configures jellyfin to use an SQLite database.
 20/// </summary>
 21[JellyfinDatabaseProviderKey("Jellyfin-SQLite")]
 22public sealed class SqliteDatabaseProvider : IJellyfinDatabaseProvider
 23{
 24    private const string BackupFolderName = "SQLiteBackups";
 25    private readonly IApplicationPaths _applicationPaths;
 26    private readonly ILogger<SqliteDatabaseProvider> _logger;
 27
 28    /// <summary>
 29    /// Initializes a new instance of the <see cref="SqliteDatabaseProvider"/> class.
 30    /// </summary>
 31    /// <param name="applicationPaths">Service to construct the fallback when the old data path configuration is used.</
 32    /// <param name="logger">A logger.</param>
 33    public SqliteDatabaseProvider(IApplicationPaths applicationPaths, ILogger<SqliteDatabaseProvider> logger)
 34    {
 4335        _applicationPaths = applicationPaths;
 4336        _logger = logger;
 4337    }
 38
 39    /// <inheritdoc/>
 40    public IDbContextFactory<JellyfinDbContext>? DbContextFactory { get; set; }
 41
 42    /// <inheritdoc/>
 43    public void Initialise(DbContextOptionsBuilder options, DatabaseConfigurationOptions databaseConfiguration)
 44    {
 4245        var sqliteConnectionBuilder = new SqliteConnectionStringBuilder();
 4246        sqliteConnectionBuilder.DataSource = Path.Combine(_applicationPaths.DataPath, "jellyfin.db");
 4247        sqliteConnectionBuilder.Cache = Enum.Parse<SqliteCacheMode>(databaseConfiguration.CustomProviderOptions?.Options
 4248        sqliteConnectionBuilder.Pooling = (databaseConfiguration.CustomProviderOptions?.Options.FirstOrDefault(e => e.Ke
 49
 4250        options
 4251            .UseSqlite(
 4252                sqliteConnectionBuilder.ToString(),
 4253                sqLiteOptions => sqLiteOptions.MigrationsAssembly(GetType().Assembly))
 4254            // TODO: Remove when https://github.com/dotnet/efcore/pull/35873 is merged & released
 4255            .ConfigureWarnings(warnings =>
 4256                warnings.Ignore(RelationalEventId.NonTransactionalMigrationOperationWarning));
 4257    }
 58
 59    /// <inheritdoc/>
 60    public async Task RunScheduledOptimisation(CancellationToken cancellationToken)
 61    {
 62        var context = await DbContextFactory!.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
 63        await using (context.ConfigureAwait(false))
 64        {
 65            if (context.Database.IsSqlite())
 66            {
 67                await context.Database.ExecuteSqlRawAsync("PRAGMA optimize", cancellationToken).ConfigureAwait(false);
 68                await context.Database.ExecuteSqlRawAsync("VACUUM", cancellationToken).ConfigureAwait(false);
 69                _logger.LogInformation("jellyfin.db optimized successfully!");
 70            }
 71            else
 72            {
 73                _logger.LogInformation("This database doesn't support optimization");
 74            }
 75        }
 76    }
 77
 78    /// <inheritdoc/>
 79    public void OnModelCreating(ModelBuilder modelBuilder)
 80    {
 281        modelBuilder.SetDefaultDateTimeKind(DateTimeKind.Utc);
 282    }
 83
 84    /// <inheritdoc/>
 85    public async Task RunShutdownTask(CancellationToken cancellationToken)
 86    {
 87        if (DbContextFactory is null)
 88        {
 89            return;
 90        }
 91
 92        // Run before disposing the application
 93        var context = await DbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
 94        await using (context.ConfigureAwait(false))
 95        {
 96            await context.Database.ExecuteSqlRawAsync("PRAGMA optimize", cancellationToken).ConfigureAwait(false);
 97        }
 98
 99        SqliteConnection.ClearAllPools();
 100    }
 101
 102    /// <inheritdoc/>
 103    public void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
 104    {
 2105        configurationBuilder.Conventions.Add(_ => new DoNotUseReturningClauseConvention());
 2106    }
 107
 108    /// <inheritdoc />
 109    public Task<string> MigrationBackupFast(CancellationToken cancellationToken)
 110    {
 0111        var key = DateTime.UtcNow.ToString("yyyyMMddhhmmss", CultureInfo.InvariantCulture);
 0112        var path = Path.Combine(_applicationPaths.DataPath, "jellyfin.db");
 0113        var backupFile = Path.Combine(_applicationPaths.DataPath, BackupFolderName);
 0114        Directory.CreateDirectory(backupFile);
 115
 0116        backupFile = Path.Combine(backupFile, $"{key}_jellyfin.db");
 0117        File.Copy(path, backupFile);
 0118        return Task.FromResult(key);
 119    }
 120
 121    /// <inheritdoc />
 122    public Task RestoreBackupFast(string key, CancellationToken cancellationToken)
 123    {
 124        // ensure there are absolutly no dangling Sqlite connections.
 0125        SqliteConnection.ClearAllPools();
 0126        var path = Path.Combine(_applicationPaths.DataPath, "jellyfin.db");
 0127        var backupFile = Path.Combine(_applicationPaths.DataPath, BackupFolderName, $"{key}_jellyfin.db");
 128
 0129        if (!File.Exists(backupFile))
 130        {
 0131            _logger.LogCritical("Tried to restore a backup that does not exist: {Key}", key);
 0132            return Task.CompletedTask;
 133        }
 134
 0135        File.Copy(backupFile, path, true);
 0136        return Task.CompletedTask;
 137    }
 138
 139    /// <inheritdoc />
 140    public Task DeleteBackup(string key)
 141    {
 0142        var backupFile = Path.Combine(_applicationPaths.DataPath, BackupFolderName, $"{key}_jellyfin.db");
 143
 0144        if (!File.Exists(backupFile))
 145        {
 0146            _logger.LogCritical("Tried to delete a backup that does not exist: {Key}", key);
 0147            return Task.CompletedTask;
 148        }
 149
 0150        File.Delete(backupFile);
 0151        return Task.CompletedTask;
 152    }
 153
 154    /// <inheritdoc/>
 155    public async Task PurgeDatabase(JellyfinDbContext dbContext, IEnumerable<string>? tableNames)
 156    {
 157        ArgumentNullException.ThrowIfNull(tableNames);
 158
 159        var deleteQueries = new List<string>();
 160        foreach (var tableName in tableNames)
 161        {
 162            deleteQueries.Add($"DELETE FROM \"{tableName}\";");
 163        }
 164
 165        var deleteAllQuery =
 166        $"""
 167        PRAGMA foreign_keys = OFF;
 168        {string.Join('\n', deleteQueries)}
 169        PRAGMA foreign_keys = ON;
 170        """;
 171
 172        await dbContext.Database.ExecuteSqlRawAsync(deleteAllQuery).ConfigureAwait(false);
 173    }
 174}