| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 | using System;using System.Collections.Generic;using System.Globalization;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 = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);        }        protected TransactionMode TransactionMode        {            get { return TransactionMode.Deferred; }        }        protected TransactionMode ReadTransactionMode        {            get { return TransactionMode.Deferred; }        }        internal static int ThreadSafeMode { get; set; }        static BaseSqliteRepository()        {            SQLite3.EnableSharedCache = false;            int rc = raw.sqlite3_config(raw.SQLITE_CONFIG_MEMSTATUS, 0);            //CheckOk(rc);            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);            rc = raw.sqlite3_enable_shared_cache(1);            ThreadSafeMode = raw.sqlite3_threadsafe();        }        private static bool _versionLogged;        private string _defaultWal;        protected ManagedConnection _connection;        protected virtual bool EnableSingleConnection        {            get { return true; }        }        protected ManagedConnection CreateConnection(bool isReadOnly = false)        {            if (_connection != null)            {                return _connection;            }            lock (WriteLock)            {                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");                    //connectionFlags = ConnectionFlags.ReadOnly;                    connectionFlags = ConnectionFlags.Create;                    connectionFlags |= ConnectionFlags.ReadWrite;                }                else                {                    //Logger.Info("Opening write connection");                    connectionFlags = ConnectionFlags.Create;                    connectionFlags |= ConnectionFlags.ReadWrite;                }                if (EnableSingleConnection)                {                    connectionFlags |= ConnectionFlags.PrivateCache;                }                else                {                    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();                    Logger.Info("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 (EnableTempStoreMemory)                {                    queries.Add("PRAGMA temp_store = memory");                }                //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))                {                    queries.Add("PRAGMA journal_mode=WAL");                    using (WriteLock.Write())                    {                        db.ExecuteAll(string.Join(";", queries.ToArray()));                    }                }                else*/                foreach (var query in queries)                {                    db.Execute(query);                }                _connection = new ManagedConnection(db, false);                return _connection;            }        }        public IStatement PrepareStatement(ManagedConnection connection, string sql)        {            return connection.PrepareStatement(sql);        }        public IStatement PrepareStatementSafe(ManagedConnection connection, string sql)        {            return 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);        }        public List<IStatement> PrepareAll(IDatabaseConnection connection, IEnumerable<string> sql)        {            return PrepareAllSafe(connection, sql);        }        public List<IStatement> PrepareAllSafe(IDatabaseConnection connection, IEnumerable<string> sql)        {            return sql.Select(connection.PrepareStatement).ToList();        }        protected void RunDefaultInitialization(ManagedConnection db)        {            var queries = new List<string>            {                "PRAGMA journal_mode=WAL",                "PRAGMA page_size=4096",                "PRAGMA synchronous=Normal"            };            if (EnableTempStoreMemory)            {                queries.AddRange(new List<string>                {                    "pragma default_temp_store = memory",                    "pragma temp_store = memory"                });            }            db.ExecuteAll(string.Join(";", queries.ToArray()));            Logger.Info("PRAGMA synchronous=" + db.Query("PRAGMA synchronous").SelectScalarString().First());        }        protected virtual bool EnableTempStoreMemory        {            get            {                return false;            }        }        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();        /// <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)            {                try                {                    lock (_disposeLock)                    {                        using (WriteLock.Write())                        {                            if (_connection != null)                            {                                using (_connection)                                {                                                                    }                                _connection = null;                            }                            CloseConnection();                        }                    }                }                catch (Exception ex)                {                    Logger.ErrorException("Error disposing database", ex);                }            }        }        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))            {                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 class DummyToken : IDisposable        {            public void Dispose()            {            }        }        public static IDisposable Read(this ReaderWriterLockSlim obj)        {            //if (BaseSqliteRepository.ThreadSafeMode > 0)            //{            //    return new DummyToken();            //}            return new WriteLockToken(obj);        }        public static IDisposable Write(this ReaderWriterLockSlim obj)        {            //if (BaseSqliteRepository.ThreadSafeMode > 0)            //{            //    return new DummyToken();            //}            return new WriteLockToken(obj);        }    }}
 |