diff options
Diffstat (limited to 'Emby.Server.Implementations/Data/BaseSqliteRepository.cs')
| -rw-r--r-- | Emby.Server.Implementations/Data/BaseSqliteRepository.cs | 411 |
1 files changed, 142 insertions, 269 deletions
diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index fba81306b..73c31f49d 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -1,187 +1,166 @@ +#nullable disable + +#pragma warning disable CS1591 + using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Threading; using Microsoft.Extensions.Logging; -using SQLitePCL; using SQLitePCL.pretty; namespace Emby.Server.Implementations.Data { public abstract class BaseSqliteRepository : IDisposable { - protected string DbFilePath { get; set; } - protected ReaderWriterLockSlim WriteLock; + private bool _disposed = false; - protected ILogger Logger { get; private set; } - - protected BaseSqliteRepository(ILogger logger) + /// <summary> + /// Initializes a new instance of the <see cref="BaseSqliteRepository"/> class. + /// </summary> + /// <param name="logger">The logger.</param> + protected BaseSqliteRepository(ILogger<BaseSqliteRepository> logger) { Logger = logger; - - WriteLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); } - protected TransactionMode TransactionMode => TransactionMode.Deferred; + /// <summary> + /// Gets or sets the path to the DB file. + /// </summary> + /// <value>Path to the DB file.</value> + protected string DbFilePath { get; set; } - protected TransactionMode ReadTransactionMode => TransactionMode.Deferred; + /// <summary> + /// Gets the logger. + /// </summary> + /// <value>The logger.</value> + protected ILogger<BaseSqliteRepository> Logger { get; } - internal static int ThreadSafeMode { get; set; } + /// <summary> + /// Gets the default connection flags. + /// </summary> + /// <value>The default connection flags.</value> + protected virtual ConnectionFlags DefaultConnectionFlags => ConnectionFlags.NoMutex; - static BaseSqliteRepository() - { - SQLite3.EnableSharedCache = false; + /// <summary> + /// Gets the transaction mode. + /// </summary> + /// <value>The transaction mode.</value>> + protected TransactionMode TransactionMode => TransactionMode.Deferred; - int rc = raw.sqlite3_config(raw.SQLITE_CONFIG_MEMSTATUS, 0); - //CheckOk(rc); + /// <summary> + /// Gets the transaction mode for read-only operations. + /// </summary> + /// <value>The transaction mode.</value> + protected TransactionMode ReadTransactionMode => TransactionMode.Deferred; - rc = raw.sqlite3_config(raw.SQLITE_CONFIG_MULTITHREAD, 1); - //rc = raw.sqlite3_config(raw.SQLITE_CONFIG_SINGLETHREAD, 1); - //rc = raw.sqlite3_config(raw.SQLITE_CONFIG_SERIALIZED, 1); - //CheckOk(rc); + /// <summary> + /// Gets the cache size. + /// </summary> + /// <value>The cache size or null.</value> + protected virtual int? CacheSize => null; - rc = raw.sqlite3_enable_shared_cache(1); + /// <summary> + /// Gets the journal mode. <see href="https://www.sqlite.org/pragma.html#pragma_journal_mode" />. + /// </summary> + /// <value>The journal mode.</value> + protected virtual string JournalMode => "TRUNCATE"; - ThreadSafeMode = raw.sqlite3_threadsafe(); - } + /// <summary> + /// Gets the page size. + /// </summary> + /// <value>The page size or null.</value> + protected virtual int? PageSize => null; + + /// <summary> + /// Gets the temp store mode. + /// </summary> + /// <value>The temp store mode.</value> + /// <see cref="TempStoreMode"/> + protected virtual TempStoreMode TempStore => TempStoreMode.Default; - private static bool _versionLogged; + /// <summary> + /// Gets the synchronous mode. + /// </summary> + /// <value>The synchronous mode or null.</value> + /// <see cref="SynchronousMode"/> + protected virtual SynchronousMode? Synchronous => null; - private string _defaultWal; - protected ManagedConnection _connection; + /// <summary> + /// Gets or sets the write lock. + /// </summary> + /// <value>The write lock.</value> + protected SemaphoreSlim WriteLock { get; set; } = new SemaphoreSlim(1, 1); - protected virtual bool EnableSingleConnection => true; + /// <summary> + /// Gets or sets the write connection. + /// </summary> + /// <value>The write connection.</value> + protected SQLiteDatabaseConnection WriteConnection { get; set; } - protected ManagedConnection CreateConnection(bool isReadOnly = false) + protected ManagedConnection GetConnection(bool readOnly = false) { - if (_connection != null) + WriteLock.Wait(); + if (WriteConnection != null) { - return _connection; + return new ManagedConnection(WriteConnection, WriteLock); } - lock (WriteLock) - { - if (!_versionLogged) - { - _versionLogged = true; - Logger.LogInformation("Sqlite version: " + SQLite3.Version); - Logger.LogInformation("Sqlite compiler options: " + string.Join(",", SQLite3.CompilerOptions.ToArray())); - } - - ConnectionFlags connectionFlags; - - if (isReadOnly) - { - //Logger.LogInformation("Opening read connection"); - //connectionFlags = ConnectionFlags.ReadOnly; - connectionFlags = ConnectionFlags.Create; - connectionFlags |= ConnectionFlags.ReadWrite; - } - else - { - //Logger.LogInformation("Opening write connection"); - connectionFlags = ConnectionFlags.Create; - connectionFlags |= ConnectionFlags.ReadWrite; - } - - if (EnableSingleConnection) - { - connectionFlags |= ConnectionFlags.PrivateCache; - } - else - { - connectionFlags |= ConnectionFlags.SharedCached; - } + WriteConnection = SQLite3.Open( + DbFilePath, + DefaultConnectionFlags | ConnectionFlags.Create | ConnectionFlags.ReadWrite, + null); - connectionFlags |= ConnectionFlags.NoMutex; - - var db = SQLite3.Open(DbFilePath, connectionFlags, null); - - try - { - if (string.IsNullOrWhiteSpace(_defaultWal)) - { - _defaultWal = db.Query("PRAGMA journal_mode").SelectScalarString().First(); - - Logger.LogInformation("Default journal_mode for {0} is {1}", DbFilePath, _defaultWal); - } - - var queries = new List<string> - { - //"PRAGMA cache size=-10000" - //"PRAGMA read_uncommitted = true", - "PRAGMA synchronous=Normal" - }; - - if (CacheSize.HasValue) - { - queries.Add("PRAGMA cache_size=" + CacheSize.Value.ToString(CultureInfo.InvariantCulture)); - } + if (CacheSize.HasValue) + { + WriteConnection.Execute("PRAGMA cache_size=" + CacheSize.Value); + } - if (EnableTempStoreMemory) - { - queries.Add("PRAGMA temp_store = memory"); - } - else - { - queries.Add("PRAGMA temp_store = file"); - } + if (!string.IsNullOrWhiteSpace(JournalMode)) + { + WriteConnection.Execute("PRAGMA journal_mode=" + JournalMode); + } - foreach (var query in queries) - { - db.Execute(query); - } - } - catch - { - using (db) - { + if (Synchronous.HasValue) + { + WriteConnection.Execute("PRAGMA synchronous=" + (int)Synchronous.Value); + } - } + if (PageSize.HasValue) + { + WriteConnection.Execute("PRAGMA page_size=" + PageSize.Value); + } - throw; - } + WriteConnection.Execute("PRAGMA temp_store=" + (int)TempStore); - _connection = new ManagedConnection(db, false); + // Configuration and pragmas can affect VACUUM so it needs to be last. + WriteConnection.Execute("VACUUM"); - return _connection; - } + return new ManagedConnection(WriteConnection, WriteLock); } public IStatement PrepareStatement(ManagedConnection connection, string sql) - { - return connection.PrepareStatement(sql); - } - - public IStatement PrepareStatementSafe(ManagedConnection connection, string sql) - { - return connection.PrepareStatement(sql); - } + => connection.PrepareStatement(sql); public IStatement PrepareStatement(IDatabaseConnection connection, string sql) - { - return connection.PrepareStatement(sql); - } - - public IStatement PrepareStatementSafe(IDatabaseConnection connection, string sql) - { - return connection.PrepareStatement(sql); - } + => connection.PrepareStatement(sql); - public List<IStatement> PrepareAll(IDatabaseConnection connection, IEnumerable<string> sql) + public IStatement[] PrepareAll(IDatabaseConnection connection, IReadOnlyList<string> sql) { - return PrepareAllSafe(connection, sql); - } + int len = sql.Count; + IStatement[] statements = new IStatement[len]; + for (int i = 0; i < len; i++) + { + statements[i] = connection.PrepareStatement(sql[i]); + } - public List<IStatement> PrepareAllSafe(IDatabaseConnection connection, IEnumerable<string> sql) - { - return sql.Select(connection.PrepareStatement).ToList(); + return statements; } protected bool TableExists(ManagedConnection connection, string name) { - return connection.RunInTransaction(db => + return connection.RunInTransaction( + db => { using (var statement = PrepareStatement(db, "select DISTINCT tbl_name from sqlite_master")) { @@ -195,44 +174,34 @@ namespace Emby.Server.Implementations.Data } return false; - }, ReadTransactionMode); } - protected void RunDefaultInitialization(ManagedConnection db) + protected List<string> GetColumnNames(IDatabaseConnection connection, string table) { - var queries = new List<string> - { - "PRAGMA journal_mode=WAL", - "PRAGMA page_size=4096", - "PRAGMA synchronous=Normal" - }; + var columnNames = new List<string>(); - if (EnableTempStoreMemory) - { - queries.AddRange(new List<string> - { - "pragma default_temp_store = memory", - "pragma temp_store = memory" - }); - } - else + foreach (var row in connection.Query("PRAGMA table_info(" + table + ")")) { - queries.AddRange(new List<string> + if (row.TryGetString(1, out var columnName)) { - "pragma temp_store = file" - }); + columnNames.Add(columnName); + } } - db.ExecuteAll(string.Join(";", queries)); - Logger.LogInformation("PRAGMA synchronous=" + db.Query("PRAGMA synchronous").SelectScalarString().First()); + return columnNames; } - protected virtual bool EnableTempStoreMemory => false; + protected void AddColumn(IDatabaseConnection connection, string table, string columnName, string type, List<string> existingColumnNames) + { + if (existingColumnNames.Contains(columnName, StringComparer.OrdinalIgnoreCase)) + { + return; + } - protected virtual int? CacheSize => null; + connection.Execute("alter table " + table + " add column " + columnName + " " + type + " NULL"); + } - private bool _disposed; protected void CheckDisposed() { if (_disposed) @@ -241,139 +210,43 @@ namespace Emby.Server.Implementations.Data } } + /// <inheritdoc /> public void Dispose() { - _disposed = true; Dispose(true); + GC.SuppressFinalize(this); } - private readonly object _disposeLock = new object(); - /// <summary> /// Releases unmanaged and - optionally - managed resources. /// </summary> /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> protected virtual void Dispose(bool dispose) { - if (dispose) - { - DisposeConnection(); - } - } - - private void DisposeConnection() - { - try - { - lock (_disposeLock) - { - using (WriteLock.Write()) - { - if (_connection != null) - { - using (_connection) - { - _connection.Close(); - } - _connection = null; - } - - CloseConnection(); - } - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Error disposing database"); - } - } - - protected virtual void CloseConnection() - { - - } - - protected List<string> GetColumnNames(IDatabaseConnection connection, string table) - { - var list = new List<string>(); - - foreach (var row in connection.Query("PRAGMA table_info(" + table + ")")) - { - if (row[1].SQLiteType != SQLiteType.Null) - { - var name = row[1].ToString(); - - list.Add(name); - } - } - - return list; - } - - protected void AddColumn(IDatabaseConnection connection, string table, string columnName, string type, List<string> existingColumnNames) - { - if (existingColumnNames.Contains(columnName, StringComparer.OrdinalIgnoreCase)) + if (_disposed) { return; } - connection.Execute("alter table " + table + " add column " + columnName + " " + type + " NULL"); - } - } - - public static class ReaderWriterLockSlimExtensions - { - private sealed class ReadLockToken : IDisposable - { - private ReaderWriterLockSlim _sync; - public ReadLockToken(ReaderWriterLockSlim sync) - { - _sync = sync; - sync.EnterReadLock(); - } - public void Dispose() + if (dispose) { - if (_sync != null) + WriteLock.Wait(); + try { - _sync.ExitReadLock(); - _sync = null; + WriteConnection?.Dispose(); } - } - } - private sealed class WriteLockToken : IDisposable - { - private ReaderWriterLockSlim _sync; - public WriteLockToken(ReaderWriterLockSlim sync) - { - _sync = sync; - sync.EnterWriteLock(); - } - public void Dispose() - { - if (_sync != null) + finally { - _sync.ExitWriteLock(); - _sync = null; + WriteLock.Release(); } + + WriteLock.Dispose(); } - } - public static IDisposable Read(this ReaderWriterLockSlim obj) - { - //if (BaseSqliteRepository.ThreadSafeMode > 0) - //{ - // return new DummyToken(); - //} - return new WriteLockToken(obj); - } + WriteConnection = null; + WriteLock = null; - public static IDisposable Write(this ReaderWriterLockSlim obj) - { - //if (BaseSqliteRepository.ThreadSafeMode > 0) - //{ - // return new DummyToken(); - //} - return new WriteLockToken(obj); + _disposed = true; } } } |
