예제 #1
0
        /// <summary>
        /// SQLite bulk insert uses EF Extensions BulkIndex.
        /// This would also work for SQLServer.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="collection"></param>
        /// <param name="itemsToSave"></param>
        /// <returns></returns>
        public async Task <bool> BulkInsert <T>(BaseDBModel db, DbSet <T> collection, List <T> itemsToSave) where T : class
        {
            if (BaseDBModel.ReadOnly)
            {
                Logging.LogVerbose("Read-only mode - no data will be inserted.");
                return(true);
            }

            bool success = false;

            try
            {
                var bulkConfig = new BulkConfig {
                    SetOutputIdentity = true, BatchSize = 500
                };

                await db.BulkInsertAsync(itemsToSave, bulkConfig);

                success = true;
            }
            catch (Exception ex)
            {
                Logging.LogError($"Exception during bulk insert: {ex}");
            }

            return(success);
        }
예제 #2
0
        /// <summary>
        /// SQLite bulk update uses EF Extensions BulkUpdate.
        /// This would also work for SQLServer.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="collection"></param>
        /// <param name="itemsToSave"></param>
        /// <returns></returns>
        public async Task <bool> BulkUpdate <T>(BaseDBModel db, DbSet <T> collection, List <T> itemsToSave) where T : class
        {
            // TODO make this method protected and then move this check to the base class
            if (BaseDBModel.ReadOnly)
            {
                Logging.LogVerbose("Read-only mode - no data will be updated.");
                return(true);
            }

            bool success = false;

            try
            {
                //collection.UpdateRange(itemsToSave);
                //await db.SaveChangesAsync();

                await db.BulkUpdateAsync(itemsToSave);

                success = true;
            }
            catch (Exception ex)
            {
                Logging.LogError($"Exception during bulk update: {ex}");
            }

            return(success);
        }
예제 #3
0
        /// <summary>
        /// SQLite bulk delete uses EF Extensions BulkDelete.
        /// This would also work for SQLServer.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="collection"></param>
        /// <param name="itemsToDelete"></param>
        /// <returns></returns>
        public async Task <bool> BulkDelete <T>(BaseDBModel db, DbSet <T> collection, List <T> itemsToDelete) where T : class
        {
            if (BaseDBModel.ReadOnly)
            {
                Logging.LogVerbose("Read-only mode - no data will be deleted.");
                return(true);
            }

            bool success = false;

            try
            {
                //collection.RemoveRange(itemsToDelete);
                //await db.SaveChangesAsync();

                await db.BulkDeleteAsync(itemsToDelete);

                success = true;
            }
            catch (Exception ex)
            {
                Logging.LogError($"Exception during bulk delete: {ex}");
            }

            return(success);
        }
예제 #4
0
        /// <summary>
        /// Enable SQLite performance improvements
        /// </summary>
        /// <param name="db"></param>
        private void IncreasePerformance(BaseDBModel db)
        {
            // Increase the timeout from the default (which I think is 30s)
            // To help concurrency.
            db.Database.SetCommandTimeout(60);
            // Enable journal mode - this will also improve
            // concurrent acces
            ExecutePragma(db, "PRAGMA journal_mode=WAL;");
            // Turn off Synchronous mode. This means that writes aren't
            // sync'd to disk every single time.
            ExecutePragma(db, "PRAGMA synchronous=OFF;");
            // Increate the cache page size TODO: check this value
            ExecutePragma(db, "PRAGMA cache_size=10000;");
            // Use a shared cache - good for multi-threaded access
            ExecutePragma(db, "PRAGMA cache=shared;");
            // Allow reading from the cache. Means we might get stale
            // data, but in most cases that's fine and concurrency will
            // be improved.
            ExecutePragma(db, "PRAGMA read_uncommitted=true;");
            // Store temporary tables in memory
            ExecutePragma(db, "PRAGMA temp_store=MEMORY;");

            // Massive hack....
            Logging.LogTrace("Deleting corrupt ImageMetaData entries");
            db.Database.ExecuteSqlRaw("delete from imagemetadata where Lastupdated = 1998;");

            Logging.Log("Running Sqlite DB optimisation...");
            db.Database.ExecuteSqlRaw("VACUUM;");
            Logging.Log("DB optimisation complete.");

            RebuildFreeText(db);
        }
예제 #5
0
 public static string GetTableWith(BaseDBModel dBModel)
 {
     if (string.IsNullOrEmpty(dBModel.GetTableWith()))
     {
         return(string.Empty);
     }
     return($" with({dBModel.GetTableWith()}) ");
 }
예제 #6
0
 public JoinQueryInfo(BaseDBModel dBModel, string joinStr, int joinTableCount, Dictionary <string, string> map, string where, DynamicParameters parameters)
 {
     JoinStr        = joinStr;
     JoinTableCount = joinTableCount;
     Map            = map;
     Where          = where;
     Param          = parameters;
     DBModel        = dBModel;
 }
예제 #7
0
        /// <summary>
        /// Postgres bulk delete uses EF Extensions BulkDelete.
        /// This would also work for SQLServer.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="collection"></param>
        /// <param name="itemsToDelete"></param>
        /// <returns></returns>
        public async Task <bool> BulkDelete <T>(BaseDBModel db, DbSet <T> collection, List <T> itemsToDelete) where T : class
        {
            // TODO make this method protected and then move this check to the base class
            if (BaseDBModel.ReadOnly)
            {
                Logging.LogVerbose("Read-only mode - no data will be deleted.");
                return(true);
            }

            collection.RemoveRange(itemsToDelete);

            int ret = await db.SaveChangesAsync("BulkSave");

            return(ret == itemsToDelete.Count);
        }
예제 #8
0
 /// <summary>
 /// SQLite pragma execution.
 /// </summary>
 /// <param name="db"></param>
 /// <param name="pragmaCommand"></param>
 private void ExecutePragma(BaseDBModel db, string pragmaCommand)
 {
     try
     {
         var connection = db.Database.GetDbConnection();
         connection.Open();
         using var command   = connection.CreateCommand();
         command.CommandText = pragmaCommand;
         command.ExecuteNonQuery();
     }
     catch (Exception ex)
     {
         Logging.LogWarning($"Unable to execute pragma command {pragmaCommand}: {ex.Message}");
     }
 }
예제 #9
0
        private void RebuildFreeText(BaseDBModel db)
        {
            const string delete       = @"DELETE from FTSKeywords; DELETE from FTSImages; DELETE from FTSNames;";
            const string insertTags   = @"INSERT INTO FTSKeywords (TagId, Keyword) SELECT t.TagId, t.Keyword FROM Tags t;";
            const string insertPeople = @"INSERT INTO FTSNames (PersonID, Name) SELECT PersonId, Name FROM people p where p.State = 1;";
            const string insertImages = @"INSERT INTO FTSImages ( ImageId, Caption, Description, Copyright, Credit ) 
                                SELECT i.ImageId, i.Caption, i.Description, i.CopyRight, i.Credit FROM imagemetadata i 
                                WHERE (coalesce(i.Caption, '') <> '' OR coalesce(i.Description, '') <> '' 
                                     OR coalesce(i.Copyright, '') <> '' OR coalesce(i.Credit, '') <> '');";

            string sql = $"{delete} {insertTags} {insertPeople} {insertImages}";

            Logging.LogVerbose("Rebuilding Free Text Index.");
            db.Database.ExecuteSqlRaw(sql);
            Logging.Log("Full-text search index rebuilt.");
        }
예제 #10
0
        public void Init(BaseDBModel db)
        {
            try
            {
                Logging.Log("Running MySql DB migrations...");

                // TODO MySQL doesn't support migrations?! - remove this big hammer
                db.Database.EnsureDeleted();
                db.Database.EnsureCreated();
            }
            catch (Exception ex)
            {
                Logging.LogWarning("Migrations failed - creating DB. Exception: {0}", ex.Message);
                db.Database.EnsureCreated();
            }
        }
예제 #11
0
        /// <summary>
        /// Postgres bulk insert uses EF Extensions BulkIndex.
        /// This would also work for SQLServer.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="collection"></param>
        /// <param name="itemsToSave"></param>
        /// <returns></returns>
        public async Task <bool> BulkInsert <T>(BaseDBModel db, DbSet <T> collection, List <T> itemsToSave) where T : class
        {
            // TODO make this method protected and then move this check to the base class
            if (BaseDBModel.ReadOnly)
            {
                Logging.LogVerbose("Read-only mode - no data will be updated.");
                return(true);
            }

            collection.AddRange(itemsToSave);

            // TODO: Set output identity here.
            int ret = await db.SaveChangesAsync("BulkSave");

            return(ret == itemsToSave.Count);
        }
예제 #12
0
        private static string GetSql(BaseDBModel obj, ICollection <string> keys, string addPre, string tableAlias = "")
        {
            if (!string.IsNullOrEmpty(tableAlias))
            {
                tableAlias += ".";
            }
            StringBuilder sql = new StringBuilder();

            foreach (var k in keys)
            {
                if (sql.Length > 0)
                {
                    sql.Append($" {addPre} ");
                }
                sql.Append($"{tableAlias}{obj.GetDBModel_SqlProvider().GetProviderOption().OpenQuote}{k}{obj.GetDBModel_SqlProvider().GetProviderOption().CloseQuote}=@{k}");
            }
            return(sql.ToString());
        }
예제 #13
0
        /// <summary>
        /// Basic implementation that inserts or updates based on whether the key/ID is zero or not.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="db"></param>
        /// <param name="collection"></param>
        /// <param name="itemsToSave"></param>
        /// <param name="getKey"></param>
        /// <returns></returns>
        public async Task <bool> BulkInsertOrUpdate <T>(BaseDBModel db, DbSet <T> collection, List <T> itemsToSave, Func <T, bool> isNew) where T : class
        {
            var result = false;

            itemsToSave.ForEach(x => { if (isNew(x))
                                       {
                                           collection.Add(x);
                                       }
                                       else
                                       {
                                           collection.Update(x);
                                       } });

            if (await db.SaveChangesAsync("BulkInsertOrUpdate") > 0)
            {
                result = true;
            }

            return(result);
        }
예제 #14
0
        /// <summary>
        /// SQLite specific initialisation. Run the migrations, and
        /// always run a VACUUM to optimise the DB at startup.
        /// </summary>
        /// <param name="db"></param>
        public void Init(BaseDBModel db)
        {
            try
            {
                Logging.Log("Running Sqlite DB migrations...");
                db.Database.Migrate();
            }
            catch (Exception ex)
            {
                Logging.LogWarning($"Migrations failed with exception: {ex}");

                if (ex.InnerException != null)
                {
                    Logging.LogWarning($"InnerException: {ex.InnerException}");
                }

                Logging.Log($"Creating DB.");
                db.Database.EnsureCreated();
            }

            IncreasePerformance(db);
        }
예제 #15
0
        /// <summary>
        /// Postgres specific initialisation. Run the migrations, and
        /// always run a VACUUM to optimise the DB at startup.
        /// </summary>
        /// <param name="db"></param>
        public void Init(BaseDBModel db)
        {
            try
            {
                Logging.Log("Running Postgres DB migrations...");
                db.Database.Migrate();
            }
            catch (Exception ex)
            {
                Logging.LogWarning("Migrations failed - creating DB. Exception: {0}", ex.Message);

                try
                {
                    db.Database.EnsureCreated();
                }
                catch (Exception ex2)
                {
                    Logging.LogError("Database creation failed. Exception: {0}", ex2.Message);
                }
            }

            IncreasePerformance(db);
        }
예제 #16
0
 /// <summary>
 /// Enable Postgres performance improvements
 /// </summary>
 /// <param name="db"></param>
 private void IncreasePerformance(BaseDBModel db)
 {
     // Nothing yet for postgres
 }
예제 #17
0
 /// <summary>
 /// Use the EF BulkExtensions to implement this.
 /// </summary>
 /// <typeparam name="T"></typeparam>
 /// <param name="db"></param>
 /// <param name="collection"></param>
 /// <param name="itemsToSave"></param>
 /// <param name="getKey"></param>
 /// <returns></returns>
 public Task <bool> BulkInsertOrUpdate <T>(BaseDBModel db, DbSet <T> collection, List <T> itemsToSave, Func <T, bool> getKey) where T : class
 {
     throw new NotImplementedException();
 }
예제 #18
0
        /// <summary>
        /// Process the startup args and initialise the logging.
        /// </summary>
        /// <param name="o"></param>
        /// <param name="args"></param>
        private static void Startup(DamselflyOptions o, string[] args)
        {
            Logging.Verbose = o.Verbose;
            Logging.Trace   = o.Trace;

            if (Directory.Exists(o.SourceDirectory))
            {
                if (!Directory.Exists(o.ConfigPath))
                {
                    Directory.CreateDirectory(o.ConfigPath);
                }

                Logging.LogFolder = Path.Combine(o.ConfigPath, "logs");

                Log.Logger = Logging.InitLogs();

                if (o.ReadOnly)
                {
                    o.NoEnableIndexing     = true;
                    o.NoGenerateThumbnails = true;
                }

                // TODO: Do away with static members here. We should pass this
                // through to the config service and pick them up via DI
                IndexingService.EnableIndexing = !o.NoEnableIndexing;
                IndexingService.RootFolder     = o.SourceDirectory;
                ThumbnailService.PicturesRoot  = o.SourceDirectory;
                ThumbnailService.Synology      = o.Synology;
                ThumbnailService.SetThumbnailRoot(o.ThumbPath);
                ThumbnailService.EnableThumbnailGeneration = !o.NoGenerateThumbnails;

                var tieredPGO = System.Environment.GetEnvironmentVariable("DOTNET_TieredPGO") == "1";

                Logging.Log("Startup State:");
                Logging.Log($" Damselfly Ver: {Assembly.GetExecutingAssembly().GetName().Version}");
                Logging.Log($" CLR Ver: {Environment.Version}");
                Logging.Log($" OS: {Environment.OSVersion}");
                Logging.Log($" CPU Arch: {RuntimeInformation.ProcessArchitecture}");
                Logging.Log($" Processor Count: {Environment.ProcessorCount}");
                Logging.Log($" Read-only mode: {o.ReadOnly}");
                Logging.Log($" Synology = {o.Synology}");
                Logging.Log($" Indexing = {!o.NoEnableIndexing}");
                Logging.Log($" ThumbGen = {!o.NoGenerateThumbnails}");
                Logging.Log($" Images Root set as {o.SourceDirectory}");
                Logging.Log($" TieredPGO Enabled={tieredPGO}");

                IDataBase dbType = null;

                if (!o.UsePostgresDB)
                {
                    string dbFolder = Path.Combine(o.ConfigPath, "db");

                    if (!Directory.Exists(dbFolder))
                    {
                        Logging.Log(" Created DB folder: {0}", dbFolder);
                        Directory.CreateDirectory(dbFolder);
                    }

                    string dbPath = Path.Combine(dbFolder, "damselfly.db");
                    dbType = new SqlLiteModel(dbPath);
                    Logging.Log(" Sqlite Database location: {0}", dbPath);
                }
                else // Postgres
                {
                    // READ Postgres config json
                    dbType = PostgresModel.ReadSettings("settings.json");
                    Logging.Log(" Postgres Database location: {0}");
                }

                // TODO: https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/providers?tabs=dotnet-core-cli
                BaseDBModel.InitDB <ImageContext>(dbType, o.ReadOnly);

                // Make ourselves low-priority.
                System.Diagnostics.Process.GetCurrentProcess().PriorityClass = System.Diagnostics.ProcessPriorityClass.Idle;

                StartWebServer(o.Port, args);

                Logging.Log("Shutting down.");
            }
            else
            {
                Console.WriteLine("Folder {0} did not exist. Exiting.", o.SourceDirectory);
            }
        }
예제 #19
0
 public static string GetTableName(BaseDBModel model)
 {
     return(string.Format(model.GetDBModel_TableName(), model.GetShuffledModel().TableId));
 }
예제 #20
0
 public void FlushDBWriteCache(BaseDBModel db)
 {
     ExecutePragma(db, "PRAGMA schema.wal_checkpoint;");
 }
예제 #21
0
 public void FlushDBWriteCache(BaseDBModel db)
 {
     //
 }
예제 #22
0
        public static void Main(string[] args)
        {
            Parser.Default.ParseArguments <DamselflyOptions>(args).WithParsed(o =>
            {
                Logging.Verbose = o.Verbose;
                Logging.Trace   = o.Trace;

                if (Directory.Exists(o.SourceDirectory))
                {
                    if (!Directory.Exists(o.ConfigPath))
                    {
                        Directory.CreateDirectory(o.ConfigPath);
                    }

                    Logging.LogFolder = Path.Combine(o.ConfigPath, "logs");

                    Log.Logger = Logging.InitLogs();

                    if (o.ReadOnly)
                    {
                        o.NoEnableIndexing     = true;
                        o.NoGenerateThumbnails = true;
                    }

                    ImageProcessService.UseImageSharp         = o.ImageSharp;
                    IndexingService.EnableIndexing            = !o.NoEnableIndexing;
                    IndexingService.EnableThumbnailGeneration = !o.NoGenerateThumbnails;
                    IndexingService.RootFolder    = o.SourceDirectory;
                    ThumbnailService.PicturesRoot = o.SourceDirectory;
                    ThumbnailService.Synology     = o.Synology;
                    ThumbnailService.SetThumbnailRoot(o.ThumbPath);

                    Logging.Log("Startup State:");
                    Logging.Log($" Damselfly Ver: {Assembly.GetExecutingAssembly().GetName().Version}");
                    Logging.Log($" CLR Ver: {Environment.Version}");
                    Logging.Log($" OS: {Environment.OSVersion}");
                    Logging.Log($" CPU Arch: {RuntimeInformation.ProcessArchitecture}");
                    Logging.Log($" Processor Count: {Environment.ProcessorCount}");
                    Logging.Log($" Read-only mode: {o.ReadOnly}");
                    Logging.Log($" Synology = {o.Synology}");
                    Logging.Log($" Indexing = {!o.NoEnableIndexing}");
                    Logging.Log($" ThumbGen = {!o.NoGenerateThumbnails}");
                    Logging.Log($" Images Root set as {o.SourceDirectory}");

                    IDataBase dbType = null;

                    //if (true) // SQLite Check
                    {
                        string dbFolder = Path.Combine(o.ConfigPath, "db");

                        if (!Directory.Exists(dbFolder))
                        {
                            Logging.Log(" Created DB folder: {0}", dbFolder);
                            Directory.CreateDirectory(dbFolder);
                        }

                        string dbPath = Path.Combine(dbFolder, "damselfly.db");
                        Logging.Log(" Sqlite Database location: {0}", dbPath);
                        dbType = new SqlLiteModel(dbPath);
                    }
                    //else // MySql
                    //{
                    //    dbType = new MySqlModel();
                    //}

                    BaseDBModel.InitDB <ImageContext>(dbType, o.ReadOnly);

                    StartWebServer(o.Port, args);

                    Logging.Log("Shutting down.");
                }
                else
                {
                    Logging.Log("Folder {0} did not exist. Exiting.", o.SourceDirectory);
                }
            });
        }