diff --git a/Elwig/Helpers/Export/Database.cs b/Elwig/Helpers/Export/Database.cs index d1c4aa1..4984434 100644 --- a/Elwig/Helpers/Export/Database.cs +++ b/Elwig/Helpers/Export/Database.cs @@ -2,16 +2,48 @@ using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; +using System.Text.Json.Nodes; using System.Threading.Tasks; namespace Elwig.Helpers.Export { public static class Database { + private static async Task<(long? ApplicationId, string? UserVersion, long? SchemaVersion, long FileSize)> GetMeta() { + long size = new FileInfo(App.Config.DatabaseFile).Length; + using var cnx = await AppDbContext.ConnectAsync(); + var applId = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA application_id"); + var userVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA user_version"); + var schemaVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA schema_version"); + return (applId, userVers != null ? $"{userVers >> 24}.{(userVers >> 16) & 0xFF}.{(userVers >> 8) & 0xFF}.{userVers & 0xFF}" : null, schemaVers, size); + } + public static async Task ExportSqlite(string filename, bool zipFile) { if (zipFile) { File.Delete(filename); using var zip = ZipFile.Open(filename, ZipArchiveMode.Create); - await zip.CheckIntegrity(); + + var version = zip.CreateEntry("version", CompressionLevel.NoCompression); + using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) { + await writer.WriteAsync("elwig-db:1"); + } + + var (applId, userVers, schemaVers, size) = await GetMeta(); + var meta = zip.CreateEntry("meta.json", CompressionLevel.NoCompression); + using (var writer = new StreamWriter(meta.Open(), Utils.UTF8)) { + var obj = new JsonObject { + ["timestamp"] = $"{DateTime.UtcNow:yyyy-MM-ddTHH:mm:ssZ}", + ["zwstid"] = App.ZwstId, + ["device"] = Environment.MachineName, + ["database"] = new JsonObject { + ["application_id"] = applId, + ["user_version"] = userVers, + ["schema_version"] = schemaVers, + ["file_size"] = size, + }, + }; + await writer.WriteAsync(obj.ToJsonString(Utils.JsonOpts)); + } + var db = zip.CreateEntryFromFile(App.Config.DatabaseFile, "database.sqlite3", CompressionLevel.SmallestSize); } else { File.Copy(App.Config.DatabaseFile, filename, true); @@ -22,10 +54,33 @@ namespace Elwig.Helpers.Export { if (zipFile) { File.Delete(filename); using var zip = ZipFile.Open(filename, ZipArchiveMode.Create); - var entry = zip.CreateEntry("database.sql", CompressionLevel.SmallestSize); - using var stream = entry.Open(); - using var writer = new StreamWriter(stream, Utils.UTF8); - await ExportSql(writer); + + var version = zip.CreateEntry("version", CompressionLevel.NoCompression); + using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) { + await writer.WriteAsync("elwig-db:1"); + } + + var (applId, userVers, schemaVers, size) = await GetMeta(); + var meta = zip.CreateEntry("meta.json", CompressionLevel.NoCompression); + using (var writer = new StreamWriter(meta.Open(), Utils.UTF8)) { + var obj = new JsonObject { + ["timestamp"] = $"{DateTime.UtcNow:yyyy-MM-ddTHH:mm:ssZ}", + ["zwstid"] = App.ZwstId, + ["device"] = Environment.MachineName, + ["database"] = new JsonObject { + ["application_id"] = applId, + ["user_version"] = userVers, + ["schema_version"] = schemaVers, + ["file_size"] = size, + }, + }; + await writer.WriteAsync(obj.ToJsonString(Utils.JsonOpts)); + } + + var sql = zip.CreateEntry("database.sql", CompressionLevel.SmallestSize); + using (var writer = new StreamWriter(sql.Open(), Utils.UTF8)) { + await ExportSql(writer); + } } else { using var stream = File.OpenWrite(filename); using var writer = new StreamWriter(stream, Utils.UTF8); @@ -56,13 +111,13 @@ namespace Elwig.Helpers.Export { await writer.WriteLineAsync($"PRAGMA application_id=0x{applId:X8};"); await writer.WriteLineAsync($"PRAGMA user_version=0x{userVers:X8};"); - foreach (var t in tables) { - await writer.WriteAsync(t.Sql); + foreach (var (name, sql) in tables) { + await writer.WriteAsync(sql); await writer.WriteLineAsync(";"); var columnNames = new List(); using (var cmd = cnx.CreateCommand()) { - cmd.CommandText = $"PRAGMA table_info({t.Name})"; + cmd.CommandText = $"PRAGMA table_info({name})"; using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { columnNames.Add(reader.GetString(1)); @@ -70,12 +125,12 @@ namespace Elwig.Helpers.Export { } using (var cmd = cnx.CreateCommand()) { - cmd.CommandText = $"SELECT {string.Join(',', columnNames)} FROM {t.Name}"; + cmd.CommandText = $"SELECT {string.Join(',', columnNames)} FROM {name}"; using var reader = await cmd.ExecuteReaderAsync(); var columns = await reader.GetColumnSchemaAsync(); var values = new object[reader.FieldCount]; while (await reader.ReadAsync()) { - await writer.WriteAsync($"INSERT INTO {t.Name} VALUES ("); + await writer.WriteAsync($"INSERT INTO {name} VALUES ("); reader.GetValues(values); for (int i = 0; i < columns.Count; i++) { diff --git a/Elwig/Helpers/Export/ElwigData.cs b/Elwig/Helpers/Export/ElwigData.cs index 8471735..bd05061 100644 --- a/Elwig/Helpers/Export/ElwigData.cs +++ b/Elwig/Helpers/Export/ElwigData.cs @@ -6,7 +6,6 @@ using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; -using System.Text.Json; using System.Text.Json.Nodes; using System.Threading.Tasks; using System.Windows; @@ -18,8 +17,6 @@ namespace Elwig.Helpers.Export { public static readonly string ImportedTxt = Path.Combine(App.DataPath, "imported.txt"); - private static readonly JsonSerializerOptions JsonOpts = new() { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; - public static async Task GetImportedFiles() { try { return await File.ReadAllLinesAsync(ImportedTxt, Utils.UTF8); @@ -469,7 +466,7 @@ namespace Elwig.Helpers.Export { ["parts"] = Deliveries.Value.Deliveries.Sum(d => d.Parts.Count), ["filters"] = new JsonArray(Deliveries.Value.Filters.Select(f => (JsonNode)f).ToArray()), }; - await writer.WriteAsync(obj.ToJsonString(JsonOpts)); + await writer.WriteAsync(obj.ToJsonString(Utils.JsonOpts)); } // TODO encrypt files @@ -477,28 +474,28 @@ namespace Elwig.Helpers.Export { var json = zip.CreateEntry("wb_kgs.json", CompressionLevel.SmallestSize); using var writer = new StreamWriter(json.Open(), Utils.UTF8); foreach (var k in WbKgs.Value.WbKgs) { - await writer.WriteLineAsync(k.ToJson().ToJsonString(JsonOpts)); + await writer.WriteLineAsync(k.ToJson().ToJsonString(Utils.JsonOpts)); } } if (Members != null) { var json = zip.CreateEntry("members.json", CompressionLevel.SmallestSize); using var writer = new StreamWriter(json.Open(), Utils.UTF8); foreach (var m in Members.Value.Members) { - await writer.WriteLineAsync(m.ToJson().ToJsonString(JsonOpts)); + await writer.WriteLineAsync(m.ToJson().ToJsonString(Utils.JsonOpts)); } } if (AreaComs != null) { var json = zip.CreateEntry("area_commitments.json", CompressionLevel.SmallestSize); using var writer = new StreamWriter(json.Open(), Utils.UTF8); foreach (var c in AreaComs.Value.AreaComs) { - await writer.WriteLineAsync(c.ToJson().ToJsonString(JsonOpts)); + await writer.WriteLineAsync(c.ToJson().ToJsonString(Utils.JsonOpts)); } } if (Deliveries != null) { var json = zip.CreateEntry("deliveries.json", CompressionLevel.SmallestSize); using var writer = new StreamWriter(json.Open(), Utils.UTF8); foreach (var d in Deliveries.Value.Deliveries) { - await writer.WriteLineAsync(d.ToJson().ToJsonString(JsonOpts)); + await writer.WriteLineAsync(d.ToJson().ToJsonString(Utils.JsonOpts)); } } } @@ -826,7 +823,7 @@ namespace Elwig.Helpers.Export { Temperature = p["temperature"]?.AsValue().GetValue(), Acid = p["acid"]?.AsValue().GetValue(), ScaleId = p["scale_id"]?.AsValue().GetValue(), - WeighingData = p["weighing_data"]?.AsObject().ToJsonString(JsonOpts), + WeighingData = p["weighing_data"]?.AsObject().ToJsonString(Utils.JsonOpts), WeighingReason = p["weighing_reason"]?.AsValue().GetValue(), }; }).ToList(), json["parts"]!.AsArray().SelectMany(p => p!["modids"]!.AsArray().Select(m => new DeliveryPartModifier { diff --git a/Elwig/Helpers/Utils.cs b/Elwig/Helpers/Utils.cs index decd923..66c71e8 100644 --- a/Elwig/Helpers/Utils.cs +++ b/Elwig/Helpers/Utils.cs @@ -1,40 +1,42 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using System.Windows; -using System.Diagnostics; -using System.Text.RegularExpressions; -using System.IO.Ports; -using System.Net.Sockets; using Elwig.Dialogs; -using System.Text; -using System.Numerics; -using Elwig.Models.Entities; +using Elwig.Documents; using Elwig.Helpers.Billing; -using System.Runtime.InteropServices; -using System.Net.Http; -using System.Text.Json.Nodes; -using System.IO; +using Elwig.Models; +using Elwig.Models.Entities; +using LinqKit; using MailKit.Net.Smtp; using MailKit.Security; using Microsoft.EntityFrameworkCore; -using System.Reflection; -using System.Collections; -using Elwig.Documents; -using MimeKit; -using LinqKit; -using System.Linq.Expressions; -using Elwig.Models; using Microsoft.Win32; +using MimeKit; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; +using System.IO; +using System.IO.Ports; +using System.Linq; +using System.Linq.Expressions; +using System.Net.Http; +using System.Net.Sockets; +using System.Numerics; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.RegularExpressions; using System.Threading; +using System.Threading.Tasks; +using System.Windows; using System.Windows.Markup; namespace Elwig.Helpers { public static partial class Utils { public static readonly Encoding UTF8 = new UTF8Encoding(false, true); + public static readonly JsonSerializerOptions JsonOpts = new() { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; public static int CurrentYear => DateTime.Now.Year; public static int CurrentNextSeason => DateTime.Now.Year - (DateTime.Now.Month <= 3 ? 1 : 0);