feat(37): reset one scene to its factory snapshot

The per-scene reset restores that scene's settings, layers, webcam placement,
and background-path settings while leaving other scenes' current edits alone.
A focused integration test covers the restore/isolation behavior.
611 tests green, clean build with 0 warnings.

Reference: OBS scene collections store scenes and sources as a saved set:
https://obsproject.com/kb/scene-collections
This commit is contained in:
2026-11-10 17:52:20 -08:00
parent 1c6fc3223d
commit c4da06b56b
2 changed files with 162 additions and 1 deletions
+114
View File
@@ -78,6 +78,120 @@ public partial class LayoutStore : IDisposable
CaptureDefaults();
}
/// <summary>Restores one existing scene by its current ID. Unknown scenes have no branded default
/// and are left untouched.</summary>
public bool RestoreDefaultScene(string sceneId)
{
ArgumentException.ThrowIfNullOrWhiteSpace(sceneId);
string? defaultSceneId;
using (var find = _connection.CreateCommand())
{
find.CommandText = """
SELECT Id FROM DefaultScene
WHERE Name = (SELECT Name FROM Scene WHERE Id = $sceneId) COLLATE NOCASE
LIMIT 1;
""";
find.Parameters.AddWithValue("$sceneId", sceneId);
defaultSceneId = find.ExecuteScalar() as string;
}
if (defaultSceneId == null) return false;
var columnsByTable = SnapshotTables.ToDictionary(table => table, ReadColumnNames);
var webcamId = GetCurrentWebcamId();
using var tx = _connection.BeginTransaction();
using (var clearBackgroundSettings = _connection.CreateCommand())
{
clearBackgroundSettings.Transaction = tx;
clearBackgroundSettings.CommandText = """
DELETE FROM Settings
WHERE Key IN (
SELECT 'BackgroundPath_' || Id FROM Source WHERE SceneId = $sceneId
UNION SELECT 'BackgroundUseDefault_' || Id FROM Source WHERE SceneId = $sceneId
UNION SELECT 'BackgroundPath_' || $sceneId
UNION SELECT 'BackgroundUseDefault_' || $sceneId
);
""";
clearBackgroundSettings.Parameters.AddWithValue("$sceneId", sceneId);
clearBackgroundSettings.ExecuteNonQuery();
}
foreach (var table in new[] { "WebcamSceneConfig", "Source" })
{
using var delete = _connection.CreateCommand();
delete.Transaction = tx;
delete.CommandText = $"DELETE FROM {table} WHERE SceneId = $sceneId;";
delete.Parameters.AddWithValue("$sceneId", sceneId);
delete.ExecuteNonQuery();
}
var sceneColumns = columnsByTable["Scene"]
.Where(column => column != "Id" && column != "SortOrder").ToArray();
var assignments = string.Join(", ", sceneColumns.Select(column =>
$"{QuoteIdentifier(column)} = (SELECT {QuoteIdentifier(column)} FROM DefaultScene WHERE Id = $defaultSceneId)"));
using (var updateScene = _connection.CreateCommand())
{
updateScene.Transaction = tx;
updateScene.CommandText = $"UPDATE Scene SET {assignments} WHERE Id = $sceneId;";
updateScene.Parameters.AddWithValue("$sceneId", sceneId);
updateScene.Parameters.AddWithValue("$defaultSceneId", defaultSceneId);
updateScene.ExecuteNonQuery();
}
CopyDefaultSceneRows("Source", sceneId, defaultSceneId, null, tx, columnsByTable["Source"]);
if (webcamId != null)
CopyDefaultSceneRows("WebcamSceneConfig", sceneId, defaultSceneId, webcamId, tx,
columnsByTable["WebcamSceneConfig"]);
using (var copyBackgroundSettings = _connection.CreateCommand())
{
copyBackgroundSettings.Transaction = tx;
copyBackgroundSettings.CommandText = """
INSERT INTO Settings (Key, Value)
SELECT Key, Value FROM DefaultLayoutSetting
WHERE Key IN (
SELECT 'BackgroundPath_' || Id FROM DefaultSource WHERE SceneId = $defaultSceneId
UNION SELECT 'BackgroundUseDefault_' || Id FROM DefaultSource WHERE SceneId = $defaultSceneId
UNION SELECT 'BackgroundPath_' || $defaultSceneId
UNION SELECT 'BackgroundUseDefault_' || $defaultSceneId
);
""";
copyBackgroundSettings.Parameters.AddWithValue("$defaultSceneId", defaultSceneId);
copyBackgroundSettings.ExecuteNonQuery();
}
tx.Commit();
return true;
}
private string? GetCurrentWebcamId()
{
using var cmd = _connection.CreateCommand();
cmd.CommandText = "SELECT Id FROM Webcam LIMIT 1;";
return cmd.ExecuteScalar() as string;
}
private void CopyDefaultSceneRows(string table, string sceneId, string defaultSceneId,
string? webcamId, SqliteTransaction tx, IReadOnlyList<string> columns)
{
var columnList = string.Join(", ", columns.Select(QuoteIdentifier));
var values = string.Join(", ", columns.Select(column => column switch
{
"SceneId" => "$sceneId",
"WebcamId" when webcamId != null => "$webcamId",
_ => QuoteIdentifier(column),
}));
using var copy = _connection.CreateCommand();
copy.Transaction = tx;
copy.CommandText = $"INSERT INTO {table} ({columnList}) SELECT {values} FROM Default{table} WHERE SceneId = $defaultSceneId;";
copy.Parameters.AddWithValue("$sceneId", sceneId);
copy.Parameters.AddWithValue("$defaultSceneId", defaultSceneId);
if (webcamId != null) copy.Parameters.AddWithValue("$webcamId", webcamId);
copy.ExecuteNonQuery();
}
private void CaptureDefaults()
{
var columnsByTable = SnapshotTables.ToDictionary(table => table, ReadColumnNames);