diff --git a/Services/LayoutStore.Snapshots.cs b/Services/LayoutStore.Snapshots.cs index 3846174..b6042ff 100644 --- a/Services/LayoutStore.Snapshots.cs +++ b/Services/LayoutStore.Snapshots.cs @@ -56,10 +56,18 @@ public partial class LayoutStore : IDisposable /// after its factory scenes have been seeded. public void PrepareLayoutSnapshots(string buildId) { - if (GetSnapshotState(DefaultsInitializedKey) != "1" && HasStoredScenes()) + ArgumentException.ThrowIfNullOrWhiteSpace(buildId); + var defaultsInitialized = GetSnapshotState(DefaultsInitializedKey) == "1"; + if (!defaultsInitialized && HasStoredScenes()) + { CaptureDefaults(); + defaultsInitialized = true; + } - if (string.IsNullOrWhiteSpace(GetSnapshotState(CurrentBuildIdKey))) + var currentBuildId = GetSnapshotState(CurrentBuildIdKey); + if (defaultsInitialized && !string.Equals(currentBuildId, buildId, StringComparison.Ordinal)) + RestoreDefaults(buildId); + else if (!defaultsInitialized && string.IsNullOrWhiteSpace(currentBuildId)) SetSnapshotState(CurrentBuildIdKey, buildId); } @@ -121,6 +129,45 @@ public partial class LayoutStore : IDisposable tx.Commit(); } + private void RestoreDefaults(string buildId) + { + var columnsByTable = SnapshotTables.ToDictionary(table => table, ReadColumnNames); + using var tx = _connection.BeginTransaction(); + + foreach (var table in SnapshotTables.Reverse()) + { + using var delete = _connection.CreateCommand(); + delete.Transaction = tx; + delete.CommandText = $"DELETE FROM {table};"; + delete.ExecuteNonQuery(); + } + + foreach (var table in SnapshotTables) + { + var columns = string.Join(", ", columnsByTable[table].Select(QuoteIdentifier)); + using var copy = _connection.CreateCommand(); + copy.Transaction = tx; + copy.CommandText = $"INSERT INTO {table} ({columns}) SELECT {columns} FROM Default{table};"; + copy.ExecuteNonQuery(); + } + + using (var clearSettings = _connection.CreateCommand()) + { + clearSettings.Transaction = tx; + clearSettings.CommandText = "DELETE FROM Settings WHERE Key LIKE 'BackgroundPath_%' OR Key LIKE 'BackgroundUseDefault_%';"; + clearSettings.ExecuteNonQuery(); + } + using (var copySettings = _connection.CreateCommand()) + { + copySettings.Transaction = tx; + copySettings.CommandText = "INSERT INTO Settings (Key, Value) SELECT Key, Value FROM DefaultLayoutSetting;"; + copySettings.ExecuteNonQuery(); + } + + SetSnapshotState(CurrentBuildIdKey, buildId, tx); + tx.Commit(); + } + private bool HasStoredScenes() { using var cmd = _connection.CreateCommand(); @@ -136,9 +183,10 @@ public partial class LayoutStore : IDisposable return cmd.ExecuteScalar() as string; } - private void SetSnapshotState(string key, string value) + private void SetSnapshotState(string key, string value, SqliteTransaction? transaction = null) { using var cmd = _connection.CreateCommand(); + cmd.Transaction = transaction; cmd.CommandText = """ INSERT INTO LayoutSnapshotState (Key, Value) VALUES ($key, $value) ON CONFLICT(Key) DO UPDATE SET Value = excluded.Value; diff --git a/ytLive.Tests/LayoutStoreSnapshotTests.cs b/ytLive.Tests/LayoutStoreSnapshotTests.cs index dc17c4a..3febf26 100644 --- a/ytLive.Tests/LayoutStoreSnapshotTests.cs +++ b/ytLive.Tests/LayoutStoreSnapshotTests.cs @@ -48,6 +48,38 @@ public sealed class LayoutStoreSnapshotTests } } + [Fact] + public void DifferentBuild_RestoresTheDefaultSnapshotBeforeLoading() + { + var path = Path.Combine(Path.GetTempPath(), $"ytLlive-layout-snapshot-{Guid.NewGuid():N}.db"); + try + { + using (var store = new LayoutStore(path)) + { + store.Save(new[] { MakeScene("Factory title") }, null, null); + store.PrepareLayoutSnapshots("build-a"); + store.Save(new[] { MakeScene("Creator edit") }, null, null); + } + + using (var reopened = new LayoutStore(path)) + { + reopened.PrepareLayoutSnapshots("build-b"); + var restored = Assert.Single(reopened.Load()); + Assert.Equal("Factory title", Assert.Single(restored.Elements.OfType()).Name); + + using var connection = GetConnection(path); + using var command = connection.CreateCommand(); + command.CommandText = "SELECT Value FROM LayoutSnapshotState WHERE Key = 'CurrentBuildId';"; + Assert.Equal("build-b", command.ExecuteScalar()); + } + } + finally + { + SqliteConnection.ClearAllPools(); + try { File.Delete(path); } catch { /* best-effort cleanup */ } + } + } + private static Scene MakeScene(string sourceName) => new() {