using System.Data.SQLite; using System.Diagnostics; using System.IO; using Azaion.Common.DTO; using Azaion.Common.DTO.Config; using LinqToDB; using LinqToDB.Data; using LinqToDB.DataProvider.SQLite; using LinqToDB.Mapping; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Azaion.Common.Database; public interface IDbFactory { Task Run(Func> func); Task Run(Func func); void SaveToDisk(); Task DeleteAnnotations(List annotations, CancellationToken cancellationToken = default); } public class DbFactory : IDbFactory { private readonly AnnotationConfig _annConfig; private string MemoryConnStr => "Data Source=:memory:"; private readonly SQLiteConnection _memoryConnection; private readonly DataOptions _memoryDataOptions; private string FileConnStr => $"Data Source={_annConfig.AnnotationsDbFile}"; private readonly SQLiteConnection _fileConnection; private readonly DataOptions _fileDataOptions; public DbFactory(IOptions annConfig, ILogger logger) { _annConfig = annConfig.Value; _memoryConnection = new SQLiteConnection(MemoryConnStr); _memoryConnection.Open(); _memoryDataOptions = new DataOptions() .UseDataProvider(SQLiteTools.GetDataProvider()) .UseConnection(_memoryConnection) .UseMappingSchema(AnnotationsDbSchemaHolder.MappingSchema) ;//.UseTracing(TraceLevel.Info, t => logger.LogInformation(t.SqlText)); _fileConnection = new SQLiteConnection(FileConnStr); _fileDataOptions = new DataOptions() .UseDataProvider(SQLiteTools.GetDataProvider()) .UseConnection(_fileConnection) .UseMappingSchema(AnnotationsDbSchemaHolder.MappingSchema); if (!File.Exists(_annConfig.AnnotationsDbFile)) CreateDb(); _fileConnection.Open(); _fileConnection.BackupDatabase(_memoryConnection, "main", "main", -1, null, -1); } private void CreateDb() { SQLiteConnection.CreateFile(_annConfig.AnnotationsDbFile); using var db = new AnnotationsDb(_fileDataOptions); db.CreateTable(); db.CreateTable(); db.CreateTable(); db.CreateTable(); db.QueueOffsets.BulkCopy(new List { new() { Offset = 0, QueueName = Constants.MQ_ANNOTATIONS_QUEUE }, new() { Offset = 0, QueueName = Constants.MQ_ANNOTATIONS_CONFIRM_QUEUE } }); } public async Task Run(Func> func) { await using var db = new AnnotationsDb(_memoryDataOptions); return await func(db); } public async Task Run(Func func) { await using var db = new AnnotationsDb(_memoryDataOptions); await func(db); } public void SaveToDisk() { _memoryConnection.BackupDatabase(_fileConnection, "main", "main", -1, null, -1); } public async Task DeleteAnnotations(List annotations, CancellationToken cancellationToken = default) { var names = annotations.Select(x => x.Name).ToList(); await Run(async db => { await db.Detections.DeleteAsync(x => names.Contains(x.AnnotationName), token: cancellationToken); await db.Annotations.DeleteAsync(x => names.Contains(x.Name), token: cancellationToken); }); SaveToDisk(); } } public static class AnnotationsDbSchemaHolder { public static readonly MappingSchema MappingSchema; static AnnotationsDbSchemaHolder() { MappingSchema = new MappingSchema(); var builder = new FluentMappingBuilder(MappingSchema); var annotationBuilder = builder.Entity(); annotationBuilder.HasTableName(Constants.ANNOTATIONS_TABLENAME) .HasPrimaryKey(x => x.Name) .Association(a => a.Detections, (a, d) => a.Name == d.AnnotationName) .Property(x => x.Time).HasDataType(DataType.Int64).HasConversion(ts => ts.Ticks, t => new TimeSpan(t)); annotationBuilder .Ignore(x => x.Milliseconds) .Ignore(x => x.Classes) .Ignore(x => x.Classes) .Ignore(x => x.ImagePath) .Ignore(x => x.LabelPath) .Ignore(x => x.ThumbPath); builder.Entity() .HasTableName(Constants.DETECTIONS_TABLENAME); builder.Entity() .HasTableName(Constants.ANNOTATIONS_QUEUE_TABLENAME); builder.Build(); } }