Add C# publication catalog sync
This commit is contained in:
parent
47a84f5689
commit
e94cd0973d
2 changed files with 147 additions and 0 deletions
|
|
@ -299,6 +299,14 @@ namespace GameCult.Mesh
|
|||
CultMeshVerseContext context,
|
||||
CultMeshStoreDocumentOptions? storeOptions,
|
||||
CultMeshPeerSnapshotDocumentOptions? peerOptions);
|
||||
|
||||
/// <summary>Reads this publication document and hydrates it into a local node.</summary>
|
||||
Task<ICultMeshDocumentHandle> SyncAsync(
|
||||
CultMeshNode node,
|
||||
CultMeshDocumentPublicationSource source,
|
||||
CultMeshVerseContext context,
|
||||
CultMeshStoreDocumentOptions? storeOptions,
|
||||
CultMeshPeerSnapshotDocumentOptions? peerOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -345,6 +353,32 @@ namespace GameCult.Mesh
|
|||
CultMesh.WithPublicationBindingOptions(storeOptions, Key, DocumentId, SourceId),
|
||||
CultMesh.WithPublicationBindingOptions(peerOptions, Key, DocumentId, SourceId));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ICultMeshDocumentHandle> SyncAsync(
|
||||
CultMeshNode node,
|
||||
CultMeshDocumentPublicationSource source,
|
||||
CultMeshVerseContext context,
|
||||
CultMeshStoreDocumentOptions? storeOptions,
|
||||
CultMeshPeerSnapshotDocumentOptions? peerOptions)
|
||||
{
|
||||
if (node == null) throw new ArgumentNullException(nameof(node));
|
||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||
|
||||
var publication = CultMesh.DocumentFromPublication<TDocument>(
|
||||
Source ?? source,
|
||||
Key,
|
||||
context,
|
||||
CultMesh.WithPublicationBindingOptions(storeOptions, Key, DocumentId, SourceId),
|
||||
CultMesh.WithPublicationBindingOptions(peerOptions, Key, DocumentId, SourceId));
|
||||
var local = CultMesh.Document<TDocument>(
|
||||
node,
|
||||
Key,
|
||||
context,
|
||||
string.IsNullOrWhiteSpace(DocumentId) ? Key.Value : DocumentId);
|
||||
await local.ReplaceAsync(await publication.LatestAsync().ConfigureAwait(false)).ConfigureAwait(false);
|
||||
return local;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// A locally hosted CultMesh node over CultCache, CultNet transport, and the mesh database facade.
|
||||
|
|
@ -1667,6 +1701,57 @@ namespace GameCult.Mesh
|
|||
return DocumentsFromPublication(source, bindings, verse.Context, storeOptions, peerOptions);
|
||||
}
|
||||
/// <summary>
|
||||
/// Reads configured publication documents, hydrates them into a local node, and returns local handles as a catalog.
|
||||
/// </summary>
|
||||
public static async Task<CultMeshDocumentCatalog> SyncDocumentsFromPublicationAsync(
|
||||
CultMeshNode node,
|
||||
CultMeshDocumentPublicationSource source,
|
||||
IEnumerable<ICultMeshPublicationDocumentBinding> bindings,
|
||||
CultMeshVerseContext context,
|
||||
CultMeshStoreDocumentOptions? storeOptions = null,
|
||||
CultMeshPeerSnapshotDocumentOptions? peerOptions = null,
|
||||
bool flush = false)
|
||||
{
|
||||
if (node == null) throw new ArgumentNullException(nameof(node));
|
||||
if (source == null) throw new ArgumentNullException(nameof(source));
|
||||
if (bindings == null) throw new ArgumentNullException(nameof(bindings));
|
||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||
|
||||
var handles = await Task.WhenAll(bindings.Select(binding =>
|
||||
{
|
||||
if (binding == null) throw new ArgumentException("CultMesh publication bindings cannot contain null.", nameof(bindings));
|
||||
return binding.SyncAsync(node, source, context, storeOptions, peerOptions);
|
||||
})).ConfigureAwait(false);
|
||||
|
||||
if (flush)
|
||||
await node.FlushAsync().ConfigureAwait(false);
|
||||
|
||||
return Documents(handles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads configured publication documents, hydrates them into a local node, and returns local handles as a catalog.
|
||||
/// </summary>
|
||||
public static Task<CultMeshDocumentCatalog> SyncDocumentsFromPublicationAsync(
|
||||
CultMeshNode node,
|
||||
CultMeshDocumentPublicationSource source,
|
||||
IEnumerable<ICultMeshPublicationDocumentBinding> bindings,
|
||||
CultMeshVerse verse,
|
||||
CultMeshStoreDocumentOptions? storeOptions = null,
|
||||
CultMeshPeerSnapshotDocumentOptions? peerOptions = null,
|
||||
bool flush = false)
|
||||
{
|
||||
if (verse == null) throw new ArgumentNullException(nameof(verse));
|
||||
return SyncDocumentsFromPublicationAsync(
|
||||
node,
|
||||
source,
|
||||
bindings,
|
||||
verse.Context,
|
||||
storeOptions,
|
||||
peerOptions,
|
||||
flush);
|
||||
}
|
||||
/// <summary>
|
||||
/// Reads one typed document from a configured publication source and hydrates it into a local node.
|
||||
/// </summary>
|
||||
public static async Task<TDocument> SyncDocumentFromPublicationAsync<TDocument>(
|
||||
|
|
|
|||
|
|
@ -2359,6 +2359,68 @@ public sealed class CultMeshStreamingTests
|
|||
catalog.Document<MeshNoteAliasDocument>().Sources.Should().ContainSingle().Which.SourceId.Should().Be("daemon:second");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task PublicationHelpers_SyncConfiguredDocumentsIntoLocalCatalog()
|
||||
{
|
||||
using var directory = new TemporaryDirectory();
|
||||
var firstPath = Path.Combine(directory.Path, "sync-first.ccmp");
|
||||
var secondPath = Path.Combine(directory.Path, "sync-second.ccmp");
|
||||
var firstKey = new CultRecordKey("mesh-note:publication-sync-catalog-first");
|
||||
var secondKey = new CultRecordKey("mesh-note:publication-sync-catalog-second");
|
||||
var firstWriter = new CultCache();
|
||||
firstWriter.AddBackingStore(new SingleFileMessagePackBackingStore(firstPath));
|
||||
await firstWriter.UpsertAsync(typeof(MeshPublicationNoteDocument), new MeshPublicationNoteDocument
|
||||
{
|
||||
Schema = "tests.mesh_publication_note.v1",
|
||||
Text = "first synced source",
|
||||
Revision = 31
|
||||
}, firstKey);
|
||||
firstWriter.FlushAllBackingStores();
|
||||
var secondWriter = new CultCache();
|
||||
secondWriter.AddBackingStore(new SingleFileMessagePackBackingStore(secondPath));
|
||||
await secondWriter.UpsertAsync(new MeshNoteDocument
|
||||
{
|
||||
Schema = "tests.mesh_note.v1",
|
||||
Text = "second synced source",
|
||||
Revision = 32
|
||||
}, new CultRecordHandle<MeshNoteDocument>(secondKey));
|
||||
secondWriter.FlushAllBackingStores();
|
||||
using var node = await CultMesh.CreateNodeAsync(
|
||||
Path.Combine(directory.Path, "target.ccmp"),
|
||||
new CultMeshNodeOptions
|
||||
{
|
||||
StartServer = false
|
||||
});
|
||||
|
||||
var catalog = await CultMesh.SyncDocumentsFromPublicationAsync(
|
||||
node,
|
||||
CultMeshDocumentPublicationSource.SingleFile(firstPath),
|
||||
new ICultMeshPublicationDocumentBinding[]
|
||||
{
|
||||
CultMesh.PublicationDocument<MeshPublicationNoteDocument>(
|
||||
firstKey,
|
||||
documentId: "local:first"),
|
||||
CultMesh.PublicationDocument<MeshNoteAliasDocument>(
|
||||
secondKey,
|
||||
documentId: "local:second",
|
||||
source: CultMeshDocumentPublicationSource.SingleFile(secondPath))
|
||||
},
|
||||
CultMesh.Verse("starbridge", "unity-sync-catalog"),
|
||||
new CultMeshStoreDocumentOptions
|
||||
{
|
||||
PollInterval = TimeSpan.FromMilliseconds(10)
|
||||
});
|
||||
|
||||
catalog.Documents.Select(document => document.DocumentId).Should().Equal("local:first", "local:second");
|
||||
(await catalog.LatestAsync<MeshPublicationNoteDocument>()).Text.Should().Be("first synced source");
|
||||
(await catalog.LatestAsync<MeshNoteAliasDocument>()).Text.Should().Be("second synced source");
|
||||
node.Cache.Get<MeshPublicationNoteDocument>(firstKey)!.Revision.Should().Be(31);
|
||||
(await node.Document<MeshNoteDocument>(secondKey, CultMesh.Verse("starbridge", "unity-sync-catalog"))
|
||||
.LatestAsync()).Text.Should().Be("second synced source");
|
||||
(await node.Document<MeshNoteAliasDocument>(secondKey, CultMesh.Verse("starbridge", "unity-sync-catalog"))
|
||||
.LatestAsync()).Revision.Should().Be(32);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task PublicationHelpers_SyncConfiguredPeerSourceIntoLocalNodeAlias()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue