From 785dc2b48b58cda31137a543033c674770ad69ad Mon Sep 17 00:00:00 2001 From: Jacob Dubin Date: Sun, 17 May 2026 06:58:12 -0500 Subject: [PATCH] Extract JSON snapshot persistence helpers --- .../Persistence/InMemoryCloudStateStore.cs | 166 ++++++++---------- .../InMemoryPersonalMemoryStore.cs | 48 ++--- .../Persistence/JsonSnapshotStore.cs | 48 +++++ 3 files changed, 131 insertions(+), 131 deletions(-) create mode 100644 OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Persistence/JsonSnapshotStore.cs diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Persistence/InMemoryCloudStateStore.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Persistence/InMemoryCloudStateStore.cs index 0aa3021..2ad0b5d 100644 --- a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Persistence/InMemoryCloudStateStore.cs +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Persistence/InMemoryCloudStateStore.cs @@ -18,7 +18,7 @@ public sealed class InMemoryCloudStateStore : ICloudStateStore private readonly ConcurrentDictionary _sessionsByToken = new(StringComparer.OrdinalIgnoreCase); private readonly ConcurrentDictionary _symmetricKeys = new(StringComparer.OrdinalIgnoreCase); private readonly ConcurrentDictionary _keyRequests = new(StringComparer.OrdinalIgnoreCase); - private readonly string? _persistencePath; + private readonly JsonSnapshotStore _snapshotStore; private readonly Lock _syncRoot = new(); private readonly List _updates; private readonly List _media = []; @@ -33,7 +33,7 @@ public sealed class InMemoryCloudStateStore : ICloudStateStore public InMemoryCloudStateStore(string? persistencePath = null) { - _persistencePath = persistencePath; + _snapshotStore = new JsonSnapshotStore(persistencePath, PersistenceJsonOptions); _robot = new DeviceRegistration { HostMappings = new Dictionary(StringComparer.OrdinalIgnoreCase) @@ -104,111 +104,88 @@ public sealed class InMemoryCloudStateStore : ICloudStateStore public void LoadPersistedState() { - if (string.IsNullOrWhiteSpace(_persistencePath) || !File.Exists(_persistencePath)) + var snapshot = _snapshotStore.Load(); + if (snapshot is null) { return; } - try + _account = snapshot.Account ?? _account; + _robot = snapshot.Robot ?? _robot; + _robotProfile = snapshot.RobotProfile ?? _robotProfile; + + _devices.Clear(); + foreach (var device in snapshot.Devices ?? []) { - var snapshot = JsonSerializer.Deserialize(File.ReadAllText(_persistencePath), PersistenceJsonOptions); - if (snapshot is null) - { - return; - } - - _account = snapshot.Account ?? _account; - _robot = snapshot.Robot ?? _robot; - _robotProfile = snapshot.RobotProfile ?? _robotProfile; - - _devices.Clear(); - foreach (var device in snapshot.Devices ?? []) - { - _devices[device.DeviceId] = device; - } - - if (_devices.IsEmpty || !_devices.ContainsKey(_robot.DeviceId)) - { - _devices[_robot.DeviceId] = _robot; - } - - _sessionsByToken.Clear(); - foreach (var session in snapshot.Sessions ?? []) - { - if (!string.IsNullOrWhiteSpace(session.Token)) - { - _sessionsByToken[session.Token] = session.ToRecord(); - } - } - - _symmetricKeys.Clear(); - foreach (var pair in snapshot.SymmetricKeys ?? new Dictionary(StringComparer.OrdinalIgnoreCase)) - { - _symmetricKeys[pair.Key] = pair.Value; - } - - _keyRequests.Clear(); - foreach (var keyRequest in snapshot.KeyRequests ?? []) - { - _keyRequests[keyRequest.RequestId] = keyRequest; - } - - _updates.Clear(); - _updates.AddRange(snapshot.Updates ?? []); - - _media.Clear(); - _media.AddRange(snapshot.Media ?? []); - - _backups.Clear(); - _backups.AddRange(snapshot.Backups ?? []); - - _loops.Clear(); - _loops.AddRange(snapshot.Loops ?? []); - - _people.Clear(); - _people.AddRange(snapshot.People ?? []); - - if (_robotProfile is null || !string.Equals(_robotProfile.RobotId, _robot.RobotId, StringComparison.OrdinalIgnoreCase)) - { - _robotProfile = new RobotProfile - { - RobotId = _robot.RobotId, - Payload = new Dictionary - { - ["SSID"] = "my-ssid", - ["connectedAt"] = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), - ["platform"] = _robot.FirmwareVersion ?? "12.10.0", - ["serialNumber"] = _robot.DeviceId - }, - UpdatedUtc = DateTimeOffset.UtcNow - }; - } - - Interlocked.Exchange(ref _revision, snapshot.Revision); - _lastLoadedUtc = snapshot.LastLoadedUtc ?? DateTimeOffset.UtcNow; - _lastSavedUtc = snapshot.LastSavedUtc; + _devices[device.DeviceId] = device; } - catch + + if (_devices.IsEmpty || !_devices.ContainsKey(_robot.DeviceId)) { - // Ignore corrupt state and continue with the in-memory defaults. + _devices[_robot.DeviceId] = _robot; } + + _sessionsByToken.Clear(); + foreach (var session in snapshot.Sessions ?? []) + { + if (!string.IsNullOrWhiteSpace(session.Token)) + { + _sessionsByToken[session.Token] = session.ToRecord(); + } + } + + _symmetricKeys.Clear(); + foreach (var pair in snapshot.SymmetricKeys ?? new Dictionary(StringComparer.OrdinalIgnoreCase)) + { + _symmetricKeys[pair.Key] = pair.Value; + } + + _keyRequests.Clear(); + foreach (var keyRequest in snapshot.KeyRequests ?? []) + { + _keyRequests[keyRequest.RequestId] = keyRequest; + } + + _updates.Clear(); + _updates.AddRange(snapshot.Updates ?? []); + + _media.Clear(); + _media.AddRange(snapshot.Media ?? []); + + _backups.Clear(); + _backups.AddRange(snapshot.Backups ?? []); + + _loops.Clear(); + _loops.AddRange(snapshot.Loops ?? []); + + _people.Clear(); + _people.AddRange(snapshot.People ?? []); + + if (_robotProfile is null || !string.Equals(_robotProfile.RobotId, _robot.RobotId, StringComparison.OrdinalIgnoreCase)) + { + _robotProfile = new RobotProfile + { + RobotId = _robot.RobotId, + Payload = new Dictionary + { + ["SSID"] = "my-ssid", + ["connectedAt"] = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), + ["platform"] = _robot.FirmwareVersion ?? "12.10.0", + ["serialNumber"] = _robot.DeviceId + }, + UpdatedUtc = DateTimeOffset.UtcNow + }; + } + + Interlocked.Exchange(ref _revision, snapshot.Revision); + _lastLoadedUtc = snapshot.LastLoadedUtc ?? DateTimeOffset.UtcNow; + _lastSavedUtc = snapshot.LastSavedUtc; } public void SavePersistedState() { - if (string.IsNullOrWhiteSpace(_persistencePath)) - { - return; - } - lock (_syncRoot) { - var directory = Path.GetDirectoryName(_persistencePath); - if (!string.IsNullOrWhiteSpace(directory)) - { - Directory.CreateDirectory(directory); - } - var now = DateTimeOffset.UtcNow; var snapshot = new PersistentStateSnapshot { @@ -229,8 +206,7 @@ public sealed class InMemoryCloudStateStore : ICloudStateStore Loops = _loops.ToArray(), People = _people.ToArray() }; - - File.WriteAllText(_persistencePath, JsonSerializer.Serialize(snapshot, PersistenceJsonOptions)); + _snapshotStore.Save(snapshot); _lastSavedUtc = now; } } diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Persistence/InMemoryPersonalMemoryStore.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Persistence/InMemoryPersonalMemoryStore.cs index e53aeb2..18f4e23 100644 --- a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Persistence/InMemoryPersonalMemoryStore.cs +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Persistence/InMemoryPersonalMemoryStore.cs @@ -13,7 +13,7 @@ public sealed class InMemoryPersonalMemoryStore : IPersonalMemoryStore }; private readonly ConcurrentDictionary _tenantMemory = new(StringComparer.OrdinalIgnoreCase); - private readonly string? _persistencePath; + private readonly JsonSnapshotStore _snapshotStore; private readonly Lock _syncRoot = new(); private long _revision; private DateTimeOffset? _lastLoadedUtc; @@ -21,7 +21,7 @@ public sealed class InMemoryPersonalMemoryStore : IPersonalMemoryStore public InMemoryPersonalMemoryStore(string? persistencePath = null) { - _persistencePath = persistencePath; + _snapshotStore = new JsonSnapshotStore(persistencePath, PersistenceJsonOptions); LoadPersistedState(); } @@ -36,50 +36,27 @@ public sealed class InMemoryPersonalMemoryStore : IPersonalMemoryStore public void LoadPersistedState() { - if (string.IsNullOrWhiteSpace(_persistencePath) || !File.Exists(_persistencePath)) + var snapshot = _snapshotStore.Load(); + if (snapshot is null) { return; } - try + _tenantMemory.Clear(); + foreach (var tenant in snapshot.Tenants ?? []) { - var snapshot = JsonSerializer.Deserialize(File.ReadAllText(_persistencePath), PersistenceJsonOptions); - if (snapshot is null) - { - return; - } - - _tenantMemory.Clear(); - foreach (var tenant in snapshot.Tenants ?? []) - { - _tenantMemory[tenant.TenantKey] = tenant.ToRecord(); - } - - Interlocked.Exchange(ref _revision, snapshot.Revision); - _lastLoadedUtc = snapshot.LastLoadedUtc ?? DateTimeOffset.UtcNow; - _lastSavedUtc = snapshot.LastSavedUtc; - } - catch - { - // Ignore corrupt state and continue with the in-memory defaults. + _tenantMemory[tenant.TenantKey] = tenant.ToRecord(); } + + Interlocked.Exchange(ref _revision, snapshot.Revision); + _lastLoadedUtc = snapshot.LastLoadedUtc ?? DateTimeOffset.UtcNow; + _lastSavedUtc = snapshot.LastSavedUtc; } public void SavePersistedState() { - if (string.IsNullOrWhiteSpace(_persistencePath)) - { - return; - } - lock (_syncRoot) { - var directory = Path.GetDirectoryName(_persistencePath); - if (!string.IsNullOrWhiteSpace(directory)) - { - Directory.CreateDirectory(directory); - } - var now = DateTimeOffset.UtcNow; var snapshot = new PersistentStateSnapshot { @@ -103,8 +80,7 @@ public sealed class InMemoryPersonalMemoryStore : IPersonalMemoryStore }) .ToArray() }; - - File.WriteAllText(_persistencePath, JsonSerializer.Serialize(snapshot, PersistenceJsonOptions)); + _snapshotStore.Save(snapshot); _lastSavedUtc = now; } } diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Persistence/JsonSnapshotStore.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Persistence/JsonSnapshotStore.cs new file mode 100644 index 0000000..c20fb74 --- /dev/null +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Persistence/JsonSnapshotStore.cs @@ -0,0 +1,48 @@ +using System.Text.Json; + +namespace Jibo.Cloud.Infrastructure.Persistence; + +internal sealed class JsonSnapshotStore +{ + private readonly string? _persistencePath; + private readonly JsonSerializerOptions _options; + + public JsonSnapshotStore(string? persistencePath, JsonSerializerOptions options) + { + _persistencePath = persistencePath; + _options = options; + } + + public TSnapshot? Load() + { + if (string.IsNullOrWhiteSpace(_persistencePath) || !File.Exists(_persistencePath)) + { + return default; + } + + try + { + return JsonSerializer.Deserialize(File.ReadAllText(_persistencePath), _options); + } + catch + { + return default; + } + } + + public void Save(TSnapshot snapshot) + { + if (string.IsNullOrWhiteSpace(_persistencePath)) + { + return; + } + + var directory = Path.GetDirectoryName(_persistencePath); + if (!string.IsNullOrWhiteSpace(directory)) + { + Directory.CreateDirectory(directory); + } + + File.WriteAllText(_persistencePath, JsonSerializer.Serialize(snapshot, _options)); + } +}