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, CloudSession> _sessionsByToken = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private readonly ConcurrentDictionary<string, string> _symmetricKeys = new(StringComparer.OrdinalIgnoreCase);
|
private readonly ConcurrentDictionary<string, string> _symmetricKeys = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private readonly ConcurrentDictionary<string, KeyRequestRecord> _keyRequests = 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 Lock _syncRoot = new();
|
||||||
private readonly List<UpdateManifest> _updates;
|
private readonly List<UpdateManifest> _updates;
|
||||||
private readonly List<MediaRecord> _media = [];
|
private readonly List<MediaRecord> _media = [];
|
||||||
@@ -32,8 +32,13 @@ public sealed class InMemoryCloudStateStore : ICloudStateStore
|
|||||||
private DateTimeOffset? _lastSavedUtc;
|
private DateTimeOffset? _lastSavedUtc;
|
||||||
|
|
||||||
public InMemoryCloudStateStore(string? persistencePath = null)
|
public InMemoryCloudStateStore(string? persistencePath = null)
|
||||||
|
: this(new JsonFileSnapshotStore(persistencePath, PersistenceJsonOptions))
|
||||||
{
|
{
|
||||||
_snapshotStore = new JsonSnapshotStore(persistencePath, PersistenceJsonOptions);
|
}
|
||||||
|
|
||||||
|
public InMemoryCloudStateStore(ISnapshotStore snapshotStore)
|
||||||
|
{
|
||||||
|
_snapshotStore = snapshotStore;
|
||||||
_robot = new DeviceRegistration
|
_robot = new DeviceRegistration
|
||||||
{
|
{
|
||||||
HostMappings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
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 ConcurrentDictionary<string, TenantMemoryRecord> _tenantMemory = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private readonly JsonSnapshotStore _snapshotStore;
|
private readonly ISnapshotStore _snapshotStore;
|
||||||
private readonly Lock _syncRoot = new();
|
private readonly Lock _syncRoot = new();
|
||||||
private long _revision;
|
private long _revision;
|
||||||
private DateTimeOffset? _lastLoadedUtc;
|
private DateTimeOffset? _lastLoadedUtc;
|
||||||
private DateTimeOffset? _lastSavedUtc;
|
private DateTimeOffset? _lastSavedUtc;
|
||||||
|
|
||||||
public InMemoryPersonalMemoryStore(string? persistencePath = null)
|
public InMemoryPersonalMemoryStore(string? persistencePath = null)
|
||||||
|
: this(new JsonFileSnapshotStore(persistencePath, PersistenceJsonOptions))
|
||||||
{
|
{
|
||||||
_snapshotStore = new JsonSnapshotStore(persistencePath, PersistenceJsonOptions);
|
}
|
||||||
|
|
||||||
|
public InMemoryPersonalMemoryStore(ISnapshotStore snapshotStore)
|
||||||
|
{
|
||||||
|
_snapshotStore = snapshotStore;
|
||||||
LoadPersistedState();
|
LoadPersistedState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,18 +2,18 @@ using System.Text.Json;
|
|||||||
|
|
||||||
namespace Jibo.Cloud.Infrastructure.Persistence;
|
namespace Jibo.Cloud.Infrastructure.Persistence;
|
||||||
|
|
||||||
internal sealed class JsonSnapshotStore
|
internal sealed class JsonFileSnapshotStore : ISnapshotStore
|
||||||
{
|
{
|
||||||
private readonly string? _persistencePath;
|
private readonly string? _persistencePath;
|
||||||
private readonly JsonSerializerOptions _options;
|
private readonly JsonSerializerOptions _options;
|
||||||
|
|
||||||
public JsonSnapshotStore(string? persistencePath, JsonSerializerOptions options)
|
public JsonFileSnapshotStore(string? persistencePath, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
_persistencePath = persistencePath;
|
_persistencePath = persistencePath;
|
||||||
_options = options;
|
_options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TSnapshot? Load<TSnapshot>()
|
public TSnapshot? Load<TSnapshot>() where TSnapshot : class
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(_persistencePath) || !File.Exists(_persistencePath))
|
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))
|
if (string.IsNullOrWhiteSpace(_persistencePath))
|
||||||
{
|
{
|
||||||
@@ -5,6 +5,33 @@ namespace Jibo.Cloud.Tests.Infrastructure;
|
|||||||
|
|
||||||
public sealed class PersistenceStoreTests
|
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]
|
[Fact]
|
||||||
public void PersonalMemoryStore_RoundTripsStateAndRevision()
|
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