using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Logging; using SQLitePCL.pretty; using System.Linq; using SQLitePCL; namespace Emby.Server.Implementations.Data { public abstract class BaseSqliteRepository : IDisposable { protected string DbFilePath { get; set; } protected ReaderWriterLockSlim WriteLock; protected ILogger Logger { get; private set; } protected BaseSqliteRepository(ILogger logger) { Logger = logger; WriteLock = AllowLockRecursion ? new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion) : new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); } protected virtual bool AllowLockRecursion { get { return false; } } static BaseSqliteRepository() { SQLite3.EnableSharedCache = false; int rc = raw.sqlite3_config(raw.SQLITE_CONFIG_MEMSTATUS, 0); //CheckOk(rc); } private static bool _versionLogged; private string _defaultWal; protected SQLiteDatabaseConnection CreateConnection(bool isReadOnly = false, Action onConnect = null) { if (!_versionLogged) { _versionLogged = true; Logger.Info("Sqlite version: " + SQLite3.Version); Logger.Info("Sqlite compiler options: " + string.Join(",", SQLite3.CompilerOptions.ToArray())); } ConnectionFlags connectionFlags; if (isReadOnly) { //Logger.Info("Opening read connection"); } else { //Logger.Info("Opening write connection"); } isReadOnly = false; if (isReadOnly) { connectionFlags = ConnectionFlags.ReadOnly; //connectionFlags = ConnectionFlags.Create; //connectionFlags |= ConnectionFlags.ReadWrite; } else { connectionFlags = ConnectionFlags.Create; connectionFlags |= ConnectionFlags.ReadWrite; } connectionFlags |= ConnectionFlags.SharedCached; connectionFlags |= ConnectionFlags.NoMutex; var db = SQLite3.Open(DbFilePath, connectionFlags, null); if (string.IsNullOrWhiteSpace(_defaultWal)) { _defaultWal = db.Query("PRAGMA journal_mode").SelectScalarString().First(); } var queries = new List { "PRAGMA default_temp_store=memory", "pragma temp_store = memory", "PRAGMA journal_mode=WAL" //"PRAGMA cache size=-10000" }; //var cacheSize = CacheSize; //if (cacheSize.HasValue) //{ //} ////foreach (var query in queries) ////{ //// db.Execute(query); ////} //Logger.Info("synchronous: " + db.Query("PRAGMA synchronous").SelectScalarString().First()); //Logger.Info("temp_store: " + db.Query("PRAGMA temp_store").SelectScalarString().First()); //if (!string.Equals(_defaultWal, "wal", StringComparison.OrdinalIgnoreCase) || onConnect != null) { using (WriteLock.Write()) { db.ExecuteAll(string.Join(";", queries.ToArray())); if (onConnect != null) { onConnect(db); } } } return db; } protected virtual int? CacheSize { get { return null; } } internal static void CheckOk(int rc) { string msg = ""; if (raw.SQLITE_OK != rc) { throw CreateException((ErrorCode)rc, msg); } } internal static Exception CreateException(ErrorCode rc, string msg) { var exp = new Exception(msg); return exp; } private bool _disposed; protected void CheckDisposed() { if (_disposed) { throw new ObjectDisposedException(GetType().Name + " has been disposed and cannot be accessed."); } } public void Dispose() { _disposed = true; Dispose(true); GC.SuppressFinalize(this); } private readonly object _disposeLock = new object(); /// /// 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 (dispose) { try { lock (_disposeLock) { using (WriteLock.Write()) { CloseConnection(); } } } catch (Exception ex) { Logger.ErrorException("Error disposing database", ex); } } } protected virtual void CloseConnection() { } protected List GetColumnNames(IDatabaseConnection connection, string table) { var list = new List(); 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 existingColumnNames) { if (existingColumnNames.Contains(columnName, StringComparer.OrdinalIgnoreCase)) { 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 (_sync != null) { _sync.ExitReadLock(); _sync = null; } } } private sealed class WriteLockToken : IDisposable { private ReaderWriterLockSlim _sync; public WriteLockToken(ReaderWriterLockSlim sync) { _sync = sync; sync.EnterWriteLock(); } public void Dispose() { if (_sync != null) { _sync.ExitWriteLock(); _sync = null; } } } public static IDisposable Read(this ReaderWriterLockSlim obj) { return new ReadLockToken(obj); } public static IDisposable Write(this ReaderWriterLockSlim obj) { return new WriteLockToken(obj); } } }