#nullable disable #pragma warning disable CS1591 using System; using System.Collections.Generic; using System.Threading; using Jellyfin.Extensions; using Microsoft.Extensions.Logging; using SQLitePCL.pretty; namespace Emby.Server.Implementations.Data { public abstract class BaseSqliteRepository : IDisposable { private bool _disposed = false; /// /// Initializes a new instance of the class. /// /// The logger. protected BaseSqliteRepository(ILogger logger) { Logger = logger; } /// /// Gets or sets the path to the DB file. /// /// Path to the DB file. protected string DbFilePath { get; set; } /// /// Gets the logger. /// /// The logger. protected ILogger Logger { get; } /// /// Gets the default connection flags. /// /// The default connection flags. protected virtual ConnectionFlags DefaultConnectionFlags => ConnectionFlags.NoMutex; /// /// Gets the transaction mode. /// /// The transaction mode.> protected TransactionMode TransactionMode => TransactionMode.Deferred; /// /// Gets the transaction mode for read-only operations. /// /// The transaction mode. protected TransactionMode ReadTransactionMode => TransactionMode.Deferred; /// /// Gets the cache size. /// /// The cache size or null. protected virtual int? CacheSize => null; /// /// Gets the locking mode. . /// protected virtual string LockingMode => "EXCLUSIVE"; /// /// Gets the journal mode. . /// /// The journal mode. protected virtual string JournalMode => "WAL"; /// /// Gets the journal size limit. . /// /// The journal size limit. protected virtual int? JournalSizeLimit => 0; /// /// Gets the page size. /// /// The page size or null. protected virtual int? PageSize => null; /// /// Gets the temp store mode. /// /// The temp store mode. /// protected virtual TempStoreMode TempStore => TempStoreMode.Default; /// /// Gets the synchronous mode. /// /// The synchronous mode or null. /// protected virtual SynchronousMode? Synchronous => SynchronousMode.Normal; /// /// Gets or sets the write lock. /// /// The write lock. protected SemaphoreSlim WriteLock { get; set; } = new SemaphoreSlim(1, 1); /// /// Gets or sets the write connection. /// /// The write connection. protected SQLiteDatabaseConnection WriteConnection { get; set; } protected ManagedConnection GetConnection(bool readOnly = false) { WriteLock.Wait(); if (WriteConnection is not null) { return new ManagedConnection(WriteConnection, WriteLock); } WriteConnection = SQLite3.Open( DbFilePath, DefaultConnectionFlags | ConnectionFlags.Create | ConnectionFlags.ReadWrite, null); if (CacheSize.HasValue) { WriteConnection.Execute("PRAGMA cache_size=" + CacheSize.Value); } if (!string.IsNullOrWhiteSpace(LockingMode)) { WriteConnection.Execute("PRAGMA locking_mode=" + LockingMode); } if (!string.IsNullOrWhiteSpace(JournalMode)) { WriteConnection.Execute("PRAGMA journal_mode=" + JournalMode); } if (JournalSizeLimit.HasValue) { WriteConnection.Execute("PRAGMA journal_size_limit=" + JournalSizeLimit.Value); } if (Synchronous.HasValue) { WriteConnection.Execute("PRAGMA synchronous=" + (int)Synchronous.Value); } if (PageSize.HasValue) { WriteConnection.Execute("PRAGMA page_size=" + PageSize.Value); } WriteConnection.Execute("PRAGMA temp_store=" + (int)TempStore); // Configuration and pragmas can affect VACUUM so it needs to be last. WriteConnection.Execute("VACUUM"); return new ManagedConnection(WriteConnection, WriteLock); } public IStatement PrepareStatement(ManagedConnection connection, string sql) => connection.PrepareStatement(sql); public IStatement PrepareStatement(IDatabaseConnection connection, string sql) => connection.PrepareStatement(sql); public IStatement[] PrepareAll(IDatabaseConnection connection, IReadOnlyList sql) { int len = sql.Count; IStatement[] statements = new IStatement[len]; for (int i = 0; i < len; i++) { statements[i] = connection.PrepareStatement(sql[i]); } return statements; } protected bool TableExists(ManagedConnection connection, string name) { return connection.RunInTransaction( db => { using (var statement = PrepareStatement(db, "select DISTINCT tbl_name from sqlite_master")) { foreach (var row in statement.ExecuteQuery()) { if (string.Equals(name, row.GetString(0), StringComparison.OrdinalIgnoreCase)) { return true; } } } return false; }, ReadTransactionMode); } protected List GetColumnNames(IDatabaseConnection connection, string table) { var columnNames = new List(); foreach (var row in connection.Query("PRAGMA table_info(" + table + ")")) { if (row.TryGetString(1, out var columnName)) { columnNames.Add(columnName); } } return columnNames; } protected void AddColumn(IDatabaseConnection connection, string table, string columnName, string type, List existingColumnNames) { if (existingColumnNames.Contains(columnName, StringComparison.OrdinalIgnoreCase)) { return; } connection.Execute("alter table " + table + " add column " + columnName + " " + type + " NULL"); } protected void CheckDisposed() { if (_disposed) { throw new ObjectDisposedException(GetType().Name, "Object has been disposed and cannot be accessed."); } } /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool dispose) { if (_disposed) { return; } if (dispose) { WriteLock.Wait(); try { WriteConnection?.Dispose(); } finally { WriteLock.Release(); } WriteLock.Dispose(); } WriteConnection = null; WriteLock = null; _disposed = true; } } }