using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Threading.Tasks; namespace Elwig.Helpers.Export { public static class Database { 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 db = zip.CreateEntryFromFile(App.Config.DatabaseFile, "database.sqlite3", CompressionLevel.SmallestSize); } else { File.Copy(App.Config.DatabaseFile, filename, true); } } public static async Task ExportSql(string filename, bool zipFile) { 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); } else { using var stream = File.OpenWrite(filename); using var writer = new StreamWriter(stream, Utils.UTF8); await ExportSql(writer); } } public static async Task ExportSql(StreamWriter writer) { using var cnx = await AppDbContext.ConnectAsync(); var tables = new List<(string Name, string Sql)>(); using (var cmd = cnx.CreateCommand()) { cmd.CommandText = "SELECT name, sql FROM sqlite_schema WHERE type = 'table'"; using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { tables.Add((reader.GetString(0), reader.GetString(1))); } } var applId = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA application_id") ?? 0; var userVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA user_version") ?? 0; var schemaVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA schema_version") ?? 0; await writer.WriteLineAsync($"-- Elwig database dump, {DateTime.Now:yyyy-MM-dd, HH:mm:ss}"); await writer.WriteLineAsync($"-- {Environment.MachineName}, Zwst. {App.BranchName}, {App.Client.Name}"); await writer.WriteLineAsync("BEGIN TRANSACTION;"); await writer.WriteLineAsync("PRAGMA foreign_keys=OFF;"); 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); await writer.WriteLineAsync(";"); var columnNames = new List(); using (var cmd = cnx.CreateCommand()) { cmd.CommandText = $"PRAGMA table_info({t.Name})"; using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { columnNames.Add(reader.GetString(1)); } } using (var cmd = cnx.CreateCommand()) { cmd.CommandText = $"SELECT {string.Join(',', columnNames)} FROM {t.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 ("); reader.GetValues(values); for (int i = 0; i < columns.Count; i++) { var c = columns[i]; var v = values[i]; if (i > 0) await writer.WriteAsync(","); if (v == null || v is DBNull) { await writer.WriteAsync("NULL"); } else if (c.DataTypeName == "TEXT") { await writer.WriteAsync($"'{v.ToString()?.Replace("'", "''")}'"); } else { await writer.WriteAsync(v.ToString()?.Replace(',', '.')); } } await writer.WriteLineAsync(");"); } } } using (var cmd = cnx.CreateCommand()) { cmd.CommandText = "SELECT sql FROM sqlite_schema WHERE type != 'table' AND sql IS NOT NULL"; using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { await writer.WriteAsync(reader.GetString(0)); await writer.WriteLineAsync(";"); } } await writer.WriteLineAsync($"PRAGMA schema_version={schemaVers};"); await writer.WriteLineAsync("PRAGMA foreign_keys=ON;"); await writer.WriteLineAsync("COMMIT;"); await writer.WriteLineAsync("VACUUM;"); } public static async Task Import(string filename) { if (filename.EndsWith(".sql")) { await ImportSql(filename, false); } else if (filename.EndsWith(".sql.zip")) { await ImportSql(filename, true); } else if (filename.EndsWith(".sqlite3")) { await ImportSqlite(filename, false); } else if (filename.EndsWith(".sqlite3.zip")) { await ImportSqlite(filename, true); } else { throw new ArgumentException($"Unknown file extension for importing: '{filename}'"); } } public static async Task ImportSql(string filename, bool zipFile = false) { if (zipFile) { using var zip = ZipFile.Open(filename, ZipArchiveMode.Read); await zip.CheckIntegrity(); foreach (var entry in zip.Entries) { if (entry.Name.EndsWith(".sql")) { using var stream = entry.Open(); using var reader = new StreamReader(stream, Utils.UTF8); await ImportSql(reader); return; } } throw new FileFormatException("ZIP archive has to contain at least one .sql file"); } else { using var stream = File.Open(filename, FileMode.Open); using var reader = new StreamReader(stream, Utils.UTF8); await ImportSql(reader); } } public static async Task ImportSqlite(string filename, bool zipFile = false) { if (zipFile) { var newName = Path.ChangeExtension(App.Config.DatabaseFile, ".new.sqlite3"); try { using var zip = ZipFile.Open(filename, ZipArchiveMode.Read); await zip.CheckIntegrity(); foreach (var entry in zip.Entries) { if (entry.Name.EndsWith(".sqlite3")) { entry.ExtractToFile(newName); await ImportSqlite(newName); return; } } throw new FileFormatException("ZIP archive has to contain at least one .sqlite3 file"); } finally { if (File.Exists(newName)) File.Delete(newName); } } var oldName = Path.ChangeExtension(App.Config.DatabaseFile, ".old.sqlite3"); File.Move(App.Config.DatabaseFile, oldName, true); File.Move(filename, App.Config.DatabaseFile, false); using var cnx = await AppDbContext.ConnectAsync(); await AppDbContext.ExecuteBatch(cnx, "VACUUM"); } public static async Task ImportSql(StreamReader reader) { var newName = Path.ChangeExtension(App.Config.DatabaseFile, ".new.sqlite3"); File.Delete(newName); try { using (var cnx = await AppDbContext.ConnectAsync($"Data Source=\"{newName}\"; Mode=ReadWriteCreate; Foreign Keys=False; Cache=Default; Pooling=False")) { await AppDbContext.ExecuteBatch(cnx, await reader.ReadToEndAsync()); } await ImportSqlite(newName); } finally { if (File.Exists(newName)) File.Delete(newName); } } } }