From 59449f8781d3d02e2553003abedbd1fb480b0b17 Mon Sep 17 00:00:00 2001 From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Fri, 13 Nov 2026 10:37:10 +0100 Subject: [PATCH 1/3] add migration for local storage --- AudioCuesheetEditor/Program.cs | 12 ++- .../Options/LocalStorageMigrationService.cs | 30 ++++++++ .../Migrations/ILocalStorageMigration.cs | 17 ++++ .../Migrations/LocalStorageMigrationV1.cs | 77 +++++++++++++++++++ 4 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 AudioCuesheetEditor/Services/Options/LocalStorageMigrationService.cs create mode 100644 AudioCuesheetEditor/Services/Options/Migrations/ILocalStorageMigration.cs create mode 100644 AudioCuesheetEditor/Services/Options/Migrations/LocalStorageMigrationV1.cs diff --git a/AudioCuesheetEditor/Program.cs b/AudioCuesheetEditor/Program.cs index d9ee4a8f..25b2dc6f 100644 --- a/AudioCuesheetEditor/Program.cs +++ b/AudioCuesheetEditor/Program.cs @@ -40,6 +40,8 @@ builder.Services.AddBlazorDownloadFile(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -70,6 +72,14 @@ var host = builder.Build(); +// Run migrations +using (var scope = host.Services.CreateScope()) +{ + var migrationService = scope.ServiceProvider.GetRequiredService(); + + await migrationService.ApplyMigrationsAsync(); +} + await host.SetCultureFromConfigurationAsync(); await host.InitializeSessionStateContainer(); -await host.RunAsync(); \ No newline at end of file +await host.RunAsync(); diff --git a/AudioCuesheetEditor/Services/Options/LocalStorageMigrationService.cs b/AudioCuesheetEditor/Services/Options/LocalStorageMigrationService.cs new file mode 100644 index 00000000..28c38123 --- /dev/null +++ b/AudioCuesheetEditor/Services/Options/LocalStorageMigrationService.cs @@ -0,0 +1,30 @@ +using System.Globalization; +using Microsoft.JSInterop; + +namespace AudioCuesheetEditor.Services.Options; + +public class LocalStorageMigrationService(IJSRuntime jsRuntime, IEnumerable migrations) +{ + readonly IJSRuntime _jsRuntime = jsRuntime; + readonly IEnumerable _migrations = migrations; + const string _versionKey = "OptionsMigrationVersion"; + + public async Task ApplyMigrationsAsync() + { + var storedVersion = await _jsRuntime.InvokeAsync("AppSettings.get", _versionKey); + var currentVersion = int.TryParse(storedVersion, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedVersion) ? parsedVersion : 0; + foreach (var migration in _migrations.OrderBy(x => x.Version)) + { + if (migration.Version <= currentVersion) + { + continue; + } + + await migration.ApplyAsync(); + + await _jsRuntime.InvokeVoidAsync("AppSettings.set", _versionKey, migration.Version.ToString(CultureInfo.InvariantCulture)); + + currentVersion = migration.Version; + } + } +} diff --git a/AudioCuesheetEditor/Services/Options/Migrations/ILocalStorageMigration.cs b/AudioCuesheetEditor/Services/Options/Migrations/ILocalStorageMigration.cs new file mode 100644 index 00000000..ce0cdf7c --- /dev/null +++ b/AudioCuesheetEditor/Services/Options/Migrations/ILocalStorageMigration.cs @@ -0,0 +1,17 @@ +namespace AudioCuesheetEditor.Services.Options; + +/// +/// Interface for local storage migrations +/// +public interface ILocalStorageMigration +{ + /// + /// Version of this migration (used for ordering updates) + /// + int Version { get; } + /// + /// Apply updates from this migration + /// + /// + Task ApplyAsync(); +} diff --git a/AudioCuesheetEditor/Services/Options/Migrations/LocalStorageMigrationV1.cs b/AudioCuesheetEditor/Services/Options/Migrations/LocalStorageMigrationV1.cs new file mode 100644 index 00000000..28af5552 --- /dev/null +++ b/AudioCuesheetEditor/Services/Options/Migrations/LocalStorageMigrationV1.cs @@ -0,0 +1,77 @@ +using System.Text.Json.Nodes; +using System.Text.RegularExpressions; +using Microsoft.JSInterop; + +namespace AudioCuesheetEditor.Services.Options; + +/// +public class LocalStorageMigrationV1(IJSRuntime jsRuntime) : ILocalStorageMigration +{ + readonly IJSRuntime _jsRuntime = jsRuntime; + /// + public int Version => 1; + + /// + public async Task ApplyAsync() + { + await MigrateKeyAsync("ExportOptions", value => value.Replace("%Track.End%", "%Track.Finish%", StringComparison.Ordinal)); + await MigrateKeyAsync("ImportOptions", value => Regex.Replace(value, @"\bEnd\b", "Finish")); + } + + async Task MigrateKeyAsync(string key, Func replace) + { + var storedJson = await _jsRuntime.InvokeAsync("AppSettings.get", key); + if (string.IsNullOrWhiteSpace(storedJson)) + { + return; + } + var root = JsonNode.Parse(storedJson); + if (root is null) + { + return; + } + + if (ReplaceSchemeTracks(root, replace)) + { + await _jsRuntime.InvokeVoidAsync("AppSettings.set", key, root.ToJsonString()); + } + } + + static bool ReplaceSchemeTracks(JsonNode node, Func replace) + { + var changed = false; + + if (node is JsonObject jsonObject) + { + foreach (var property in jsonObject.ToList()) + { + if (property.Key == "SchemeTracks" + && property.Value is JsonValue jsonValue + && jsonValue.TryGetValue(out var oldValue) + && oldValue is not null) + { + var newValue = replace(oldValue); + + if (newValue != oldValue) + { + jsonObject[property.Key] = newValue; + changed = true; + } + } + else + { + changed |= ReplaceSchemeTracks(property.Value, replace); + } + } + } + else if (node is JsonArray jsonArray) + { + foreach (var item in jsonArray) + { + changed |= ReplaceSchemeTracks(item, replace); + } + } + + return changed; + } +} From 964afedc3babbc83a6512ba381a06f120ddaa748 Mon Sep 17 00:00:00 2001 From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Fri, 13 Nov 2026 10:49:25 +0100 Subject: [PATCH 2/3] centralize package versions --- .../AudioCuesheetEditor.End2EndTests.csproj | 4 ++-- .../AudioCuesheetEditor.Tests.csproj | 6 ++--- .../AudioCuesheetEditor.csproj | 20 ++++++++-------- Directory.Packages.props | 23 +++++++++++++++++++ 4 files changed, 38 insertions(+), 15 deletions(-) create mode 100644 Directory.Packages.props diff --git a/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj b/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj index 3922ee65..d982e6bb 100644 --- a/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj +++ b/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj b/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj index 9bfee3fd..18e49aa0 100644 --- a/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj +++ b/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj @@ -9,12 +9,12 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/AudioCuesheetEditor/AudioCuesheetEditor.csproj b/AudioCuesheetEditor/AudioCuesheetEditor.csproj index 23277956..ff365df4 100644 --- a/AudioCuesheetEditor/AudioCuesheetEditor.csproj +++ b/AudioCuesheetEditor/AudioCuesheetEditor.csproj @@ -16,16 +16,16 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 00000000..3bddc5b5 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,23 @@ + + + true + true + + + + + + + + + + + + + + + + + + + \ No newline at end of file From fce995a68e7a626f94b14cd3e565d73bbca2f090 Mon Sep 17 00:00:00 2001 From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Fri, 13 Nov 2026 11:02:36 +0100 Subject: [PATCH 3/3] update libraries --- .../Tests/PlaywrightTestBase.cs | 2 +- Directory.Packages.props | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/AudioCuesheetEditor.End2EndTests/Tests/PlaywrightTestBase.cs b/AudioCuesheetEditor.End2EndTests/Tests/PlaywrightTestBase.cs index 95eb0794..98ebc9f6 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/PlaywrightTestBase.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/PlaywrightTestBase.cs @@ -42,7 +42,7 @@ public async Task TestInitializeAsync() TestPage = Page; } await _testContextInstance.ClearCookiesAsync(); - await _testContextInstance.Tracing.StartAsync(new() + _ = await _testContextInstance.Tracing.StartAsync(new() { Title = $"{TestContext.FullyQualifiedTestClassName}.{TestContext.TestName}", Screenshots = true, diff --git a/Directory.Packages.props b/Directory.Packages.props index 3bddc5b5..ea5aae50 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,20 +3,19 @@ true true - - + - - + + - - - - + + + + - - + +