From 76af705a4c7dc05fa2eda78e4e91c2cb76f5af7e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 May 2026 16:51:42 +0000 Subject: [PATCH 1/5] Initial plan From dac1a67ea659534962581d4c5fab6e52c5227c87 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 May 2026 16:56:13 +0000 Subject: [PATCH 2/5] Add database template, gitignore actual DB, and copy-on-startup logic Agent-Logs-Url: https://github.com/Bommberk/SystemGameManager/sessions/af01ac42-b3fb-46c5-865e-ed2088819a15 Co-authored-by: Bommberk <83213983+Bommberk@users.noreply.github.com> --- .gitignore | 3 ++- database/DatabaseController.cs | 18 +++++++++++++++++- ...nager.db => template-systemgamemanager.db} | Bin 32768 -> 32768 bytes 3 files changed, 19 insertions(+), 2 deletions(-) rename database/{systemgamemanager.db => template-systemgamemanager.db} (78%) diff --git a/.gitignore b/.gitignore index 1d33522..baa0110 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ obj .vscode temp-build-check Output -RELEASE* \ No newline at end of file +RELEASE* +database/systemgamemanager.db \ No newline at end of file diff --git a/database/DatabaseController.cs b/database/DatabaseController.cs index d61abdc..57b3288 100644 --- a/database/DatabaseController.cs +++ b/database/DatabaseController.cs @@ -45,9 +45,25 @@ public void ShowTable(string tableName) dump(rows); } + private static readonly object _dbInitLock = new(); + protected static SqliteConnection GetSqlConnection() { - string dbPath = "Data Source=database/systemgamemanager.db"; + const string dbFile = "database/systemgamemanager.db"; + const string templateFile = "database/template-systemgamemanager.db"; + + lock (_dbInitLock) + { + if (!File.Exists(dbFile)) + { + if (File.Exists(templateFile)) + { + File.Copy(templateFile, dbFile); + } + } + } + + string dbPath = $"Data Source={dbFile}"; var connection = new SqliteConnection(dbPath); connection.Open(); return connection; diff --git a/database/systemgamemanager.db b/database/template-systemgamemanager.db similarity index 78% rename from database/systemgamemanager.db rename to database/template-systemgamemanager.db index 81de5674b6a6d993fb0e2182f7f9e7a251d8b6b7..7f052c886393ba80518b7bd85b2b96a198871fef 100644 GIT binary patch delta 503 zcmZo@U}|V!(g_aq$t+1#NXswEO)OC`W)NUtVq$PmP+(wSU>ovEeAy_QhwIVeju_Pl_A;dKzL-LG^LeH0A!dSXcgxR7NiD+A3^dm#GpQ)CsM0Mn2gAdY_w!1C z+{xR@$;QYoE-ud4oISaiPX*$3NwDc4r-H*s!OuTL!7tRuM~7=NBfmCKjTOHXCs+;0 k0h1T-%P|3^rt_;aa&11tZ=tZ5ML}ROi-5vr7J)zV0FoHq)$ literal 32768 zcmeI5OK{u98GsjpNbw;OeuSnGMJcQ%Bg?j|+Op+X8jS-{kiyZ^glNSzR0Sed6e@^d z0MNENeGJ_?mtK18_|WN0(_^N`UV7-Y?ex-1dvh-BG`F7GUOLlVJSc($Wjn4rdyuB;Q`v9W3mc)`98>ljsCu81%j%AQbxpLMTDL!G|S*{@{chnYM6` zAj#wMCu14zpJ*UmMOmHueKtS%lgwYz)xk!OBoGY*fB+Bx0zd!=00AHX1YUmvkJ9mh zd@jd)|Ikr)47{iwxZi<7NfI|CVMCl-mV|pWdP_K;NeLaFq9znqH>5?WB&?T;D`M%c zuq53T#PY^kah2q;BCT#*%B1eCx*2RpA8rV%YvfyAUKYx$#oJ|xN{cnyQ4M3kG&F3j ztIpn*Kvk(odWgdj>z4T~Y}-c}QmvJStyk`tM&kglW2=H|&Q?$^DyGGcmWFFIHaZZ` z&*T`S*EIal{@5UQAlqugG2QcRn$7kV`aAJR>L17t4>Ld5auJ(0r*V%UnrK&p>&SpvX?R!20wbOs|ke%0zd!=00AHX1b_e#00J*gpoy6@%IEKn zk2lAp71_b+flYrErWLX;zG|rl0^Nyi3+ErcH#MfX3W}?78Iz6l(_Tz)we1Sjdfiqk z=D~qkyEj5K!YYcF+EwzkMlOvkw$!}c-%(myN4XoG)~>s~-@bfb+m+WAHa5kQq-?Hj zPF8?Gb5yuYVBnd^2}A+#E)W zk|>DvI#vbJXk9QQgKWyT%$i;iM9Z-iqCAi7eaEbagMK6}_?OFrWb9Xuqz`vaMd>U> zyY0^KaU238&5zhLx_GfUbSqy*dU;2;&0R+*n6(qe@Zd&9G9Lk(tQ!O3a%BEhMx~5P~*Gff00e*l5C8%|00;m9An@`8 zhS(V-2{coD5{>OQPi7dVaJYl5dZV^4T>aXO8)R|8odEnd%eE~*c&il3OQ|a*X})e@ z!(T0+4ozOOPAAZ4@nDo;7B|E@f>hg6$?Ie_Qj$eWt!cu>9wz=|k-P|4cwVVOa=N}9 z~qho{+Cu(+EWVSLJ5;ql?JJxBgpze!%!X5 z@_59j&|B#GgXR#~gtaU!t_cO*ITXs|rN~yLIns$xx?qXd@cx~m41fn^U zTKj#6WE1@HJ0_~-W2XfS^mwgsl61L=+0zU&r#g;-cXe!N!W5wwI#;?Pc6O&|PUOIA ziOkrt1n zVnOz-$-H*IKZ%C6nlaMq>!d&9a{Zaz@dfo1Uq3if)4Z1aGfLf#rTNlT zS_OLKHS<&gP0m!t8D^Ds3i7{~1i4N_A7IrXdAqR-p;+2MqoQdX(R7p3)YM349Is`c zqVi0^yg~+P-Oy_eg@g@5m^0MMzCqp`zBoP6Lo(4*bRnHUbX1TI_l!}&YjrMxW~J?~ zM0L(RRWl#hkzLahMR!bJM!KaJ@;j$@!fW5iCeTfBEowNFYh;>@vgZmSt$7l$Q1+~p zd2K(FKr@AVWS;68ZY#Q_RwKunCyBJ+i^PH#@a%YDUi;2q0xiinYDOs*i==BFs5WU- z9670kH6o37sE7p;;gx^{c+E-moX|@6KKUb0M@3`{wuvJRx68#sX}#oGB>^%@lEEnH zB3vv+HD9q9X@f)u8QAVD?AOhoT!T1X`)(qEinpsn5lvdW*e$bW87qf7^~NUJTOSO|h1;^2&2A$^@qA-S1r z-Xqhwd(|M*r$9DU%O+PZ6kJ$QxpbJmDhCOLsTL zki;H60g$lX6t|PJ1%#0%xTt)aR`m$NkRYNEkjh)Vi6Ke=)TRZa4dwdz_ulJjtHm{>J^0`$d+`j&MKAe3AKW=Cf?+72mf&_CNp#00AHX z1b_e#00KY&2mpcqBLb(`v5qAh{w;RabF)V{E%R@);xV_JXL$BTv@XwClS%V3((VhAy(*i4XE4TTdQna33mWsvX2wRMZZ};XTBJ*zZ^$*at u>B_t1TaOR3x4OlFhbES0hXOZy|0pXzkB^2u%U+LMHtOTpca!Z4RR0ET^Cg)8 From 5c6961bd5e423696d6735e0263cd0b131278f604 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 09:51:22 +0000 Subject: [PATCH 3/5] Fix WAL journal files and add schema sync from template DB Agent-Logs-Url: https://github.com/Bommberk/SystemGameManager/sessions/c8698f19-c8cc-4a68-bd56-a0d69427cf55 Co-authored-by: Bommberk <83213983+Bommberk@users.noreply.github.com> --- .gitignore | 5 ++- database/DatabaseController.cs | 60 ++++++++++++++++++++++--- database/template-systemgamemanager.db | Bin 32768 -> 32768 bytes 3 files changed, 57 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index baa0110..b5e3a4f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,7 @@ obj temp-build-check Output RELEASE* -database/systemgamemanager.db \ No newline at end of file +database/systemgamemanager.db +*.db-wal +*.db-shm +*.db-journal \ No newline at end of file diff --git a/database/DatabaseController.cs b/database/DatabaseController.cs index 57b3288..e77aab7 100644 --- a/database/DatabaseController.cs +++ b/database/DatabaseController.cs @@ -54,21 +54,67 @@ protected static SqliteConnection GetSqlConnection() lock (_dbInitLock) { - if (!File.Exists(dbFile)) + if (!File.Exists(dbFile) && File.Exists(templateFile)) { - if (File.Exists(templateFile)) - { - File.Copy(templateFile, dbFile); - } + File.Copy(templateFile, dbFile); + } + else if (File.Exists(dbFile) && File.Exists(templateFile)) + { + SyncSchemaFromTemplate(dbFile, templateFile); } } - string dbPath = $"Data Source={dbFile}"; - var connection = new SqliteConnection(dbPath); + var connection = new SqliteConnection($"Data Source={dbFile}"); connection.Open(); + + using var pragma = connection.CreateCommand(); + pragma.CommandText = "PRAGMA journal_mode=DELETE;"; + pragma.ExecuteNonQuery(); + return connection; } + private static void SyncSchemaFromTemplate(string dbFile, string templateFile) + { + var templateTables = new Dictionary(StringComparer.OrdinalIgnoreCase); + using (var templateConn = new SqliteConnection($"Data Source={templateFile};Mode=ReadOnly")) + { + templateConn.Open(); + using var cmd = templateConn.CreateCommand(); + cmd.CommandText = "SELECT name, sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';"; + using var reader = cmd.ExecuteReader(); + while (reader.Read()) + { + templateTables[reader.GetString(0)] = reader.GetString(1); + } + } + + if (templateTables.Count == 0) return; + + using var mainConn = new SqliteConnection($"Data Source={dbFile}"); + mainConn.Open(); + + var existingTables = new HashSet(StringComparer.OrdinalIgnoreCase); + using (var cmd = mainConn.CreateCommand()) + { + cmd.CommandText = "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';"; + using var reader = cmd.ExecuteReader(); + while (reader.Read()) + { + existingTables.Add(reader.GetString(0)); + } + } + + foreach (var (tableName, createSql) in templateTables) + { + if (existingTables.Contains(tableName)) continue; + + using var cmd = mainConn.CreateCommand(); + cmd.CommandText = createSql; + cmd.ExecuteNonQuery(); + } + } + public DatabaseService GetDatabaseService() { return new DatabaseService(); diff --git a/database/template-systemgamemanager.db b/database/template-systemgamemanager.db index 7f052c886393ba80518b7bd85b2b96a198871fef..39b6755ee9d430577bb3036cec1af0af71e8b3ff 100644 GIT binary patch delta 60 zcmZo@U}|V!(g_aq$t+1#NXswEO)OC`W)NUtWMptqP+(wSU Date: Wed, 6 May 2026 11:38:03 +0000 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20auto-sync=20entity=20schema=20to=20?= =?UTF-8?q?template=20and=20add=20column-level=20template=E2=86=92live=20s?= =?UTF-8?q?ync?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent-Logs-Url: https://github.com/Bommberk/SystemGameManager/sessions/9a76567e-6fb7-4ced-b27e-32789ccbde35 Co-authored-by: Bommberk <83213983+Bommberk@users.noreply.github.com> --- database/DatabaseController.cs | 167 ++++++++++++++++++++++++++++++--- 1 file changed, 155 insertions(+), 12 deletions(-) diff --git a/database/DatabaseController.cs b/database/DatabaseController.cs index e77aab7..d73dd86 100644 --- a/database/DatabaseController.cs +++ b/database/DatabaseController.cs @@ -1,5 +1,6 @@ namespace Krassheiten.SystemGameManager.Controller; +using System.Reflection; using Microsoft.Data.Sqlite; using Krassheiten.SystemGameManager.Service; using Krassheiten.SystemGameManager.Entity; @@ -54,6 +55,11 @@ protected static SqliteConnection GetSqlConnection() lock (_dbInitLock) { + if (File.Exists(templateFile)) + { + SyncEntitiesToTemplate(templateFile); + } + if (!File.Exists(dbFile) && File.Exists(templateFile)) { File.Copy(templateFile, dbFile); @@ -74,22 +80,136 @@ protected static SqliteConnection GetSqlConnection() return connection; } + private static void SyncEntitiesToTemplate(string templateFile) + { + var entityTypes = Assembly.GetExecutingAssembly() + .GetTypes() + .Where(t => t.Name == "Record" && t.IsClass && t.DeclaringType != null) + .Select(t => new + { + RecordType = t, + TableName = t.DeclaringType! + .GetField("TABLE_NAME", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy) + ?.GetRawConstantValue() as string + }) + .Where(x => !string.IsNullOrWhiteSpace(x.TableName)) + .ToList(); + + if (entityTypes.Count == 0) return; + + using var conn = new SqliteConnection($"Data Source={templateFile}"); + conn.Open(); + + using var pragma = conn.CreateCommand(); + pragma.CommandText = "PRAGMA journal_mode=DELETE;"; + pragma.ExecuteNonQuery(); + + foreach (var entity in entityTypes) + { + var properties = entity.RecordType + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(p => p.CanRead && p.CanWrite) + .OrderBy(p => p.MetadataToken) + .ToArray(); + + if (properties.Length == 0) continue; + + var columns = new List { "Id INTEGER PRIMARY KEY AUTOINCREMENT" }; + foreach (var property in properties) + { + var colType = GetEntitySqlType(property.PropertyType); + var colDef = property.Name.Equals("Name", StringComparison.OrdinalIgnoreCase) + ? $"[{property.Name}] {colType} NOT NULL UNIQUE" + : $"[{property.Name}] {colType}"; + columns.Add(colDef); + } + + using (var cmd = conn.CreateCommand()) + { + var columnDefinitions = string.Join(",\r\n ", columns); + cmd.CommandText = $@" + CREATE TABLE IF NOT EXISTS [{entity.TableName}] ( + {columnDefinitions} + );"; + cmd.ExecuteNonQuery(); + } + + var existingColumns = new HashSet(StringComparer.OrdinalIgnoreCase); + using (var cmd = conn.CreateCommand()) + { + cmd.CommandText = $"PRAGMA table_info([{entity.TableName}]);"; + using var reader = cmd.ExecuteReader(); + while (reader.Read()) + { + existingColumns.Add(reader.GetString(1)); + } + } + + foreach (var property in properties) + { + if (existingColumns.Contains(property.Name)) continue; + + var colType = GetEntitySqlType(property.PropertyType); + using var cmd = conn.CreateCommand(); + cmd.CommandText = $"ALTER TABLE [{entity.TableName}] ADD COLUMN [{property.Name}] {colType};"; + cmd.ExecuteNonQuery(); + } + } + } + + private static string GetEntitySqlType(Type propertyType) + { + var type = Nullable.GetUnderlyingType(propertyType) ?? propertyType; + + if (type == typeof(bool) || type == typeof(byte) || type == typeof(sbyte) || + type == typeof(short) || type == typeof(ushort) || type == typeof(int) || + type == typeof(uint) || type == typeof(long) || type == typeof(ulong)) + { + return "INTEGER"; + } + + if (type == typeof(float) || type == typeof(double) || type == typeof(decimal)) + { + return "REAL"; + } + + return "TEXT"; + } + private static void SyncSchemaFromTemplate(string dbFile, string templateFile) { - var templateTables = new Dictionary(StringComparer.OrdinalIgnoreCase); + var templateSchema = new Dictionary Columns)>(StringComparer.OrdinalIgnoreCase); + using (var templateConn = new SqliteConnection($"Data Source={templateFile};Mode=ReadOnly")) { templateConn.Open(); - using var cmd = templateConn.CreateCommand(); - cmd.CommandText = "SELECT name, sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';"; - using var reader = cmd.ExecuteReader(); - while (reader.Read()) + + var tableNames = new List<(string Name, string Sql)>(); + using (var cmd = templateConn.CreateCommand()) + { + cmd.CommandText = "SELECT name, sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';"; + using var reader = cmd.ExecuteReader(); + while (reader.Read()) + { + tableNames.Add((reader.GetString(0), reader.GetString(1))); + } + } + + foreach (var (name, sql) in tableNames) { - templateTables[reader.GetString(0)] = reader.GetString(1); + var columns = new List<(string Name, string Type)>(); + using var colCmd = templateConn.CreateCommand(); + colCmd.CommandText = $"PRAGMA table_info([{name}]);"; + using var colReader = colCmd.ExecuteReader(); + while (colReader.Read()) + { + columns.Add((colReader.GetString(1), colReader.GetString(2))); + } + templateSchema[name] = (sql, columns); } } - if (templateTables.Count == 0) return; + if (templateSchema.Count == 0) return; using var mainConn = new SqliteConnection($"Data Source={dbFile}"); mainConn.Open(); @@ -105,13 +225,36 @@ private static void SyncSchemaFromTemplate(string dbFile, string templateFile) } } - foreach (var (tableName, createSql) in templateTables) + foreach (var (tableName, (createSql, templateColumns)) in templateSchema) { - if (existingTables.Contains(tableName)) continue; + if (!existingTables.Contains(tableName)) + { + using var cmd = mainConn.CreateCommand(); + cmd.CommandText = createSql; + cmd.ExecuteNonQuery(); + } + else + { + var existingColumns = new HashSet(StringComparer.OrdinalIgnoreCase); + using (var cmd = mainConn.CreateCommand()) + { + cmd.CommandText = $"PRAGMA table_info([{tableName}]);"; + using var reader = cmd.ExecuteReader(); + while (reader.Read()) + { + existingColumns.Add(reader.GetString(1)); + } + } + + foreach (var (colName, colType) in templateColumns) + { + if (existingColumns.Contains(colName)) continue; - using var cmd = mainConn.CreateCommand(); - cmd.CommandText = createSql; - cmd.ExecuteNonQuery(); + using var cmd = mainConn.CreateCommand(); + cmd.CommandText = $"ALTER TABLE [{tableName}] ADD COLUMN [{colName}] {colType};"; + cmd.ExecuteNonQuery(); + } + } } } From 0fd569995925549664d418aac293382b1fffb322 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 11:39:46 +0000 Subject: [PATCH 5/5] fix: remove hardcoded whitespace in CREATE TABLE SQL string Agent-Logs-Url: https://github.com/Bommberk/SystemGameManager/sessions/9a76567e-6fb7-4ced-b27e-32789ccbde35 Co-authored-by: Bommberk <83213983+Bommberk@users.noreply.github.com> --- database/DatabaseController.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/database/DatabaseController.cs b/database/DatabaseController.cs index d73dd86..0c7e492 100644 --- a/database/DatabaseController.cs +++ b/database/DatabaseController.cs @@ -126,11 +126,8 @@ private static void SyncEntitiesToTemplate(string templateFile) using (var cmd = conn.CreateCommand()) { - var columnDefinitions = string.Join(",\r\n ", columns); - cmd.CommandText = $@" - CREATE TABLE IF NOT EXISTS [{entity.TableName}] ( - {columnDefinitions} - );"; + var columnDefinitions = string.Join(", ", columns); + cmd.CommandText = $"CREATE TABLE IF NOT EXISTS [{entity.TableName}] ({columnDefinitions});"; cmd.ExecuteNonQuery(); }