diff --git a/src/Speckle.Sdk/Api/Operations/Operations.Send.cs b/src/Speckle.Sdk/Api/Operations/Operations.Send.cs index 7670484e..dbe3fbed 100644 --- a/src/Speckle.Sdk/Api/Operations/Operations.Send.cs +++ b/src/Speckle.Sdk/Api/Operations/Operations.Send.cs @@ -14,7 +14,7 @@ public partial class Operations /// Sends a Speckle Object to the provided and (optionally) the default local cache /// /// - /// + /// /// When , an additional will be included /// The or was /// @@ -23,7 +23,7 @@ public partial class Operations /// public async Task<(string rootObjId, IReadOnlyDictionary convertedReferences)> Send( Base value, - ITransport transport, + IWritableTransport transport, bool useDefaultCache, Action>? onProgressAction = null, CancellationToken cancellationToken = default @@ -34,7 +34,7 @@ public partial class Operations throw new ArgumentNullException(nameof(transport), "Expected a transport to be explicitly specified"); } - List transports = new() { transport }; + List transports = new() { transport }; using SQLiteTransport? localCache = useDefaultCache ? new SQLiteTransport { TransportName = "LC" } : null; if (localCache is not null) { @@ -60,7 +60,7 @@ public partial class Operations /// The requested cancellation public async Task<(string rootObjId, IReadOnlyDictionary convertedReferences)> Send( Base value, - IReadOnlyCollection transports, + IReadOnlyCollection transports, Action>? onProgressAction = null, CancellationToken cancellationToken = default ) @@ -92,7 +92,7 @@ public partial class Operations { t.OnProgressAction = internalProgressAction; t.CancellationToken = cancellationToken; - t.BeginWrite(); + await t.BeginWrite().ConfigureAwait(false); } try @@ -128,13 +128,13 @@ public partial class Operations { foreach (var t in transports) { - t.EndWrite(); + await t.EndWrite().ConfigureAwait(false); } } } } - /// + /// internal static async Task SerializerSend( Base value, SpeckleObjectSerializer serializer, diff --git a/src/Speckle.Sdk/Api/Operations/Operations.Serialize.cs b/src/Speckle.Sdk/Api/Operations/Operations.Serialize.cs index 1c2e3cd3..3f134318 100644 --- a/src/Speckle.Sdk/Api/Operations/Operations.Serialize.cs +++ b/src/Speckle.Sdk/Api/Operations/Operations.Serialize.cs @@ -13,7 +13,7 @@ public partial class Operations /// /// If you want to save and persist an object to Speckle Transport or Server, /// please use any of the "Send" methods. - /// + /// /// /// The object to serialise /// diff --git a/src/Speckle.Sdk/Models/Base.cs b/src/Speckle.Sdk/Models/Base.cs index b6856b7d..0d051b5a 100644 --- a/src/Speckle.Sdk/Models/Base.cs +++ b/src/Speckle.Sdk/Models/Base.cs @@ -73,7 +73,7 @@ public virtual string speckle_type public string GetId(bool decompose = false) { //TODO remove me - var transports = decompose ? [new MemoryTransport()] : Array.Empty(); + var transports = decompose ? [new MemoryTransport()] : Array.Empty(); var serializer = new SpeckleObjectSerializer(transports); string obj = serializer.Serialize(this); diff --git a/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer.cs b/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer.cs index c3f04549..320e9197 100644 --- a/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer.cs +++ b/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer.cs @@ -33,7 +33,7 @@ public class SpeckleObjectSerializer public Dictionary ObjectReferences { get; } = new(); /// The sync transport. This transport will be used synchronously. - public IReadOnlyCollection WriteTransports { get; } + public IReadOnlyCollection WriteTransports { get; } public CancellationToken CancellationToken { get; set; } @@ -41,7 +41,7 @@ public class SpeckleObjectSerializer public TimeSpan Elapsed => _stopwatch.Elapsed; public SpeckleObjectSerializer() - : this(Array.Empty()) { } + : this(Array.Empty()) { } /// /// Creates a new Serializer instance. @@ -51,7 +51,7 @@ public SpeckleObjectSerializer() /// Whether to store all detachable objects while serializing. They can be retrieved via post serialization. /// public SpeckleObjectSerializer( - IReadOnlyCollection writeTransports, + IReadOnlyCollection writeTransports, Action? onProgressAction = null, bool trackDetachedChildren = false, CancellationToken cancellationToken = default diff --git a/src/Speckle.Sdk/Transports/ITransport.cs b/src/Speckle.Sdk/Transports/ITransport.cs index e55d76ac..e1df0fc2 100644 --- a/src/Speckle.Sdk/Transports/ITransport.cs +++ b/src/Speckle.Sdk/Transports/ITransport.cs @@ -109,3 +109,52 @@ public interface IBlobCapableTransport // NOTE: not needed, should be implemented in "CopyObjectsAndChildren" //public void GetBlob(Blob obj); } + +public interface IWritableTransport +{ + + /// + /// Show how much time the transport was busy for. + /// + public TimeSpan Elapsed { get; } + /// + /// Human readable name for the transport + /// + public string TransportName { get; set; } + + + /// + /// Should be checked often and gracefully stop all in progress sending if requested. + /// + public CancellationToken CancellationToken { get; set; } + + /// + /// Used to report progress during the transport's longer operations. + /// + public Action? OnProgressAction { get; set; } + + /// + /// Signals to the transport that writes are about to begin. + /// + public Task BeginWrite(); + + /// + /// Signals to the transport that no more items will need to be written. + /// + public Task EndWrite(); + + /// + /// Saves an object. + /// + /// The hash of the object. + /// The full string representation of the object + /// Failed to save object + /// requested cancel + public Task SaveObject(string id, string serializedObject); + + /// + /// Awaitable method to figure out whether writing is completed. + /// + /// + public Task WriteComplete(); +} diff --git a/src/Speckle.Sdk/Transports/MemoryTransport.cs b/src/Speckle.Sdk/Transports/MemoryTransport.cs index 3d36aa49..ee6332f5 100644 --- a/src/Speckle.Sdk/Transports/MemoryTransport.cs +++ b/src/Speckle.Sdk/Transports/MemoryTransport.cs @@ -8,7 +8,7 @@ namespace Speckle.Sdk.Transports; /// /// An in memory storage of speckle objects. /// -public sealed class MemoryTransport : ITransport, ICloneable, IBlobCapableTransport +public sealed class MemoryTransport : ITransport, ICloneable, IBlobCapableTransport, IWritableTransport { private readonly string _basePath; private readonly string _applicationName; @@ -50,6 +50,23 @@ public object Clone() }; } + Task IWritableTransport.EndWrite() + { + EndWrite(); + return Task.CompletedTask; + } + + Task IWritableTransport.SaveObject(string id, string serializedObject) + { + SaveObject(id, serializedObject); + return Task.CompletedTask; + } + Task IWritableTransport.BeginWrite() + { + BeginWrite(); + return Task.CompletedTask; + } + public CancellationToken CancellationToken { get; set; } public string TransportName { get; set; } = "Memory"; diff --git a/src/Speckle.Sdk/Transports/SQLiteTransport.cs b/src/Speckle.Sdk/Transports/SQLiteTransport.cs index f8d40201..745fc1b6 100644 --- a/src/Speckle.Sdk/Transports/SQLiteTransport.cs +++ b/src/Speckle.Sdk/Transports/SQLiteTransport.cs @@ -9,7 +9,7 @@ namespace Speckle.Sdk.Transports; -public sealed class SQLiteTransport : IDisposable, ICloneable, ITransport, IBlobCapableTransport +public sealed class SQLiteTransport : IDisposable, ICloneable, ITransport, IBlobCapableTransport, IWritableTransport { private bool _isWriting; private const int MAX_TRANSACTION_SIZE = 1000; @@ -128,6 +128,23 @@ public void BeginWrite() SavedObjectCount = 0; } + Task IWritableTransport.EndWrite() + { + EndWrite(); + return Task.CompletedTask; + } + + Task IWritableTransport.SaveObject(string id, string serializedObject) + { + SaveObject(id, serializedObject); + return Task.CompletedTask; + } + Task IWritableTransport.BeginWrite() + { + BeginWrite(); + return Task.CompletedTask; + } + public void EndWrite() { } public Task> HasObjects(IReadOnlyList objectIds) diff --git a/src/Speckle.Sdk/Transports/ServerTransport.cs b/src/Speckle.Sdk/Transports/ServerTransport.cs index 71e629ec..439cc782 100644 --- a/src/Speckle.Sdk/Transports/ServerTransport.cs +++ b/src/Speckle.Sdk/Transports/ServerTransport.cs @@ -9,7 +9,7 @@ namespace Speckle.Sdk.Transports; -public sealed class ServerTransport : IServerTransport +public sealed class ServerTransport : IServerTransport, IWritableTransport { private readonly ISpeckleHttp _http; private readonly ISdkActivityFactory _activityFactory; @@ -217,6 +217,18 @@ public async Task> HasObjects(IReadOnlyList obj return await Api.HasObjects(StreamId, objectIds).ConfigureAwait(false); } + Task IWritableTransport.EndWrite() + { + EndWrite(); + return Task.CompletedTask; + } + + Task IWritableTransport.SaveObject(string id, string serializedObject) + { + SaveObject(id, serializedObject); + return Task.CompletedTask; + } + public void SaveObject(string id, string serializedObject) { lock (_sendBufferLock) @@ -267,6 +279,12 @@ public async Task WriteComplete() } } + Task IWritableTransport.BeginWrite() + { + BeginWrite(); + return Task.CompletedTask; + } + public void EndWrite() { if (!_shouldSendThreadRun || _sendingThread == null) diff --git a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs index 97a6e6f4..74614b83 100644 --- a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs +++ b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs @@ -51,7 +51,7 @@ public string RunTest() } } -public class NullTransport : ITransport +public class NullTransport : ITransport, IWritableTransport { public string TransportName { get; set; } = ""; public Dictionary TransportContext { get; } = new(); @@ -69,6 +69,23 @@ public Task WriteComplete() { return Task.CompletedTask; } + + Task IWritableTransport.EndWrite() + { + EndWrite(); + return Task.CompletedTask; + } + + Task IWritableTransport.SaveObject(string id, string serializedObject) + { + SaveObject(id, serializedObject); + return Task.CompletedTask; + } + Task IWritableTransport.BeginWrite() + { + BeginWrite(); + return Task.CompletedTask; + } public Task GetObject(string id) => throw new NotImplementedException();