Introduce pluggable snapshot storage for persistence
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
namespace Jibo.Cloud.Infrastructure.Persistence;
|
||||
|
||||
public interface ISnapshotStore
|
||||
{
|
||||
TSnapshot? Load<TSnapshot>() where TSnapshot : class;
|
||||
void Save<TSnapshot>(TSnapshot snapshot) where TSnapshot : class;
|
||||
}
|
||||
@@ -18,7 +18,7 @@ public sealed class InMemoryCloudStateStore : ICloudStateStore
|
||||
private readonly ConcurrentDictionary<string, CloudSession> _sessionsByToken = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly ConcurrentDictionary<string, string> _symmetricKeys = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly ConcurrentDictionary<string, KeyRequestRecord> _keyRequests = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly JsonSnapshotStore _snapshotStore;
|
||||
private readonly ISnapshotStore _snapshotStore;
|
||||
private readonly Lock _syncRoot = new();
|
||||
private readonly List<UpdateManifest> _updates;
|
||||
private readonly List<MediaRecord> _media = [];
|
||||
@@ -32,8 +32,13 @@ public sealed class InMemoryCloudStateStore : ICloudStateStore
|
||||
private DateTimeOffset? _lastSavedUtc;
|
||||
|
||||
public InMemoryCloudStateStore(string? persistencePath = null)
|
||||
: this(new JsonFileSnapshotStore(persistencePath, PersistenceJsonOptions))
|
||||
{
|
||||
_snapshotStore = new JsonSnapshotStore(persistencePath, PersistenceJsonOptions);
|
||||
}
|
||||
|
||||
public InMemoryCloudStateStore(ISnapshotStore snapshotStore)
|
||||
{
|
||||
_snapshotStore = snapshotStore;
|
||||
_robot = new DeviceRegistration
|
||||
{
|
||||
HostMappings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
|
||||
@@ -13,15 +13,20 @@ public sealed class InMemoryPersonalMemoryStore : IPersonalMemoryStore
|
||||
};
|
||||
|
||||
private readonly ConcurrentDictionary<string, TenantMemoryRecord> _tenantMemory = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly JsonSnapshotStore _snapshotStore;
|
||||
private readonly ISnapshotStore _snapshotStore;
|
||||
private readonly Lock _syncRoot = new();
|
||||
private long _revision;
|
||||
private DateTimeOffset? _lastLoadedUtc;
|
||||
private DateTimeOffset? _lastSavedUtc;
|
||||
|
||||
public InMemoryPersonalMemoryStore(string? persistencePath = null)
|
||||
: this(new JsonFileSnapshotStore(persistencePath, PersistenceJsonOptions))
|
||||
{
|
||||
_snapshotStore = new JsonSnapshotStore(persistencePath, PersistenceJsonOptions);
|
||||
}
|
||||
|
||||
public InMemoryPersonalMemoryStore(ISnapshotStore snapshotStore)
|
||||
{
|
||||
_snapshotStore = snapshotStore;
|
||||
LoadPersistedState();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,18 +2,18 @@ using System.Text.Json;
|
||||
|
||||
namespace Jibo.Cloud.Infrastructure.Persistence;
|
||||
|
||||
internal sealed class JsonSnapshotStore
|
||||
internal sealed class JsonFileSnapshotStore : ISnapshotStore
|
||||
{
|
||||
private readonly string? _persistencePath;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
|
||||
public JsonSnapshotStore(string? persistencePath, JsonSerializerOptions options)
|
||||
public JsonFileSnapshotStore(string? persistencePath, JsonSerializerOptions options)
|
||||
{
|
||||
_persistencePath = persistencePath;
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public TSnapshot? Load<TSnapshot>()
|
||||
public TSnapshot? Load<TSnapshot>() where TSnapshot : class
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_persistencePath) || !File.Exists(_persistencePath))
|
||||
{
|
||||
@@ -30,7 +30,7 @@ internal sealed class JsonSnapshotStore
|
||||
}
|
||||
}
|
||||
|
||||
public void Save<TSnapshot>(TSnapshot snapshot)
|
||||
public void Save<TSnapshot>(TSnapshot snapshot) where TSnapshot : class
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_persistencePath))
|
||||
{
|
||||
@@ -5,6 +5,33 @@ namespace Jibo.Cloud.Tests.Infrastructure;
|
||||
|
||||
public sealed class PersistenceStoreTests
|
||||
{
|
||||
[Fact]
|
||||
public void PersonalMemoryStore_CanUseAlternateSnapshotBackend()
|
||||
{
|
||||
var backend = new RecordingSnapshotStore();
|
||||
var store = new InMemoryPersonalMemoryStore(backend);
|
||||
var scope = new PersonalMemoryTenantScope("acct-b", "loop-b", "device-b", "person-b");
|
||||
|
||||
store.SetName(scope, "Alt Backend");
|
||||
|
||||
Assert.Single(backend.Saves);
|
||||
Assert.Equal("Alt Backend", store.GetName(scope));
|
||||
Assert.Equal("1", store.GetPersistenceStateInfo().SchemaVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CloudStateStore_CanUseAlternateSnapshotBackend()
|
||||
{
|
||||
var backend = new RecordingSnapshotStore();
|
||||
var store = new InMemoryCloudStateStore(backend);
|
||||
|
||||
store.CreateMedia("openjibo-default-loop", "backend-photo", "image", "photo-ref", false, null);
|
||||
|
||||
Assert.Single(backend.Saves);
|
||||
Assert.Contains(store.ListMedia(), item => item.Path == "backend-photo");
|
||||
Assert.Equal("1", store.GetPersistenceStateInfo().SchemaVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PersonalMemoryStore_RoundTripsStateAndRevision()
|
||||
{
|
||||
@@ -84,4 +111,19 @@ public sealed class PersistenceStoreTests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RecordingSnapshotStore : ISnapshotStore
|
||||
{
|
||||
public List<object> Saves { get; } = [];
|
||||
|
||||
public TSnapshot2? Load<TSnapshot2>() where TSnapshot2 : class
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
public void Save<TSnapshot2>(TSnapshot2 snapshot) where TSnapshot2 : class
|
||||
{
|
||||
Saves.Add(snapshot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user