diff --git a/src/Speckle.Sdk/Api/Blob/BlobApi.cs b/src/Speckle.Sdk/Api/Blob/BlobApi.cs index 1dd68eb5..c9a17b8e 100644 --- a/src/Speckle.Sdk/Api/Blob/BlobApi.cs +++ b/src/Speckle.Sdk/Api/Blob/BlobApi.cs @@ -19,7 +19,6 @@ public partial interface IBlobApi : IDisposable; /// Low level access to the blob API /// /// -/// [GenerateAutoInterface] public sealed class BlobApi : IBlobApi { diff --git a/src/Speckle.Sdk/Api/Operations/Operations.Receive.cs b/src/Speckle.Sdk/Api/Operations/Operations.Receive.cs index b75db9fc..03fc5db4 100644 --- a/src/Speckle.Sdk/Api/Operations/Operations.Receive.cs +++ b/src/Speckle.Sdk/Api/Operations/Operations.Receive.cs @@ -1,7 +1,5 @@ -using Microsoft.Extensions.Logging; using Speckle.Sdk.Logging; using Speckle.Sdk.Models; -using Speckle.Sdk.Serialisation; using Speckle.Sdk.Transports; namespace Speckle.Sdk.Api; @@ -55,173 +53,4 @@ CancellationToken cancellationToken await process.DisposeAsync().ConfigureAwait(false); } } - - /// - /// Receives an object (and all its sub-children) from the two provided s. - ///
- /// Will first try and find objects using the (the faster transport) - /// If not found, will attempt to copy the objects from the into the before deserialization - ///
- /// - /// If Transports are properly implemented, there is no hard distinction between what is a local or remote transport; it's still just an . - ///
So, for example, if you want to receive an object without actually writing it first to a local transport, you can just pass a as a local transport. - ///
This is not recommended, but shows what you can do. Another tidbit: the local transport does not need to be disk-bound; it can easily be an in . In memory transports are the fastest ones, but they're of limited use for larger datasets - ///
- /// The id of the object to receive - /// The remote transport (slower). If , will assume all objects are present in - /// The local transport (faster). If , will use a default cache - /// Action invoked on progress iterations - /// - /// Failed to retrieve objects from the provided transport(s) - /// Deserialization of the requested object(s) failed - /// requested cancel - /// The requested Speckle Object - public async Task Receive( - string objectId, - ITransport? remoteTransport = null, - ITransport? localTransport = null, - IProgress? onProgressAction = null, - CancellationToken cancellationToken = default - ) - { - using var receiveActivity = activityFactory.Start("Operations.Receive"); - metricsFactory.CreateCounter("Receive").Add(1); - - if (remoteTransport != null) - { - receiveActivity?.SetTags("remoteTransportContext", remoteTransport.TransportContext); - } - receiveActivity?.SetTag("objectId", objectId); - - try - { - using IDisposable? d1 = UseDefaultTransportIfNull(localTransport, out localTransport); - receiveActivity?.SetTags("localTransportContext", localTransport.TransportContext); - - var result = await ReceiveImpl(objectId, remoteTransport, localTransport, onProgressAction, cancellationToken) - .ConfigureAwait(false); - - receiveActivity?.SetStatus(SdkActivityStatusCode.Ok); - return result; - } - catch (Exception ex) - { - receiveActivity?.SetStatus(SdkActivityStatusCode.Error); - receiveActivity?.RecordException(ex); - throw; - } - } - - /// - private async Task ReceiveImpl( - string objectId, - ITransport? remoteTransport, - ITransport localTransport, - IProgress? internalProgressAction, - CancellationToken cancellationToken - ) - { - // Setup Local Transport - localTransport.OnProgressAction = internalProgressAction; - localTransport.CancellationToken = cancellationToken; - - // Setup Remote Transport - if (remoteTransport is not null) - { - remoteTransport.OnProgressAction = internalProgressAction; - remoteTransport.CancellationToken = cancellationToken; - } - - // Setup Serializer - SpeckleObjectDeserializer serializer = new() - { - ReadTransport = localTransport, - OnProgressAction = internalProgressAction, - CancellationToken = cancellationToken, - BlobStorageFolder = (remoteTransport as IBlobCapableTransport)?.BlobStorageFolder, - }; - - // Try Local Receive - string? objString = await LocalReceive(objectId, localTransport).ConfigureAwait(false); - - if (objString is null) - { - // Fall back to remote - if (remoteTransport is null) - { - throw new TransportException( - $"Could not find specified object using the local transport {localTransport.TransportName}, and you didn't provide a fallback remote from which to pull it." - ); - } - - logger.LogDebug( - "Cannot find object {objectId} in the local transport, hitting remote {transportName}", - objectId, - remoteTransport.TransportName - ); - - objString = await RemoteReceive(objectId, remoteTransport, localTransport).ConfigureAwait(false); - } - - using var serializerActivity = activityFactory.Start(); - - // Proceed to deserialize the object, now safely knowing that all its children are present in the local (fast) transport. - return await DeserializeActivity(objString, serializer).ConfigureAwait(false); - } - - /// - /// Try and get the object from the local transport. If it's there, we assume all its children are there - /// This assumption is hard-wired into the - /// - /// - /// - /// - /// - internal static async Task LocalReceive(string objectId, ITransport localTransport) - { - string? objString = await localTransport.GetObject(objectId).ConfigureAwait(false); - if (objString is null) - { - return null; - } - return objString; - } - - /// - /// Copies the requested object and all its children from to - /// - /// - /// - /// - /// - /// - /// Remote transport was not specified - private static async Task RemoteReceive( - string objectId, - ITransport remoteTransport, - ITransport localTransport - ) - { - var objString = await remoteTransport.CopyObjectAndChildren(objectId, localTransport).ConfigureAwait(false); - - // DON'T THINK THIS IS NEEDED CopyObjectAndChildren should call this - // Wait for the local transport to finish "writing" - in this case, it signifies that the remote transport has done pushing copying objects into it. (TODO: I can see some scenarios where latency can screw things up, and we should rather wait on the remote transport). - await localTransport.WriteComplete().ConfigureAwait(false); - - return objString; - } - - private static IDisposable? UseDefaultTransportIfNull(ITransport? userTransport, out ITransport actualLocalTransport) - { - if (userTransport is not null) - { - actualLocalTransport = userTransport; - return null; - } - - //User did not specify a transport, default to SQLite - SQLiteTransport defaultLocalTransport = new(); - actualLocalTransport = defaultLocalTransport; - return defaultLocalTransport; - } } diff --git a/src/Speckle.Sdk/Api/Operations/Operations.Send.cs b/src/Speckle.Sdk/Api/Operations/Operations.Send.cs index eaad0b18..d48d7526 100644 --- a/src/Speckle.Sdk/Api/Operations/Operations.Send.cs +++ b/src/Speckle.Sdk/Api/Operations/Operations.Send.cs @@ -1,9 +1,5 @@ -using System.Diagnostics; -using Microsoft.Extensions.Logging; -using Speckle.Newtonsoft.Json.Linq; using Speckle.Sdk.Logging; using Speckle.Sdk.Models; -using Speckle.Sdk.Serialisation; using Speckle.Sdk.Serialisation.V2.Send; using Speckle.Sdk.Transports; @@ -63,186 +59,4 @@ CancellationToken cancellationToken await process.DisposeAsync().ConfigureAwait(false); } } - - /// - /// Sends a Speckle Object to the provided and (optionally) the default local cache - /// - /// - /// - /// When , an additional will be included - /// The or was - /// - /// using ServerTransport destination = new(account, streamId); - /// var (objectId, references) = await Send(mySpeckleObject, destination, true); - /// - public async Task<(string rootObjId, IReadOnlyDictionary convertedReferences)> Send( - Base value, - IServerTransport transport, - bool useDefaultCache, - IProgress? onProgressAction = null, - CancellationToken cancellationToken = default - ) - { - if (transport is null) - { - throw new ArgumentNullException(nameof(transport), "Expected a transport to be explicitly specified"); - } - - List transports = new() { transport }; - using SQLiteTransport2? localCache = useDefaultCache - ? new SQLiteTransport2(transport.StreamId) { TransportName = "LC2" } - : null; - if (localCache is not null) - { - transports.Add(localCache); - } - - return await Send(value, transports, onProgressAction, cancellationToken).ConfigureAwait(false); - } - - /// - /// Sends a Speckle Object to the provided and (optionally) the default local cache - /// - /// - /// - /// When , an additional will be included - /// The or was - /// - /// using ServerTransport destination = new(account, streamId); - /// var (objectId, references) = await Send(mySpeckleObject, destination, true); - /// - public async Task<(string rootObjId, IReadOnlyDictionary convertedReferences)> Send( - Base value, - ITransport transport, - bool useDefaultCache, - IProgress? onProgressAction = null, - CancellationToken cancellationToken = default - ) - { - if (transport is null) - { - throw new ArgumentNullException(nameof(transport), "Expected a transport to be explicitly specified"); - } - - List transports = new() { transport }; - using SQLiteTransport? localCache = useDefaultCache ? new SQLiteTransport { TransportName = "LC" } : null; - if (localCache is not null) - { - transports.Add(localCache); - } - - return await Send(value, transports, onProgressAction, cancellationToken).ConfigureAwait(false); - } - - /// - /// Sends a Speckle Object to the provided - /// - /// Only sends to the specified transports, the default local cache won't be used unless you also pass it in - /// The id (hash) of the object sent - /// The object you want to send - /// Where you want to send them - /// Action that gets triggered on every progress tick (keeps track of all transports) - /// - /// No transports were specified - /// The was - /// Serialization or Send operation was unsuccessful - /// One or more failed to send - /// The requested cancellation - public async Task<(string rootObjId, IReadOnlyDictionary convertedReferences)> Send( - Base value, - IReadOnlyCollection transports, - IProgress? onProgressAction = null, - CancellationToken cancellationToken = default - ) - { -#pragma warning disable CA1510 - if (value is null) - { - throw new ArgumentNullException(nameof(value)); - } -#pragma warning restore CA1510 - - if (transports.Count == 0) - { - throw new ArgumentException("Expected at least on transport to be specified", nameof(transports)); - } - - // make sure all logs in the operation have the proper context - metricsFactory.CreateCounter("Send").Add(1); - using var activity = activityFactory.Start(); - activity?.SetTag("correlationId", Guid.NewGuid().ToString()); - { - var sendTimer = Stopwatch.StartNew(); - logger.LogDebug("Starting send operation"); - - SpeckleObjectSerializer serializerV2 = new(transports, onProgressAction, true, cancellationToken); - - foreach (var t in transports) - { - t.OnProgressAction = onProgressAction; - t.CancellationToken = cancellationToken; - t.BeginWrite(); - } - - try - { - var rootObjectId = await SerializerSend(value, serializerV2, cancellationToken).ConfigureAwait(false); - - sendTimer.Stop(); - activity?.SetTags("transportElapsedBreakdown", transports.ToDictionary(t => t.TransportName, t => t.Elapsed)); - activity?.SetTag( - "note", - "the elapsed summary doesn't need to add up to the total elapsed... Threading magic..." - ); - activity?.SetTag("serializerElapsed", serializerV2.Elapsed); - logger.LogDebug( - "Finished sending objects after {elapsed}, result {objectId}", - sendTimer.Elapsed.TotalSeconds, - rootObjectId - ); - - return (rootObjectId, serializerV2.ObjectReferences); - } - catch (Exception ex) when (!ex.IsFatal()) - { - logger.LogInformation(ex, "Send operation failed after {elapsed} seconds", sendTimer.Elapsed.TotalSeconds); - if (ex is OperationCanceledException or SpeckleException) - { - throw; - } - - throw new SpeckleException("Send operation was unsuccessful", ex); - } - finally - { - foreach (var t in transports) - { - t.EndWrite(); - } - } - } - } - - /// - internal static async Task SerializerSend( - Base value, - SpeckleObjectSerializer serializer, - CancellationToken cancellationToken = default - ) - { - string obj = serializer.Serialize(value); - Task[] transportAwaits = serializer.WriteTransports.Select(t => t.WriteComplete()).ToArray(); - - cancellationToken.ThrowIfCancellationRequested(); - - await Task.WhenAll(transportAwaits).ConfigureAwait(false); - - JToken? idToken = JObject.Parse(obj).GetValue("id"); - if (idToken == null) - { - throw new SpeckleException("Failed to get id of serialized object"); - } - - return idToken.ToString(); - } } diff --git a/src/Speckle.Sdk/Api/Operations/Operations.Serialize.cs b/src/Speckle.Sdk/Api/Operations/Operations.Serialize.cs index 075776f2..ce189cc0 100644 --- a/src/Speckle.Sdk/Api/Operations/Operations.Serialize.cs +++ b/src/Speckle.Sdk/Api/Operations/Operations.Serialize.cs @@ -1,64 +1,27 @@ -using System.Diagnostics.CodeAnalysis; -using Speckle.Newtonsoft.Json; -using Speckle.Sdk.Logging; +using System.Collections.Concurrent; using Speckle.Sdk.Models; using Speckle.Sdk.Serialisation; -using Speckle.Sdk.Transports; namespace Speckle.Sdk.Api; public partial class Operations { - /// - /// Serializes a given object. - /// - /// - /// 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 - /// - /// A json string representation of the object. - public string Serialize(Base value, CancellationToken cancellationToken = default) - { - var serializer = new SpeckleObjectSerializer { CancellationToken = cancellationToken }; - return serializer.Serialize(value); - } + private static readonly Id s_emptyId = new(Guid.NewGuid().ToString()); - /// - /// Note: if you want to pull an object from a Speckle Transport or Server, - /// please use - /// - /// - /// The json string representation of a speckle object that you want to deserialize - /// - /// - /// was null - /// was not valid JSON - /// cannot be deserialised to type - /// contains closure references (see Remarks) - public async Task DeserializeAsync(string value, CancellationToken cancellationToken = default) + public string Serialize(Base value, CancellationToken cancellationToken = default) { - var deserializer = new SpeckleObjectDeserializer { CancellationToken = cancellationToken }; - return await DeserializeActivity(value, deserializer).ConfigureAwait(false); + using var serializer2 = objectSerializerFactory.Create(cancellationToken); + var items = serializer2.Serialize(value); + return items.First().Item2.Value; } - /// - private async Task DeserializeActivity([NotNull] string? objString, SpeckleObjectDeserializer deserializer) + public Task DeserializeAsync(string value, CancellationToken cancellationToken = default) { - using var activity = activityFactory.Start(); - try - { - Base res = await deserializer.DeserializeAsync(objString).ConfigureAwait(false); - activity?.SetStatus(SdkActivityStatusCode.Ok); - return res; - } - catch (Exception ex) - { - activity?.SetStatus(SdkActivityStatusCode.Error); - activity?.RecordException(ex); - throw; - } + var deserializer = objectDeserializerFactory.Create( + s_emptyId, + new List(), + new ConcurrentDictionary() + ); + return Task.FromResult(deserializer.Deserialize(new(value), cancellationToken)); } } diff --git a/src/Speckle.Sdk/Api/Operations/Operations.cs b/src/Speckle.Sdk/Api/Operations/Operations.cs index cc5aea13..d2e0ce10 100644 --- a/src/Speckle.Sdk/Api/Operations/Operations.cs +++ b/src/Speckle.Sdk/Api/Operations/Operations.cs @@ -1,7 +1,8 @@ -using Microsoft.Extensions.Logging; using Speckle.InterfaceGenerator; using Speckle.Sdk.Logging; using Speckle.Sdk.Serialisation.V2; +using Speckle.Sdk.Serialisation.V2.Receive; +using Speckle.Sdk.Serialisation.V2.Send; namespace Speckle.Sdk.Api; @@ -12,9 +13,10 @@ namespace Speckle.Sdk.Api; /// [GenerateAutoInterface] public partial class Operations( - ILogger logger, ISdkActivityFactory activityFactory, ISdkMetricsFactory metricsFactory, + IObjectSerializerFactory objectSerializerFactory, + IObjectDeserializerFactory objectDeserializerFactory, ISerializeProcessFactory serializeProcessFactory, IDeserializeProcessFactory deserializeProcessFactory ) : IOperations; diff --git a/src/Speckle.Sdk/Caching/ModelCacheManager.cs b/src/Speckle.Sdk/Caching/ModelCacheManager.cs index fbbe0688..289ddbe3 100644 --- a/src/Speckle.Sdk/Caching/ModelCacheManager.cs +++ b/src/Speckle.Sdk/Caching/ModelCacheManager.cs @@ -1,7 +1,6 @@ using Microsoft.Extensions.Logging; using Speckle.InterfaceGenerator; using Speckle.Sdk.Logging; -using Speckle.Sdk.Transports; namespace Speckle.Sdk.Caching; @@ -29,7 +28,7 @@ public static string GetDbPath(string streamId) catch (Exception ex) when (ex is ArgumentException or IOException or UnauthorizedAccessException or NotSupportedException) { - throw new TransportException($"Path was invalid or could not be created {db}", ex); + throw new ModelManagerException($"Path was invalid or could not be created {db}", ex); } } @@ -57,7 +56,7 @@ public void ClearCache() catch (Exception ex) when (ex is ArgumentException or IOException or UnauthorizedAccessException or NotSupportedException) { - throw new TransportException($"Cache folder could not be cleared: {CacheFolder}", ex); + throw new ModelManagerException($"Cache folder could not be cleared: {CacheFolder}", ex); } } @@ -87,7 +86,7 @@ public long GetCacheSize() catch (Exception ex) when (ex is ArgumentException or IOException or UnauthorizedAccessException or NotSupportedException) { - throw new TransportException($"Cache folder size could not be determined: {CacheFolder}", ex); + throw new ModelManagerException($"Cache folder size could not be determined: {CacheFolder}", ex); } } } diff --git a/src/Speckle.Sdk/Caching/ModelManagerException.cs b/src/Speckle.Sdk/Caching/ModelManagerException.cs new file mode 100644 index 00000000..499fd8bf --- /dev/null +++ b/src/Speckle.Sdk/Caching/ModelManagerException.cs @@ -0,0 +1,12 @@ +namespace Speckle.Sdk.Caching; + +public class ModelManagerException : SpeckleException +{ + public ModelManagerException() { } + + public ModelManagerException(string message) + : base(message) { } + + public ModelManagerException(string message, Exception inner) + : base(message, inner) { } +} diff --git a/src/Speckle.Sdk/Models/Base.cs b/src/Speckle.Sdk/Models/Base.cs index 1065e6b0..2978d152 100644 --- a/src/Speckle.Sdk/Models/Base.cs +++ b/src/Speckle.Sdk/Models/Base.cs @@ -2,11 +2,8 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; using Speckle.Newtonsoft.Json; -using Speckle.Newtonsoft.Json.Linq; using Speckle.Sdk.Helpers; using Speckle.Sdk.Host; -using Speckle.Sdk.Serialisation; -using Speckle.Sdk.Transports; namespace Speckle.Sdk.Models; @@ -25,7 +22,7 @@ public class Base : DynamicBase, ISpeckleObject private string? _type; /// - /// A speckle object's id is an unique hash based on its properties. NOTE: this field will be null unless the object was deserialised from a source. Use the function to get it. + /// A speckle object's id is an unique hash based on its properties. NOTE: this field will be null unless the object was deserialised from a source. /// public virtual string? id { get; set; } @@ -50,26 +47,6 @@ public virtual string speckle_type } } - /// - /// Calculates the id (a unique hash) of this object. - /// - /// - /// This method fully serialize the object and any referenced objects. This has a tangible cost and should be avoided.
- /// Objects retrieved from a already have a property populated
- /// The hash of a decomposed object differs from the hash of a non-decomposed object. - ///
- /// If , will decompose the object in the process of hashing. - /// the resulting id (hash) - public string GetId(bool decompose = false) - { - //TODO remove me - var transports = decompose ? [new MemoryTransport()] : Array.Empty(); - var serializer = new SpeckleObjectSerializer(transports); - - string obj = serializer.Serialize(this); - return JObject.Parse(obj).GetValue(nameof(id))?.ToString() ?? string.Empty; - } - /// /// Attempts to count the total number of detachable objects. /// diff --git a/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs b/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs deleted file mode 100644 index 549e8a34..00000000 --- a/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs +++ /dev/null @@ -1,334 +0,0 @@ -using System.Collections.Concurrent; -using System.Diagnostics.CodeAnalysis; -using System.Numerics; -using System.Reflection; -using Speckle.Newtonsoft.Json; -using Speckle.Sdk.Common; -using Speckle.Sdk.Host; -using Speckle.Sdk.Models; -using Speckle.Sdk.Serialisation.Utilities; -using Speckle.Sdk.Transports; - -namespace Speckle.Sdk.Serialisation; - -public sealed class SpeckleObjectDeserializer -{ - private volatile bool _isBusy; - private readonly object?[] _invokeNull = [null]; - - // id -> Base if already deserialized or id -> ValueTask if was handled by a bg thread - private readonly ConcurrentDictionary _deserializedObjects = new(StringComparer.Ordinal); - private long _total; - - /// - /// Property that describes the type of the object. - /// - private const string TYPE_DISCRIMINATOR = nameof(Base.speckle_type); - - public CancellationToken CancellationToken { get; set; } - - /// - /// The sync transport. This transport will be used synchronously. - /// - public ITransport ReadTransport { get; set; } - - public IProgress? OnProgressAction { get; set; } - - public string? BlobStorageFolder { get; set; } - - /// The JSON string of the object to be deserialized - /// A typed object deserialized from the - /// Thrown when - /// was null - /// cannot be deserialised to type - // /// did not contain the required json objects (closures) - public async ValueTask DeserializeAsync([NotNull] string? rootObjectJson) - { - if (_isBusy) - { - throw new InvalidOperationException( - "A deserializer instance can deserialize only 1 object at a time. Consider creating multiple deserializer instances" - ); - } - - try - { - if (rootObjectJson is null) - { - throw new ArgumentNullException( - nameof(rootObjectJson), - $"Cannot deserialize {nameof(rootObjectJson)}, value was null" - ); - } - - _isBusy = true; - - var result = (Base)await DeserializeJsonAsyncInternal(rootObjectJson).NotNull().ConfigureAwait(false); - return result; - } - finally - { - _isBusy = false; - } - } - - private async ValueTask DeserializeJsonAsyncInternal(string objectJson) - { - // Apparently this automatically parses DateTimes in strings if it matches the format: - // JObject doc1 = JObject.Parse(objectJson); - - // This is equivalent code that doesn't parse datetimes: - using JsonTextReader reader = SpeckleObjectSerializerPool.Instance.GetJsonTextReader(new StringReader(objectJson)); - reader.DateParseHandling = DateParseHandling.None; - - object? converted; - try - { - reader.Read(); - converted = await ReadObjectAsync(reader, CancellationToken).ConfigureAwait(false); - } - catch (Exception ex) when (!ex.IsFatal() && ex is not OperationCanceledException) - { - throw new SpeckleDeserializeException("Failed to deserialize", ex); - } - - OnProgressAction?.Report(new ProgressArgs(ProgressEvent.DeserializeObject, _deserializedObjects.Count, _total)); - - return converted; - } - - //this should be buffered - private async ValueTask> ReadArrayAsync(JsonReader reader, CancellationToken ct) - { - reader.Read(); - List retList = new(); - while (reader.TokenType != JsonToken.EndArray) - { - object? convertedValue = await ReadPropertyAsync(reader, ct).ConfigureAwait(false); - if (convertedValue is DataChunk chunk) - { - retList.AddRange(chunk.data); - } - else - { - retList.Add(convertedValue); - } - reader.Read(); //goes to next - } - return retList; - } - - private async ValueTask ReadObjectAsync(JsonReader reader, CancellationToken ct) - { - reader.Read(); - Dictionary dict = new(); - while (reader.TokenType != JsonToken.EndObject) - { - switch (reader.TokenType) - { - case JsonToken.PropertyName: - { - string propName = (reader.Value?.ToString()).NotNull(); - if (propName == "__closure") - { - reader.Read(); //goes to prop value - var closures = ClosureParser.GetClosures(reader, CancellationToken); - if (closures.Any()) - { - _total = 0; - foreach (var closure in closures) - { - string objId = closure.Item1; - //don't do anything with return value but later check if null - // https://linear.app/speckle/issue/CXPLA-54/when-deserializing-dont-allow-closures-that-arent-downloadable - await TryGetDeserializedAsync(objId).ConfigureAwait(false); - } - } - - reader.Read(); //goes to next - continue; - } - reader.Read(); //goes prop value - object? convertedValue = await ReadPropertyAsync(reader, ct).ConfigureAwait(false); - dict[propName] = convertedValue; - reader.Read(); //goes to next - } - break; - default: - throw new InvalidOperationException($"Unknown {reader.ValueType} with {reader.Value}"); - } - } - - if (!dict.TryGetValue(TYPE_DISCRIMINATOR, out object? speckleType)) - { - return dict; - } - - if (speckleType as string == "reference" && dict.TryGetValue("referencedId", out object? referencedId)) - { - var objId = (string)referencedId.NotNull(); - object? deserialized = await TryGetDeserializedAsync(objId).ConfigureAwait(false); - return deserialized; - } - - return Dict2Base(dict); - } - - private async ValueTask TryGetDeserializedAsync(string objId) - { - object? deserialized = null; - _deserializedObjects.NotNull(); - if (_deserializedObjects.TryGetValue(objId, out object? o)) - { - deserialized = o; - } - - if (deserialized is Task task) - { - try - { - deserialized = await task.ConfigureAwait(false); - } - catch (AggregateException ex) - { - throw new SpeckleDeserializeException("Failed to deserialize reference object", ex); - } - - _deserializedObjects.TryAdd(objId, deserialized); - } - if (deserialized is ValueTask valueTask) - { - try - { - deserialized = await valueTask.ConfigureAwait(false); - } - catch (AggregateException ex) - { - throw new SpeckleDeserializeException("Failed to deserialize reference object", ex); - } - - _deserializedObjects.TryAdd(objId, deserialized); - } - - if (deserialized != null) - { - return deserialized; - } - - // This reference was not already deserialized. Do it now in sync mode - string? objectJson = await ReadTransport.GetObject(objId).ConfigureAwait(false); - if (objectJson is null) - { - return null; - } - - deserialized = await DeserializeJsonAsyncInternal(objectJson).ConfigureAwait(false); - - _deserializedObjects.TryAdd(objId, deserialized); - - return deserialized; - } - - private async ValueTask ReadPropertyAsync(JsonReader reader, CancellationToken ct) - { - ct.ThrowIfCancellationRequested(); - switch (reader.TokenType) - { - case JsonToken.Undefined: - case JsonToken.Null: - case JsonToken.None: - return null; - case JsonToken.Boolean: - return (bool)reader.Value.NotNull(); - case JsonToken.Integer: - if (reader.Value is long longValue) - { - return longValue; - } - if (reader.Value is BigInteger bitInt) - { - // This is behaviour carried over from v2 to facilitate large numbers from Python - // This is quite hacky, as it's a bit questionable exactly what numbers are supported, and with what tolerance - // For this reason, this can be considered undocumented behaviour, and is only for values within the range of a 64bit integer. - return (double)bitInt; - } - - throw new ArgumentException( - $"Found an unsupported integer type {reader.Value?.GetType()} with value {reader.Value}" - ); - case JsonToken.Float: - return (double)reader.Value.NotNull(); - case JsonToken.String: - return (string?)reader.Value.NotNull(); - case JsonToken.Date: - return (DateTime)reader.Value.NotNull(); - case JsonToken.StartArray: - return await ReadArrayAsync(reader, ct).ConfigureAwait(false); - case JsonToken.StartObject: - var dict = await ReadObjectAsync(reader, ct).ConfigureAwait(false); - return dict; - - default: - throw new ArgumentException("Json value not supported: " + reader.ValueType); - } - } - - private Base Dict2Base(Dictionary dictObj) - { - string typeName = (string)dictObj[TYPE_DISCRIMINATOR].NotNull(); - Type type = TypeLoader.GetType(typeName); - Base baseObj = (Base)Activator.CreateInstance(type).NotNull(); - - dictObj.Remove(TYPE_DISCRIMINATOR); - dictObj.Remove("__closure"); - - var staticProperties = TypeCache.GetTypeProperties(typeName); - foreach (var entry in dictObj) - { - if (staticProperties.TryGetValue(entry.Key, out PropertyInfo? value) && value.CanWrite) - { - if (entry.Value == null) - { - // Check for JsonProperty(NullValueHandling = NullValueHandling.Ignore) attribute - JsonPropertyAttribute? attr = TypeLoader.GetJsonPropertyAttribute(value); - if (attr is { NullValueHandling: NullValueHandling.Ignore }) - { - continue; - } - } - - Type targetValueType = value.PropertyType; - bool conversionOk = ValueConverter.ConvertValue(targetValueType, entry.Value, out object? convertedValue); - if (conversionOk) - { - value.SetValue(baseObj, convertedValue); - } - else - { - // Cannot convert the value in the json to the static property type - throw new SpeckleDeserializeException( - $"Cannot deserialize {entry.Value?.GetType().FullName} to {targetValueType.FullName}" - ); - } - } - else - { - // No writable property with this name - CallSiteCache.SetValue(entry.Key, baseObj, entry.Value); - } - } - - if (baseObj is Blob bb && BlobStorageFolder != null) - { - bb.filePath = bb.GetLocalDestinationPath(BlobStorageFolder); - } - - var onDeserializedCallbacks = TypeCache.GetOnDeserializedCallbacks(typeName); - foreach (MethodInfo onDeserialized in onDeserializedCallbacks) - { - onDeserialized.Invoke(baseObj, _invokeNull); - } - - return baseObj; - } -} diff --git a/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer.cs b/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer.cs deleted file mode 100644 index 618f7e6f..00000000 --- a/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializer.cs +++ /dev/null @@ -1,541 +0,0 @@ -using System.Collections; -using System.Diagnostics; -using System.Drawing; -using System.Globalization; -using System.Reflection; -using Speckle.DoubleNumerics; -using Speckle.Newtonsoft.Json; -using Speckle.Sdk.Common; -using Speckle.Sdk.Dependencies; -using Speckle.Sdk.Helpers; -using Speckle.Sdk.Models; -using Speckle.Sdk.Serialisation.Utilities; -using Speckle.Sdk.Transports; - -namespace Speckle.Sdk.Serialisation; - -public class SpeckleObjectSerializer -{ - private readonly Stopwatch _stopwatch = new(); - private volatile bool _isBusy; - private List> _parentClosures = new(); - private HashSet _parentObjects = new(); - private readonly Dictionary> _typedPropertiesCache = new(); - private readonly IProgress? _onProgressAction; - - private readonly bool _trackDetachedChildren; - private int _serializedCount; - - /// - /// Keeps track of all detached children created during serialisation that have an applicationId (provided this serializer instance has been told to track detached children). - /// This is currently used to cache previously converted objects and avoid their conversion if they haven't changed. See the DUI3 send bindings in rhino or another host app. - /// - public Dictionary ObjectReferences { get; } = new(); - - /// The sync transport. This transport will be used synchronously. - public IReadOnlyCollection WriteTransports { get; } - - public CancellationToken CancellationToken { get; set; } - - /// The current total elapsed time spent serializing - public TimeSpan Elapsed => _stopwatch.Elapsed; - - public SpeckleObjectSerializer() - : this(Array.Empty()) { } - - /// - /// Creates a new Serializer instance. - /// - /// The transports detached children should be persisted to. - /// Used to track progress. - /// Whether to store all detachable objects while serializing. They can be retrieved via post serialization. - /// - public SpeckleObjectSerializer( - IReadOnlyCollection writeTransports, - IProgress? onProgressAction = null, - bool trackDetachedChildren = false, - CancellationToken cancellationToken = default - ) - { - WriteTransports = writeTransports; - _onProgressAction = onProgressAction; - CancellationToken = cancellationToken; - _trackDetachedChildren = trackDetachedChildren; - } - - /// The object to serialize - /// The serialized JSON - /// The serializer is busy (already serializing an object) - /// Failed to save object in one or more - /// Failed to extract (pre-serialize) properties from the - /// One or more 's cancellation token requested cancel - public string Serialize(Base baseObj) - { - if (_isBusy) - { - throw new InvalidOperationException( - "A serializer instance can serialize only 1 object at a time. Consider creating multiple serializer instances" - ); - } - - try - { - _stopwatch.Start(); - _isBusy = true; - try - { - var result = SerializeBase(baseObj, true).NotNull(); - StoreObject(result.Id.NotNull(), result.Json); - return result.Json.Value; - } - catch (Exception ex) when (!ex.IsFatal() && ex is not OperationCanceledException) - { - throw new SpeckleSerializeException($"Failed to extract (pre-serialize) properties from the {baseObj}", ex); - } - } - finally - { - _parentClosures = new List>(); // cleanup in case of exceptions - _parentObjects = new HashSet(); - _isBusy = false; - _stopwatch.Stop(); - } - } - - // `Preserialize` means transforming all objects into the final form that will appear in json, with basic .net objects - // (primitives, lists and dictionaries with string keys) - private void SerializeProperty( - object? obj, - JsonWriter writer, - bool computeClosures = false, - PropertyAttributeInfo inheritedDetachInfo = default - ) - { - CancellationToken.ThrowIfCancellationRequested(); - - if (obj == null) - { - writer.WriteNull(); - return; - } - - if (obj.GetType().IsPrimitive || obj is string) - { - writer.WriteValue(obj); - return; - } - - switch (obj) - { - // Start with object references so they're not captured by the Base class case below - // Note: this change was needed as we've made the ObjectReference type inherit from Base for - // the purpose of the "do not convert unchanged previously converted objects" POC. - case ObjectReference r: - Dictionary ret = new() - { - ["speckle_type"] = r.speckle_type, - ["referencedId"] = r.referencedId, - ["__closure"] = r.closure, - }; - if (r.closure is not null) - { - foreach (var kvp in r.closure) - { - UpdateParentClosures(kvp.Key); - } - } - UpdateParentClosures(r.referencedId); - SerializeProperty(ret, writer); - break; - case Base b: - var result = SerializeBase(b, computeClosures, inheritedDetachInfo); - if (result is not null) - { - writer.WriteRawValue(result.Value.Json.Value); - } - else - { - writer.WriteNull(); - } - break; - case IDictionary d: - { - writer.WriteStartObject(); - - foreach (DictionaryEntry kvp in d) - { - if (kvp.Key is not string key) - { - throw new ArgumentException( - "Serializing dictionaries that are not string based keys is not supported", - nameof(obj) - ); - } - - writer.WritePropertyName(key); - SerializeProperty(kvp.Value, writer, inheritedDetachInfo: inheritedDetachInfo); - } - writer.WriteEndObject(); - } - break; - case ICollection e: - { - writer.WriteStartArray(); - foreach (object? element in e) - { - SerializeProperty(element, writer, inheritedDetachInfo: inheritedDetachInfo); - } - writer.WriteEndArray(); - } - break; - case Enum: - writer.WriteValue((int)obj); - break; - // Support for simple types - case Guid g: - writer.WriteValue(g.ToString()); - break; - case Color c: - writer.WriteValue(c.ToArgb()); - break; - case DateTime t: - writer.WriteValue(t.ToString("o", CultureInfo.InvariantCulture)); - break; - case Matrix4x4 md: - writer.WriteStartArray(); - - writer.WriteValue(md.M11); - writer.WriteValue(md.M12); - writer.WriteValue(md.M13); - writer.WriteValue(md.M14); - writer.WriteValue(md.M21); - writer.WriteValue(md.M22); - writer.WriteValue(md.M23); - writer.WriteValue(md.M24); - writer.WriteValue(md.M31); - writer.WriteValue(md.M32); - writer.WriteValue(md.M33); - writer.WriteValue(md.M34); - writer.WriteValue(md.M41); - writer.WriteValue(md.M42); - writer.WriteValue(md.M43); - writer.WriteValue(md.M44); - writer.WriteEndArray(); - break; - //BACKWARDS COMPATIBILITY: matrix4x4 changed from System.Numerics float to System.DoubleNumerics double in release 2.16 - case System.Numerics.Matrix4x4: - throw new ArgumentException("Please use Speckle.DoubleNumerics.Matrix4x4 instead", nameof(obj)); - default: - throw new ArgumentException($"Unsupported value in serialization: {obj.GetType()}", nameof(obj)); - } - } - - internal SerializationResult? SerializeBase( - Base baseObj, - bool computeClosures = false, - PropertyAttributeInfo inheritedDetachInfo = default - ) - { - // handle circular references - bool alreadySerialized = !_parentObjects.Add(baseObj); - if (alreadySerialized) - { - return null; - } - - Dictionary closure = new(); - if (computeClosures || inheritedDetachInfo.IsDetachable || baseObj is Blob) - { - _parentClosures.Add(closure); - } - - var stringBuilder = Pools.StringBuilders.Get(); - using var writer = new StringWriter(); - using var jsonWriter = SpeckleObjectSerializerPool.Instance.GetJsonTextWriter(writer); - var id = SerializeBaseObject(baseObj, jsonWriter, closure); - var json = new Json(writer.ToString()); - Pools.StringBuilders.Return(stringBuilder); - - if (computeClosures || inheritedDetachInfo.IsDetachable || baseObj is Blob) - { - _parentClosures.RemoveAt(_parentClosures.Count - 1); - } - - _parentObjects.Remove(baseObj); - - if (baseObj is Blob myBlob) - { - StoreBlob(myBlob); - UpdateParentClosures($"blob:{id}"); - return new(json, id); - } - - if (inheritedDetachInfo.IsDetachable && WriteTransports.Count > 0) - { - StoreObject(id, json); - - var json2 = ReferenceGenerator.CreateReference(id); - UpdateParentClosures(id.Value); - - _onProgressAction?.Report(new(ProgressEvent.SerializeObject, ++_serializedCount, null)); - - // add to obj refs to return - if (baseObj.applicationId != null && _trackDetachedChildren) // && baseObj is not DataChunk && baseObj is not Abstract) // not needed, as data chunks will never have application ids, and abstract objs are not really used. - { - ObjectReferences[baseObj.applicationId] = new ObjectReference() - { - referencedId = id.Value, - applicationId = baseObj.applicationId, - closure = closure, - }; - } - return new(json2, null); - } - return new(json, id); - } - - private Dictionary ExtractAllProperties(Base baseObj) - { - IReadOnlyList<(PropertyInfo, PropertyAttributeInfo)> typedProperties = GetTypedPropertiesWithCache(baseObj); - IReadOnlyCollection dynamicProperties = baseObj.DynamicPropertyKeys; - - // propertyName -> (originalValue, isDetachable, isChunkable, chunkSize) - Dictionary allProperties = new( - typedProperties.Count + dynamicProperties.Count - ); - - // Construct `allProperties`: Add typed properties - foreach ((PropertyInfo propertyInfo, PropertyAttributeInfo detachInfo) in typedProperties) - { - object? baseValue = propertyInfo.GetValue(baseObj); - allProperties[propertyInfo.Name] = (baseValue, detachInfo); - } - - // Construct `allProperties`: Add dynamic properties - foreach (string propName in dynamicProperties) - { - if (propName.StartsWith("__")) - { - continue; - } - - object? baseValue = baseObj[propName]; - - bool isDetachable = PropNameValidator.IsDetached(propName); - - int chunkSize = 1000; - bool isChunkable = isDetachable && PropNameValidator.IsChunkable(propName, out chunkSize); - - allProperties[propName] = (baseValue, new PropertyAttributeInfo(isDetachable, isChunkable, chunkSize, null)); - } - - return allProperties; - } - - private Id SerializeBaseObject(Base baseObj, JsonWriter writer, IReadOnlyDictionary closure) - { - var allProperties = ExtractAllProperties(baseObj); - - if (baseObj is not Blob) - { - writer = new SerializerIdWriter(writer); - } - - writer.WriteStartObject(); - // Convert all properties - foreach (var prop in allProperties) - { - if (prop.Value.info.JsonPropertyInfo is { NullValueHandling: NullValueHandling.Ignore }) - { - continue; - } - - writer.WritePropertyName(prop.Key); - SerializeProperty(prop.Value.value, writer, prop.Value.info); - } - - Id id; - if (writer is SerializerIdWriter serializerIdWriter) - { - (var json, writer) = serializerIdWriter.FinishIdWriter(); - id = IdGenerator.ComputeId(json); - } - else - { - id = new Id(((Blob)baseObj).id.NotNull()); - } - writer.WritePropertyName("id"); - writer.WriteValue(id.Value); - baseObj.id = id.Value; - - if (closure.Count > 0) - { - writer.WritePropertyName("__closure"); - writer.WriteStartObject(); - foreach (var c in closure) - { - writer.WritePropertyName(c.Key); - writer.WriteValue(c.Value); - } - writer.WriteEndObject(); - } - - writer.WriteEndObject(); - return id; - } - - private void SerializeProperty(object? baseValue, JsonWriter jsonWriter, PropertyAttributeInfo detachInfo) - { - // If there are no WriteTransports, keep everything attached. - if (WriteTransports.Count == 0) - { - SerializeProperty(baseValue, jsonWriter, inheritedDetachInfo: detachInfo); - return; - } - - if (baseValue is IEnumerable chunkableCollection && detachInfo.IsChunkable) - { - List chunks = new(); - DataChunk crtChunk = new() { data = new List(detachInfo.ChunkSize) }; - - foreach (object element in chunkableCollection) - { - crtChunk.data.Add(element); - if (crtChunk.data.Count >= detachInfo.ChunkSize) - { - chunks.Add(crtChunk); - crtChunk = new DataChunk { data = new List(detachInfo.ChunkSize) }; - } - } - - if (crtChunk.data.Count > 0) - { - chunks.Add(crtChunk); - } - SerializeProperty(chunks, jsonWriter, inheritedDetachInfo: new PropertyAttributeInfo(true, false, 0, null)); - return; - } - - SerializeProperty(baseValue, jsonWriter, inheritedDetachInfo: detachInfo); - } - - private void UpdateParentClosures(string objectId) - { - for (int parentLevel = 0; parentLevel < _parentClosures.Count; parentLevel++) - { - int childDepth = _parentClosures.Count - parentLevel; - if (!_parentClosures[parentLevel].TryGetValue(objectId, out int currentValue)) - { - currentValue = childDepth; - } - - _parentClosures[parentLevel][objectId] = Math.Min(currentValue, childDepth); - } - } - - private void StoreObject(Id objectId, Json objectJson) - { - _stopwatch.Stop(); - foreach (var transport in WriteTransports) - { - transport.SaveObject(objectId.Value, objectJson.Value); - } - - _stopwatch.Start(); - } - - private void StoreBlob(Blob obj) - { - bool hasBlobTransport = false; - - _stopwatch.Stop(); - - foreach (var transport in WriteTransports) - { - if (transport is IBlobCapableTransport blobTransport) - { - hasBlobTransport = true; - blobTransport.SaveBlob(obj); - } - } - - _stopwatch.Start(); - if (!hasBlobTransport) - { - throw new InvalidOperationException( - "Object tree contains a Blob (file), but the serializer has no blob saving capable transports." - ); - } - } - - // (propertyInfo, isDetachable, isChunkable, chunkSize, JsonPropertyAttribute) - private IReadOnlyList<(PropertyInfo, PropertyAttributeInfo)> GetTypedPropertiesWithCache(Base baseObj) - { - Type type = baseObj.GetType(); - - if ( - _typedPropertiesCache.TryGetValue( - type.FullName.NotNull(), - out List<(PropertyInfo, PropertyAttributeInfo)>? cached - ) - ) - { - return cached; - } - - var typedProperties = baseObj.GetInstanceMembers().ToList(); - List<(PropertyInfo, PropertyAttributeInfo)> ret = new(typedProperties.Count); - - foreach (PropertyInfo typedProperty in typedProperties) - { - if (typedProperty.Name.StartsWith("__") || typedProperty.Name == "id") - { - continue; - } - - bool jsonIgnore = typedProperty.IsDefined(typeof(JsonIgnoreAttribute), false); - if (jsonIgnore) - { - continue; - } - - _ = typedProperty.GetValue(baseObj); - - List detachableAttributes = typedProperty - .GetCustomAttributes(true) - .ToList(); - List chunkableAttributes = typedProperty - .GetCustomAttributes(true) - .ToList(); - bool isDetachable = detachableAttributes.Count > 0 && detachableAttributes[0].Detachable; - bool isChunkable = chunkableAttributes.Count > 0; - int chunkSize = isChunkable ? chunkableAttributes[0].MaxObjCountPerChunk : 1000; - JsonPropertyAttribute? jsonPropertyAttribute = typedProperty.GetCustomAttribute(); - ret.Add((typedProperty, new PropertyAttributeInfo(isDetachable, isChunkable, chunkSize, jsonPropertyAttribute))); - } - - _typedPropertiesCache[type.FullName] = ret; - return ret; - } - - internal readonly struct PropertyAttributeInfo - { - public PropertyAttributeInfo( - bool isDetachable, - bool isChunkable, - int chunkSize, - JsonPropertyAttribute? jsonPropertyAttribute - ) - { - IsDetachable = isDetachable || isChunkable; - IsChunkable = isChunkable; - ChunkSize = chunkSize; - JsonPropertyInfo = jsonPropertyAttribute; - } - - public readonly bool IsDetachable; - public readonly bool IsChunkable; - public readonly int ChunkSize; - public readonly JsonPropertyAttribute? JsonPropertyInfo; - } -} diff --git a/src/Speckle.Sdk/ServiceRegistration.cs b/src/Speckle.Sdk/ServiceRegistration.cs index 94e147cc..8d62e0eb 100644 --- a/src/Speckle.Sdk/ServiceRegistration.cs +++ b/src/Speckle.Sdk/ServiceRegistration.cs @@ -12,8 +12,6 @@ using Speckle.Sdk.Serialisation.V2.Receive; using Speckle.Sdk.Serialisation.V2.Send; using Speckle.Sdk.SQLite; -using Speckle.Sdk.Transports; -using Speckle.Sdk.Transports.ServerUtils; namespace Speckle.Sdk; @@ -82,9 +80,7 @@ SpeckleSdkOptions speckleSdkOptions serviceCollection.TryAddSingleton(); serviceCollection.AddMatchingInterfacesAsTransient( Assembly.GetExecutingAssembly(), - typeof(ServerTransport), typeof(Account), - typeof(ServerApi), typeof(SqLiteJsonCacheManager), typeof(ServerObjectManager), typeof(BlobApi), diff --git a/src/Speckle.Sdk/Transports/Exceptions.cs b/src/Speckle.Sdk/Transports/Exceptions.cs deleted file mode 100644 index 90cd81b7..00000000 --- a/src/Speckle.Sdk/Transports/Exceptions.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Speckle.Sdk.Transports; - -public class TransportException : SpeckleException -{ - public ITransport? Transport { get; } - - public TransportException(ITransport? transport, string? message, Exception? innerException = null) - : this(message, innerException) - { - Transport = transport; - } - - public TransportException() { } - - public TransportException(string? message) - : base(message) { } - - public TransportException(string? message, Exception? innerException) - : base(message, innerException) { } -} diff --git a/src/Speckle.Sdk/Transports/IServerTransport.cs b/src/Speckle.Sdk/Transports/IServerTransport.cs deleted file mode 100644 index 41ecb700..00000000 --- a/src/Speckle.Sdk/Transports/IServerTransport.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Speckle.Sdk.Transports; - -public interface IServerTransport : IDisposable, ITransport, IBlobCapableTransport, ICloneable -{ - Credentials.Account Account { get; } - Uri BaseUri { get; } - string StreamId { get; } - int TimeoutSeconds { get; set; } -} diff --git a/src/Speckle.Sdk/Transports/ITransport.cs b/src/Speckle.Sdk/Transports/ITransport.cs deleted file mode 100644 index 6d315966..00000000 --- a/src/Speckle.Sdk/Transports/ITransport.cs +++ /dev/null @@ -1,94 +0,0 @@ -using Speckle.Sdk.Models; - -namespace Speckle.Sdk.Transports; - -/// -/// Interface defining the contract for transport implementations. -/// -public interface ITransport -{ - /// - /// Human readable name for the transport - /// - public string TransportName { get; set; } - - /// - /// Extra descriptor properties of the given transport. - /// - public Dictionary TransportContext { get; } - - /// - /// Show how much time the transport was busy for. - /// - public TimeSpan Elapsed { get; } - - /// - /// 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 IProgress? OnProgressAction { get; set; } - - /// - /// Signals to the transport that writes are about to begin. - /// - public void BeginWrite(); - - /// - /// Signals to the transport that no more items will need to be written. - /// - public void EndWrite(); - - /// - /// Saves an object. - /// - /// The hash of the object. - /// The full string representation of the object - /// Failed to save object - /// requested cancel - public void SaveObject(string id, string serializedObject); - - /// - /// Awaitable method to figure out whether writing is completed. - /// - /// - public Task WriteComplete(); - - /// The object's hash. - /// The serialized object data, or if the transport cannot find the object - /// requested cancel - public Task GetObject(string id); - - /// - /// Copies the parent object and all its children to the provided transport. - /// - /// The id of the object you want to copy. - /// The transport you want to copy the object to. - /// The string representation of the root object. - /// The provided arguments are not valid - /// The transport could not complete the operation - /// requested cancel - public Task CopyObjectAndChildren(string id, ITransport targetTransport); - - /// - /// Checks if objects are present in the transport - /// - /// List of object ids to check - /// A dictionary with the specified object ids as keys and boolean values, whether each object is present in the transport or not - /// The transport could not complete the operation - /// requested cancel - public Task> HasObjects(IReadOnlyList objectIds); -} - -public interface IBlobCapableTransport -{ - public string BlobStorageFolder { get; } - - public void SaveBlob(Blob obj); - - // NOTE: not needed, should be implemented in "CopyObjectsAndChildren" - //public void GetBlob(Blob obj); -} diff --git a/src/Speckle.Sdk/Transports/MemoryTransport.cs b/src/Speckle.Sdk/Transports/MemoryTransport.cs deleted file mode 100644 index cc6aa1d5..00000000 --- a/src/Speckle.Sdk/Transports/MemoryTransport.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System.Collections.Concurrent; -using System.Diagnostics; -using Speckle.Sdk.Logging; -using Speckle.Sdk.Models; - -namespace Speckle.Sdk.Transports; - -/// -/// An in memory storage of speckle objects. -/// -public sealed class MemoryTransport : ITransport, ICloneable, IBlobCapableTransport -{ - private readonly string _basePath; - private readonly string _applicationName; - private readonly bool _blobStorageEnabled; - public IReadOnlyDictionary Objects => _objects; - private readonly ConcurrentDictionary _objects; - - public MemoryTransport( - ConcurrentDictionary? objects = null, - bool blobStorageEnabled = false, - string? basePath = null, - string? applicationName = null - ) - { - _objects = objects ?? new ConcurrentDictionary(StringComparer.Ordinal); - _blobStorageEnabled = blobStorageEnabled; - _basePath = basePath ?? SpecklePathProvider.UserApplicationDataPath(); - _applicationName = applicationName ?? "Speckle"; - var dir = Path.Combine(_basePath, _applicationName); - try - { - Directory.CreateDirectory(dir); //ensure dir is there - } - catch (Exception ex) - when (ex is ArgumentException or IOException or UnauthorizedAccessException or NotSupportedException) - { - throw new TransportException($"Path was invalid or could not be created {dir}", ex); - } - } - - public object Clone() - { - return new MemoryTransport(_objects, _blobStorageEnabled, _basePath, _applicationName) - { - TransportName = TransportName, - OnProgressAction = OnProgressAction, - CancellationToken = CancellationToken, - SavedObjectCount = SavedObjectCount, - }; - } - - public CancellationToken CancellationToken { get; set; } - - public string TransportName { get; set; } = "Memory"; - - public IProgress? OnProgressAction { get; set; } - - public int SavedObjectCount { get; private set; } - - public Dictionary TransportContext => - new() - { - { "name", TransportName }, - { "type", GetType().Name }, - { "basePath", _basePath }, - { "applicationName", _applicationName }, - { "blobStorageFolder", BlobStorageFolder }, - }; - - public TimeSpan Elapsed { get; private set; } = TimeSpan.Zero; - - public void BeginWrite() - { - SavedObjectCount = 0; - } - - public void EndWrite() { } - - public void SaveObject(string id, string serializedObject) - { - CancellationToken.ThrowIfCancellationRequested(); - var stopwatch = Stopwatch.StartNew(); - - _objects[id] = serializedObject; - - SavedObjectCount++; - stopwatch.Stop(); - Elapsed += stopwatch.Elapsed; - } - - public Task GetObject(string id) - { - var stopwatch = Stopwatch.StartNew(); - var ret = Objects.TryGetValue(id, out string? o) ? o : null; - stopwatch.Stop(); - Elapsed += stopwatch.Elapsed; - return Task.FromResult(ret); - } - - public async Task CopyObjectAndChildren(string id, ITransport targetTransport) - { - string res = await TransportHelpers - .CopyObjectAndChildrenAsync(id, this, targetTransport, CancellationToken) - .ConfigureAwait(false); - return res; - } - - public Task WriteComplete() - { - return Task.CompletedTask; - } - - public Task> HasObjects(IReadOnlyList objectIds) - { - Dictionary ret = new(objectIds.Count); - foreach (string objectId in objectIds) - { - ret[objectId] = Objects.ContainsKey(objectId); - } - - return Task.FromResult(ret); - } - - public override string ToString() - { - return $"Memory Transport {TransportName}"; - } - - public string BlobStorageFolder => SpecklePathProvider.BlobStoragePath(Path.Combine(_basePath, _applicationName)); - - public void SaveBlob(Blob obj) - { - if (!_blobStorageEnabled) - { - return; - } - var blobPath = obj.originalPath; - var targetPath = obj.GetLocalDestinationPath(BlobStorageFolder); - File.Copy(blobPath, targetPath, true); - } -} diff --git a/src/Speckle.Sdk/Transports/SQLiteTransport.cs b/src/Speckle.Sdk/Transports/SQLiteTransport.cs deleted file mode 100644 index 46b2985d..00000000 --- a/src/Speckle.Sdk/Transports/SQLiteTransport.cs +++ /dev/null @@ -1,433 +0,0 @@ -using System.Collections.Concurrent; -using System.Diagnostics; -using System.Text; -using System.Timers; -using Microsoft.Data.Sqlite; -using Speckle.Sdk.Logging; -using Speckle.Sdk.Models; -using Timer = System.Timers.Timer; - -namespace Speckle.Sdk.Transports; - -public sealed class SQLiteTransport : IDisposable, ICloneable, ITransport, IBlobCapableTransport -{ - private bool _isWriting; - private const int MAX_TRANSACTION_SIZE = 1000; - private const int POLL_INTERVAL = 500; - - private ConcurrentQueue<(string id, string serializedObject, int byteCount)> _queue = new(); - - /// - /// Timer that ensures queue is consumed if less than MAX_TRANSACTION_SIZE objects are being sent. - /// - private readonly Timer _writeTimer; - - /// - /// Connects to an SQLite DB at {}/{}/{}.db - /// Will attempt to create db + directory structure as needed - /// - /// defaults to if - /// defaults to "Speckle" if - /// defaults to "Data" if - /// Failed to initialize a connection to the db - /// Path was invalid or could not be created - public SQLiteTransport(string? basePath = null, string? applicationName = null, string? scope = null) - { - _basePath = basePath ?? SpecklePathProvider.UserApplicationDataPath(); - _applicationName = applicationName ?? "Speckle"; - _scope = scope ?? "Data"; - - try - { - var dir = Path.Combine(_basePath, _applicationName); - _rootPath = Path.Combine(dir, $"{_scope}.db"); - - Directory.CreateDirectory(dir); //ensure dir is there - } - catch (Exception ex) - when (ex is ArgumentException or IOException or UnauthorizedAccessException or NotSupportedException) - { - throw new TransportException($"Path was invalid or could not be created {_rootPath}", ex); - } - - _connectionString = $"Data Source={_rootPath};"; - - Initialize(); - - _writeTimer = new Timer - { - AutoReset = true, - Enabled = false, - Interval = POLL_INTERVAL, - }; - _writeTimer.Elapsed += WriteTimerElapsed; - } - - private readonly string _rootPath; - - private readonly string _basePath; - private readonly string _applicationName; - private readonly string _scope; - private readonly string _connectionString; - - private SqliteConnection Connection { get; set; } - private readonly SemaphoreSlim _connectionLock = new(1, 1); - - public string BlobStorageFolder => SpecklePathProvider.BlobStoragePath(Path.Combine(_basePath, _applicationName)); - - public void SaveBlob(Blob obj) - { - var blobPath = obj.originalPath; - var targetPath = obj.GetLocalDestinationPath(BlobStorageFolder); - File.Copy(blobPath, targetPath, true); - } - - public object Clone() - { - return new SQLiteTransport(_basePath, _applicationName, _scope) - { - OnProgressAction = OnProgressAction, - CancellationToken = CancellationToken, - }; - } - - public void Dispose() - { - // TODO: Check if it's still writing? - Connection.Close(); - Connection.Dispose(); - _writeTimer.Dispose(); - _connectionLock.Dispose(); - } - - public string TransportName { get; set; } = "SQLite"; - - public Dictionary TransportContext => - new() - { - { "name", TransportName }, - { "type", GetType().Name }, - { "basePath", _basePath }, - { "applicationName", _applicationName }, - { "scope", _scope }, - { "blobStorageFolder", BlobStorageFolder }, - }; - - public CancellationToken CancellationToken { get; set; } - - public IProgress? OnProgressAction { get; set; } - - public int SavedObjectCount { get; private set; } - - public TimeSpan Elapsed { get; private set; } - - public void BeginWrite() - { - _queue = new(); - SavedObjectCount = 0; - } - - public void EndWrite() { } - - public Task> HasObjects(IReadOnlyList objectIds) - { - Dictionary ret = new(objectIds.Count); - // Initialize with false so that canceled queries still return a dictionary item for every object id - foreach (string objectId in objectIds) - { - ret[objectId] = false; - } - - try - { - const string COMMAND_TEXT = "SELECT 1 FROM objects WHERE hash = @hash LIMIT 1 "; - using var command = new SqliteCommand(COMMAND_TEXT, Connection); - - foreach (string objectId in objectIds) - { - CancellationToken.ThrowIfCancellationRequested(); - - command.Parameters.Clear(); - command.Parameters.AddWithValue("@hash", objectId); - - using var reader = command.ExecuteReader(); - bool rowFound = reader.Read(); - ret[objectId] = rowFound; - } - } - catch (SqliteException ex) - { - throw new TransportException("SQLite transport failed", ex); - } - - return Task.FromResult(ret); - } - - /// Failed to initialize connection to the SQLite DB - private void Initialize() - { - // NOTE: used for creating partioned object tables. - //string[] HexChars = new string[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; - //var cart = new List(); - //foreach (var str in HexChars) - // foreach (var str2 in HexChars) - // cart.Add(str + str2); - - using (var c = new SqliteConnection(_connectionString)) - { - c.Open(); - const string COMMAND_TEXT = - @" - CREATE TABLE IF NOT EXISTS objects( - hash TEXT PRIMARY KEY, - content TEXT - ) WITHOUT ROWID; - "; - using (var command = new SqliteCommand(COMMAND_TEXT, c)) - { - command.ExecuteNonQuery(); - } - - // Insert Optimisations - - using SqliteCommand cmd0 = new("PRAGMA journal_mode='wal';", c); - cmd0.ExecuteNonQuery(); - - //Note / Hack: This setting has the potential to corrupt the db. - //cmd = new SqliteCommand("PRAGMA synchronous=OFF;", Connection); - //cmd.ExecuteNonQuery(); - - using SqliteCommand cmd1 = new("PRAGMA count_changes=OFF;", c); - cmd1.ExecuteNonQuery(); - - using SqliteCommand cmd2 = new("PRAGMA temp_store=MEMORY;", c); - cmd2.ExecuteNonQuery(); - } - - Connection = new SqliteConnection(_connectionString); - Connection.Open(); - } - - /// - /// Returns all the objects in the store. Note: do not use for large collections. - /// - /// - /// This function uses a separate so is safe to call concurrently (unlike most other transport functions) - internal IEnumerable GetAllObjects() - { - CancellationToken.ThrowIfCancellationRequested(); - - using SqliteConnection connection = new(_connectionString); - connection.Open(); - - using var command = new SqliteCommand("SELECT * FROM objects", connection); - - using var reader = command.ExecuteReader(); - while (reader.Read()) - { - CancellationToken.ThrowIfCancellationRequested(); - yield return reader.GetString(1); - } - } - - /// - /// Deletes an object. Note: do not use for any speckle object transport, as it will corrupt the database. - /// - /// - public void DeleteObject(string hash) - { - CancellationToken.ThrowIfCancellationRequested(); - - using var command = new SqliteCommand("DELETE FROM objects WHERE hash = @hash", Connection); - command.Parameters.AddWithValue("@hash", hash); - command.ExecuteNonQuery(); - } - - /// - /// Updates an object. - /// - /// - /// - public void UpdateObject(string hash, string serializedObject) - { - CancellationToken.ThrowIfCancellationRequested(); - - using var c = new SqliteConnection(_connectionString); - c.Open(); - const string COMMAND_TEXT = "REPLACE INTO objects(hash, content) VALUES(@hash, @content)"; - using var command = new SqliteCommand(COMMAND_TEXT, c); - command.Parameters.AddWithValue("@hash", hash); - command.Parameters.AddWithValue("@content", serializedObject); - command.ExecuteNonQuery(); - } - - public override string ToString() - { - return $"Sqlite Transport @{_rootPath}"; - } - - #region Writes - - /// - /// Awaits untill write completion (ie, the current queue is fully consumed). - /// - /// - public async Task WriteComplete() => - await Utilities.WaitUntil(() => WriteCompletionStatus, 500).ConfigureAwait(false); - - /// - /// Returns true if the current write queue is empty and comitted. - /// - /// - public bool WriteCompletionStatus => _queue.IsEmpty && !_isWriting; - - private void WriteTimerElapsed(object? sender, ElapsedEventArgs e) - { - _writeTimer.Enabled = false; - - if (CancellationToken.IsCancellationRequested) - { - _queue = new ConcurrentQueue<(string, string, int)>(); - return; - } - - if (!_isWriting && !_queue.IsEmpty) - { - ConsumeQueue(); - } - } - - private void ConsumeQueue() - { - var stopwatch = Stopwatch.StartNew(); - _isWriting = true; - try - { - CancellationToken.ThrowIfCancellationRequested(); - - var i = 0; //BUG: This never gets incremented! - - var saved = 0; - - using (var c = new SqliteConnection(_connectionString)) - { - c.Open(); - using var t = c.BeginTransaction(); - const string COMMAND_TEXT = "INSERT OR IGNORE INTO objects(hash, content) VALUES(@hash, @content)"; - - while (i < MAX_TRANSACTION_SIZE && _queue.TryPeek(out var result)) - { - using var command = new SqliteCommand(COMMAND_TEXT, c, t); - _queue.TryDequeue(out result); - command.Parameters.AddWithValue("@hash", result.id); - command.Parameters.AddWithValue("@content", result.serializedObject); - command.ExecuteNonQuery(); - - saved++; - } - - t.Commit(); - CancellationToken.ThrowIfCancellationRequested(); - } - - CancellationToken.ThrowIfCancellationRequested(); - - if (!_queue.IsEmpty) - { - ConsumeQueue(); - } - } - catch (SqliteException ex) - { - throw new TransportException(this, "SQLite Command Failed", ex); - } - catch (OperationCanceledException) - { - _queue = new(); - } - finally - { - stopwatch.Stop(); - Elapsed += stopwatch.Elapsed; - _isWriting = false; - } - } - - /// - /// Adds an object to the saving queue. - /// - /// - /// - public void SaveObject(string id, string serializedObject) - { - CancellationToken.ThrowIfCancellationRequested(); - _queue.Enqueue((id, serializedObject, Encoding.UTF8.GetByteCount(serializedObject))); - - _writeTimer.Enabled = true; - _writeTimer.Start(); - } - - /// - /// Directly saves the object in the db. - /// - /// - /// - public void SaveObjectSync(string hash, string serializedObject) - { - const string COMMAND_TEXT = "INSERT OR IGNORE INTO objects(hash, content) VALUES(@hash, @content)"; - - try - { - using var command = new SqliteCommand(COMMAND_TEXT, Connection); - command.Parameters.AddWithValue("@hash", hash); - command.Parameters.AddWithValue("@content", serializedObject); - command.ExecuteNonQuery(); - } - catch (SqliteException ex) - { - throw new TransportException(this, "SQLite Command Failed", ex); - } - } - - #endregion - - #region Reads - - /// - /// Gets an object. - /// - /// - /// - public async Task GetObject(string id) - { - CancellationToken.ThrowIfCancellationRequested(); - await _connectionLock.WaitAsync(CancellationToken).ConfigureAwait(false); - var startTime = Stopwatch.GetTimestamp(); - try - { - using var command = new SqliteCommand("SELECT * FROM objects WHERE hash = @hash LIMIT 1 ", Connection); - command.Parameters.AddWithValue("@hash", id); - using var reader = command.ExecuteReader(); - if (reader.Read()) - { - return reader.GetString(1); - } - } - finally - { - Elapsed += LoggingHelpers.GetElapsedTime(startTime, Stopwatch.GetTimestamp()); - _connectionLock.Release(); - } - return null; // pass on the duty of null checks to consumers - } - - public async Task CopyObjectAndChildren(string id, ITransport targetTransport) - { - string res = await TransportHelpers - .CopyObjectAndChildrenAsync(id, this, targetTransport, CancellationToken) - .ConfigureAwait(false); - return res; - } - - #endregion -} diff --git a/src/Speckle.Sdk/Transports/SQLiteTransport2.cs b/src/Speckle.Sdk/Transports/SQLiteTransport2.cs deleted file mode 100644 index 3009d3cb..00000000 --- a/src/Speckle.Sdk/Transports/SQLiteTransport2.cs +++ /dev/null @@ -1,408 +0,0 @@ -using System.Collections.Concurrent; -using System.Diagnostics; -using System.Text; -using System.Timers; -using Microsoft.Data.Sqlite; -using Speckle.Sdk.Caching; -using Speckle.Sdk.Logging; -using Speckle.Sdk.Models; -using Timer = System.Timers.Timer; - -namespace Speckle.Sdk.Transports; - -public sealed class SQLiteTransport2 : IDisposable, ICloneable, ITransport, IBlobCapableTransport -{ - private readonly string _streamId; - private bool _isWriting; - private const int MAX_TRANSACTION_SIZE = 1000; - private const int POLL_INTERVAL = 500; - - private ConcurrentQueue<(string id, string serializedObject, int byteCount)> _queue = new(); - - /// - /// Timer that ensures queue is consumed if less than MAX_TRANSACTION_SIZE objects are being sent. - /// - private readonly Timer _writeTimer; - - public SQLiteTransport2(string streamId) - { - _streamId = streamId; - - _rootPath = ModelCacheManager.GetDbPath(streamId); - - _connectionString = $"Data Source={_rootPath};"; - - Initialize(); - - _writeTimer = new Timer - { - AutoReset = true, - Enabled = false, - Interval = POLL_INTERVAL, - }; - _writeTimer.Elapsed += WriteTimerElapsed; - } - - private readonly string _rootPath; - - private readonly string _connectionString; - - private SqliteConnection Connection { get; set; } - private readonly SemaphoreSlim _connectionLock = new(1, 1); - - public string BlobStorageFolder => SpecklePathProvider.UserSpeckleFolderPath; - - public void SaveBlob(Blob obj) - { - var blobPath = obj.originalPath; - var targetPath = obj.GetLocalDestinationPath(BlobStorageFolder); - File.Copy(blobPath, targetPath, true); - } - - public object Clone() - { - return new SQLiteTransport2(_streamId) - { - OnProgressAction = OnProgressAction, - CancellationToken = CancellationToken, - }; - } - - public void Dispose() - { - // TODO: Check if it's still writing? - Connection.Close(); - Connection.Dispose(); - _writeTimer.Dispose(); - _connectionLock.Dispose(); - } - - public string TransportName { get; set; } = "SQLite"; - - public Dictionary TransportContext => - new() - { - { "name", TransportName }, - { "type", GetType().Name }, - { "streamId", _streamId }, - { "blobStorageFolder", BlobStorageFolder }, - }; - - public CancellationToken CancellationToken { get; set; } - - public IProgress? OnProgressAction { get; set; } - - public int SavedObjectCount { get; private set; } - - public TimeSpan Elapsed { get; private set; } - - public void BeginWrite() - { - _queue = new(); - SavedObjectCount = 0; - } - - public void EndWrite() { } - - public Task> HasObjects(IReadOnlyList objectIds) - { - Dictionary ret = new(objectIds.Count); - // Initialize with false so that canceled queries still return a dictionary item for every object id - foreach (string objectId in objectIds) - { - ret[objectId] = false; - } - - try - { - const string COMMAND_TEXT = "SELECT 1 FROM objects WHERE hash = @hash LIMIT 1 "; - using var command = new SqliteCommand(COMMAND_TEXT, Connection); - - foreach (string objectId in objectIds) - { - CancellationToken.ThrowIfCancellationRequested(); - - command.Parameters.Clear(); - command.Parameters.AddWithValue("@hash", objectId); - - using var reader = command.ExecuteReader(); - bool rowFound = reader.Read(); - ret[objectId] = rowFound; - } - } - catch (SqliteException ex) - { - throw new TransportException("SQLite transport failed", ex); - } - - return Task.FromResult(ret); - } - - /// Failed to initialize connection to the SQLite DB - private void Initialize() - { - // NOTE: used for creating partioned object tables. - //string[] HexChars = new string[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; - //var cart = new List(); - //foreach (var str in HexChars) - // foreach (var str2 in HexChars) - // cart.Add(str + str2); - - using (var c = new SqliteConnection(_connectionString)) - { - c.Open(); - const string COMMAND_TEXT = - @" - CREATE TABLE IF NOT EXISTS objects( - hash TEXT PRIMARY KEY, - content TEXT - ) WITHOUT ROWID; - "; - using (var command = new SqliteCommand(COMMAND_TEXT, c)) - { - command.ExecuteNonQuery(); - } - - // Insert Optimisations - - using SqliteCommand cmd0 = new("PRAGMA journal_mode='wal';", c); - cmd0.ExecuteNonQuery(); - - //Note / Hack: This setting has the potential to corrupt the db. - //cmd = new SqliteCommand("PRAGMA synchronous=OFF;", Connection); - //cmd.ExecuteNonQuery(); - - using SqliteCommand cmd1 = new("PRAGMA count_changes=OFF;", c); - cmd1.ExecuteNonQuery(); - - using SqliteCommand cmd2 = new("PRAGMA temp_store=MEMORY;", c); - cmd2.ExecuteNonQuery(); - } - - Connection = new SqliteConnection(_connectionString); - Connection.Open(); - } - - /// - /// Returns all the objects in the store. Note: do not use for large collections. - /// - /// - /// This function uses a separate so is safe to call concurrently (unlike most other transport functions) - internal IEnumerable GetAllObjects() - { - CancellationToken.ThrowIfCancellationRequested(); - - using SqliteConnection connection = new(_connectionString); - connection.Open(); - - using var command = new SqliteCommand("SELECT * FROM objects", connection); - - using var reader = command.ExecuteReader(); - while (reader.Read()) - { - CancellationToken.ThrowIfCancellationRequested(); - yield return reader.GetString(1); - } - } - - /// - /// Deletes an object. Note: do not use for any speckle object transport, as it will corrupt the database. - /// - /// - public void DeleteObject(string hash) - { - CancellationToken.ThrowIfCancellationRequested(); - - using var command = new SqliteCommand("DELETE FROM objects WHERE hash = @hash", Connection); - command.Parameters.AddWithValue("@hash", hash); - command.ExecuteNonQuery(); - } - - /// - /// Updates an object. - /// - /// - /// - public void UpdateObject(string hash, string serializedObject) - { - CancellationToken.ThrowIfCancellationRequested(); - - using var c = new SqliteConnection(_connectionString); - c.Open(); - const string COMMAND_TEXT = "REPLACE INTO objects(hash, content) VALUES(@hash, @content)"; - using var command = new SqliteCommand(COMMAND_TEXT, c); - command.Parameters.AddWithValue("@hash", hash); - command.Parameters.AddWithValue("@content", serializedObject); - command.ExecuteNonQuery(); - } - - public override string ToString() - { - return $"Sqlite Transport @{_rootPath}"; - } - - #region Writes - - /// - /// Awaits untill write completion (ie, the current queue is fully consumed). - /// - /// - public async Task WriteComplete() => - await Utilities.WaitUntil(() => WriteCompletionStatus, 500).ConfigureAwait(false); - - /// - /// Returns true if the current write queue is empty and comitted. - /// - /// - public bool WriteCompletionStatus => _queue.IsEmpty && !_isWriting; - - private void WriteTimerElapsed(object? sender, ElapsedEventArgs e) - { - _writeTimer.Enabled = false; - - if (CancellationToken.IsCancellationRequested) - { - _queue = new ConcurrentQueue<(string, string, int)>(); - return; - } - - if (!_isWriting && !_queue.IsEmpty) - { - ConsumeQueue(); - } - } - - private void ConsumeQueue() - { - var stopwatch = Stopwatch.StartNew(); - _isWriting = true; - try - { - CancellationToken.ThrowIfCancellationRequested(); - - var i = 0; //BUG: This never gets incremented! - - var saved = 0; - - using (var c = new SqliteConnection(_connectionString)) - { - c.Open(); - using var t = c.BeginTransaction(); - const string COMMAND_TEXT = "INSERT OR IGNORE INTO objects(hash, content) VALUES(@hash, @content)"; - - while (i < MAX_TRANSACTION_SIZE && _queue.TryPeek(out var result)) - { - using var command = new SqliteCommand(COMMAND_TEXT, c, t); - _queue.TryDequeue(out result); - command.Parameters.AddWithValue("@hash", result.id); - command.Parameters.AddWithValue("@content", result.serializedObject); - command.ExecuteNonQuery(); - - saved++; - } - - t.Commit(); - CancellationToken.ThrowIfCancellationRequested(); - } - - CancellationToken.ThrowIfCancellationRequested(); - - if (!_queue.IsEmpty) - { - ConsumeQueue(); - } - } - catch (SqliteException ex) - { - throw new TransportException(this, "SQLite Command Failed", ex); - } - catch (OperationCanceledException) - { - _queue = new(); - } - finally - { - stopwatch.Stop(); - Elapsed += stopwatch.Elapsed; - _isWriting = false; - } - } - - /// - /// Adds an object to the saving queue. - /// - /// - /// - public void SaveObject(string id, string serializedObject) - { - CancellationToken.ThrowIfCancellationRequested(); - _queue.Enqueue((id, serializedObject, Encoding.UTF8.GetByteCount(serializedObject))); - - _writeTimer.Enabled = true; - _writeTimer.Start(); - } - - /// - /// Directly saves the object in the db. - /// - /// - /// - public void SaveObjectSync(string hash, string serializedObject) - { - const string COMMAND_TEXT = "INSERT OR IGNORE INTO objects(hash, content) VALUES(@hash, @content)"; - - try - { - using var command = new SqliteCommand(COMMAND_TEXT, Connection); - command.Parameters.AddWithValue("@hash", hash); - command.Parameters.AddWithValue("@content", serializedObject); - command.ExecuteNonQuery(); - } - catch (SqliteException ex) - { - throw new TransportException(this, "SQLite Command Failed", ex); - } - } - - #endregion - - #region Reads - - /// - /// Gets an object. - /// - /// - /// - public async Task GetObject(string id) - { - CancellationToken.ThrowIfCancellationRequested(); - await _connectionLock.WaitAsync(CancellationToken).ConfigureAwait(false); - var startTime = Stopwatch.GetTimestamp(); - try - { - using var command = new SqliteCommand("SELECT * FROM objects WHERE hash = @hash LIMIT 1 ", Connection); - command.Parameters.AddWithValue("@hash", id); - using var reader = command.ExecuteReader(); - if (reader.Read()) - { - return reader.GetString(1); - } - } - finally - { - Elapsed += LoggingHelpers.GetElapsedTime(startTime, Stopwatch.GetTimestamp()); - _connectionLock.Release(); - } - return null; // pass on the duty of null checks to consumers - } - - public async Task CopyObjectAndChildren(string id, ITransport targetTransport) - { - string res = await TransportHelpers - .CopyObjectAndChildrenAsync(id, this, targetTransport, CancellationToken) - .ConfigureAwait(false); - return res; - } - - #endregion -} diff --git a/src/Speckle.Sdk/Transports/ServerTransport.cs b/src/Speckle.Sdk/Transports/ServerTransport.cs deleted file mode 100644 index 7ca7536c..00000000 --- a/src/Speckle.Sdk/Transports/ServerTransport.cs +++ /dev/null @@ -1,374 +0,0 @@ -using System.Diagnostics; -using Speckle.Sdk.Common; -using Speckle.Sdk.Credentials; -using Speckle.Sdk.Helpers; -using Speckle.Sdk.Logging; -using Speckle.Sdk.Models; -using Speckle.Sdk.Serialisation.Utilities; -using Speckle.Sdk.Transports.ServerUtils; - -namespace Speckle.Sdk.Transports; - -public sealed class ServerTransport : IServerTransport -{ - private readonly ISpeckleHttp _http; - private readonly ISdkActivityFactory _activityFactory; - private readonly object _elapsedLock = new(); - - private Exception? _exception; - private bool IsInErrorState => _exception is not null; - private bool _isWriteComplete; - - // TODO: make send buffer more flexible to accept blobs too - private List<(string id, string data)> _sendBuffer = new(); - private readonly object _sendBufferLock = new(); - private Thread? _sendingThread; - - private volatile bool _shouldSendThreadRun; - - /// - /// - /// - /// Defaults to - /// was not formatted as valid stream id - public ServerTransport( - ISpeckleHttp http, - ISdkActivityFactory activityFactory, - Account account, - string streamId, - int timeoutSeconds = 60, - string? blobStorageFolder = null - ) - { - if (string.IsNullOrWhiteSpace(streamId)) - { - throw new ArgumentException($"{streamId} is not a valid id", streamId); - } - - _http = http; - _activityFactory = activityFactory; - - Account = account; - BaseUri = new(account.serverInfo.url); - StreamId = streamId; - AuthorizationToken = account.token; - TimeoutSeconds = timeoutSeconds; - BlobStorageFolder = blobStorageFolder ?? SpecklePathProvider.BlobStoragePath(); - Api = new ParallelServerApi(http, activityFactory, BaseUri, AuthorizationToken, BlobStorageFolder, TimeoutSeconds); - - Directory.CreateDirectory(BlobStorageFolder); - } - - public Account Account { get; } - public Uri BaseUri { get; } - public string StreamId { get; internal set; } - - public int TimeoutSeconds { get; set; } - private string AuthorizationToken { get; } - - internal ParallelServerApi Api { get; private set; } - - public string BlobStorageFolder { get; set; } - - public void SaveBlob(Blob obj) - { - var hash = obj.GetFileHash(); - - lock (_sendBufferLock) - { - if (IsInErrorState) - { - throw new TransportException("Server transport is in an errored state", _exception); - } - - _sendBuffer.Add(($"blob:{hash}", obj.filePath)); - } - } - - public object Clone() - { - return new ServerTransport(_http, _activityFactory, Account, StreamId, TimeoutSeconds, BlobStorageFolder) - { - OnProgressAction = OnProgressAction, - CancellationToken = CancellationToken, - }; - } - - public void Dispose() - { - if (_sendingThread != null) - { - _shouldSendThreadRun = false; - _sendingThread.Join(); - } - Api.Dispose(); - } - - public string TransportName { get; set; } = "RemoteTransport"; - - public Dictionary TransportContext => - new() - { - { "name", TransportName }, - { "type", GetType().Name }, - { "streamId", StreamId }, - { "serverUrl", BaseUri }, - { "blobStorageFolder", BlobStorageFolder }, - }; - - public CancellationToken CancellationToken { get; set; } - public IProgress? OnProgressAction { get; set; } - public TimeSpan Elapsed { get; private set; } = TimeSpan.Zero; - - public async Task CopyObjectAndChildren(string id, ITransport targetTransport) - { - if (string.IsNullOrEmpty(id)) - { - throw new ArgumentException("Cannot copy object with empty id", nameof(id)); - } - - CancellationToken.ThrowIfCancellationRequested(); - - using ParallelServerApi api = new( - _http, - _activityFactory, - BaseUri, - AuthorizationToken, - BlobStorageFolder, - TimeoutSeconds - ); - - var stopwatch = Stopwatch.StartNew(); - api.CancellationToken = CancellationToken; - - string? rootObjectJson = await api.DownloadSingleObject(StreamId, id, OnProgressAction).ConfigureAwait(false); - var allIds = ClosureParser.GetChildrenIds(rootObjectJson.NotNull(), CancellationToken).ToList(); - - var childrenIds = allIds.Where(x => !x.Contains("blob:")); - var blobIds = allIds.Where(x => x.Contains("blob:")).Select(x => x.Remove(0, 5)); - - // - // Objects download - // - - // Check which children are not already in the local transport - var childrenFoundMap = await targetTransport.HasObjects(childrenIds.ToList()).ConfigureAwait(false); - List newChildrenIds = new(from objId in childrenFoundMap.Keys where !childrenFoundMap[objId] select objId); - - targetTransport.BeginWrite(); - - await api.DownloadObjects( - StreamId, - newChildrenIds, - OnProgressAction, - (childId, childData) => - { - stopwatch.Stop(); - targetTransport.SaveObject(childId, childData); - stopwatch.Start(); - } - ) - .ConfigureAwait(false); - - // pausing until writing to the target transport - stopwatch.Stop(); - targetTransport.SaveObject(id, rootObjectJson); - - await targetTransport.WriteComplete().ConfigureAwait(false); - targetTransport.EndWrite(); - stopwatch.Start(); - - // - // Blobs download - // - var localBlobTrimmedHashes = Directory - .GetFiles(BlobStorageFolder) - .Select(fileName => fileName.Split(Path.DirectorySeparatorChar).Last()) - .Where(fileName => fileName.Length > 10) - .Select(fileName => fileName[..Blob.LocalHashPrefixLength]) - .ToList(); - - var newBlobIds = blobIds - .Where(blobId => !localBlobTrimmedHashes.Contains(blobId[..Blob.LocalHashPrefixLength])) - .ToList(); - - await api.DownloadBlobs(StreamId, newBlobIds, OnProgressAction).ConfigureAwait(false); - - stopwatch.Stop(); - Elapsed += stopwatch.Elapsed; - return rootObjectJson; - } - - public async Task GetObject(string id) - { - CancellationToken.ThrowIfCancellationRequested(); - var stopwatch = Stopwatch.StartNew(); - var result = await Api.DownloadSingleObject(StreamId, id, OnProgressAction).ConfigureAwait(false); - stopwatch.Stop(); - Elapsed += stopwatch.Elapsed; - return result; - } - - public async Task> HasObjects(IReadOnlyList objectIds) - { - return await Api.HasObjects(StreamId, objectIds).ConfigureAwait(false); - } - - public void SaveObject(string id, string serializedObject) - { - lock (_sendBufferLock) - { - if (IsInErrorState) - { - throw new TransportException($"{TransportName} transport failed", _exception); - } - - _sendBuffer.Add((id, serializedObject)); - _isWriteComplete = false; - } - } - - public void BeginWrite() - { - if (_shouldSendThreadRun || _sendingThread != null) - { - throw new InvalidOperationException("ServerTransport already sending"); - } - - _exception = null; - _shouldSendThreadRun = true; - _sendingThread = new Thread(SendingThreadMain) { Name = "ServerTransportSender", IsBackground = true }; - _sendingThread.Start(); - } - - public async Task WriteComplete() - { - while (true) - { - lock (_sendBufferLock) - { - if (_isWriteComplete || IsInErrorState) - { - CancellationToken.ThrowIfCancellationRequested(); - - if (_exception is not null) - { - throw new TransportException(this, $"{TransportName} transport failed", _exception); - } - - return; - } - } - - await Task.Delay(50, CancellationToken).ConfigureAwait(false); - } - } - - public void EndWrite() - { - if (!_shouldSendThreadRun || _sendingThread == null) - { - throw new InvalidOperationException("ServerTransport not sending"); - } - - _shouldSendThreadRun = false; - _sendingThread.Join(); - _sendingThread = null; - } - - public override string ToString() - { - return $"Server Transport @{Account.serverInfo.url}"; - } - - private async void SendingThreadMain() - { - while (true) - { - var stopwatch = Stopwatch.StartNew(); - if (!_shouldSendThreadRun || CancellationToken.IsCancellationRequested) - { - return; - } - - List<(string id, string data)>? buffer = null; - lock (_sendBufferLock) - { - if (_sendBuffer.Count > 0) - { - buffer = _sendBuffer; - _sendBuffer = new(); - } - else - { - _isWriteComplete = true; - } - } - - if (buffer is null) - { - Thread.Sleep(100); - continue; - } - try - { - var bufferObjects = buffer.Where(tuple => !tuple.id.Contains("blob")).ToList(); - var bufferBlobs = buffer.Where(tuple => tuple.id.Contains("blob")).ToList(); - - List objectIds = new(bufferObjects.Count); - - foreach ((string id, _) in bufferObjects) - { - if (id != "blob") - { - objectIds.Add(id); - } - } - - Dictionary hasObjects = await Api.HasObjects(StreamId, objectIds).ConfigureAwait(false); - List<(string, string)> newObjects = new(); - foreach ((string id, object json) in bufferObjects) - { - if (!hasObjects[id]) - { - newObjects.Add((id, (string)json)); - } - } - - await Api.UploadObjects(StreamId, newObjects, OnProgressAction).ConfigureAwait(false); - - if (bufferBlobs.Count != 0) - { - var blobIdsToUpload = await Api.HasBlobs(StreamId, bufferBlobs).ConfigureAwait(false); - var formattedIds = blobIdsToUpload.Select(id => $"blob:{id}").ToList(); - var newBlobs = bufferBlobs.Where(tuple => formattedIds.IndexOf(tuple.id) != -1).ToList(); - if (newBlobs.Count != 0) - { - await Api.UploadBlobs(StreamId, newBlobs, OnProgressAction).ConfigureAwait(false); - } - } - } - catch (Exception ex) - { - lock (_sendBufferLock) - { - _sendBuffer.Clear(); - _exception = ex; - } - - if (ex.IsFatal()) - { - throw; - } - } - finally - { - stopwatch.Stop(); - lock (_elapsedLock) - { - Elapsed += stopwatch.Elapsed; - } - } - } - } -} diff --git a/src/Speckle.Sdk/Transports/ServerTransportFactory.cs b/src/Speckle.Sdk/Transports/ServerTransportFactory.cs deleted file mode 100644 index 0710cfe6..00000000 --- a/src/Speckle.Sdk/Transports/ServerTransportFactory.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Speckle.InterfaceGenerator; -using Speckle.Sdk.Credentials; -using Speckle.Sdk.Helpers; -using Speckle.Sdk.Logging; - -namespace Speckle.Sdk.Transports; - -[GenerateAutoInterface] -[ExcludeFromCodeCoverage] //factories don't need coverage -public class ServerTransportFactory(ISpeckleHttp http, ISdkActivityFactory activityFactory) : IServerTransportFactory -{ - public ServerTransport Create( - Account account, - string streamId, - int timeoutSeconds = 60, - string? blobStorageFolder = null - ) => new ServerTransport(http, activityFactory, account, streamId, timeoutSeconds, blobStorageFolder); -} diff --git a/src/Speckle.Sdk/Transports/ServerUtils/IServerApi.cs b/src/Speckle.Sdk/Transports/ServerUtils/IServerApi.cs deleted file mode 100644 index 0fa65d33..00000000 --- a/src/Speckle.Sdk/Transports/ServerUtils/IServerApi.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace Speckle.Sdk.Transports.ServerUtils; - -public delegate void CbObjectDownloaded(string id, string json); - -internal interface IServerApi -{ - public Task DownloadSingleObject(string streamId, string objectId, IProgress? progress); - - public Task DownloadObjects( - string streamId, - IReadOnlyList objectIds, - IProgress? progress, - CbObjectDownloaded onObjectCallback - ); - - public Task> HasObjects(string streamId, IReadOnlyList objectIds); - - public Task UploadObjects( - string streamId, - IReadOnlyList<(string id, string data)> objects, - IProgress? progress - ); - - public Task UploadBlobs( - string streamId, - IReadOnlyList<(string id, string data)> objects, - IProgress? progress - ); - - public Task DownloadBlobs(string streamId, IReadOnlyList blobIds, IProgress? progress); -} diff --git a/src/Speckle.Sdk/Transports/ServerUtils/ParallelServerAPI.cs b/src/Speckle.Sdk/Transports/ServerUtils/ParallelServerAPI.cs deleted file mode 100644 index 7e048b40..00000000 --- a/src/Speckle.Sdk/Transports/ServerUtils/ParallelServerAPI.cs +++ /dev/null @@ -1,325 +0,0 @@ -using System.Collections.Concurrent; -using System.Diagnostics; -using Speckle.Sdk.Common; -using Speckle.Sdk.Helpers; -using Speckle.Sdk.Logging; -using Speckle.Sdk.Serialisation.Utilities; - -namespace Speckle.Sdk.Transports.ServerUtils; - -internal enum ServerApiOperation -{ - NoOp = default, - DownloadSingleObject, - DownloadObjects, - HasObjects, - UploadObjects, - UploadBlobs, - DownloadBlobs, - HasBlobs, -} - -internal class ParallelServerApi : ParallelOperationExecutor, IServerApi -{ - private readonly string _authToken; - - private readonly ISpeckleHttp _http; - private readonly ISdkActivityFactory _activityFactory; - private readonly Uri _baseUri; - - private readonly int _timeoutSeconds; - - public ParallelServerApi( - ISpeckleHttp http, - ISdkActivityFactory activityFactory, - Uri baseUri, - string authorizationToken, - string blobStorageFolder, - int timeoutSeconds, - int numThreads = 4, - int numBufferedOperations = 8 - ) - { - _http = http; - _activityFactory = activityFactory; - _baseUri = baseUri; - _authToken = authorizationToken; - _timeoutSeconds = timeoutSeconds; - NumThreads = numThreads; - - BlobStorageFolder = blobStorageFolder; - - NumThreads = numThreads; - Tasks = new BlockingCollection>(numBufferedOperations); - } - - public CancellationToken CancellationToken { get; set; } - public bool CompressPayloads { get; set; } = true; - - public string BlobStorageFolder { get; set; } - - #region Operations - - public async Task> HasObjects(string streamId, IReadOnlyList objectIds) - { - EnsureStarted(); - List> tasks = new(); - IReadOnlyList> splitObjectsIds; - if (objectIds.Count <= 50) - { - splitObjectsIds = new List> { objectIds }; - } - else - { - splitObjectsIds = SplitList(objectIds, NumThreads); - } - - for (int i = 0; i < NumThreads; i++) - { - if (splitObjectsIds.Count <= i || splitObjectsIds[i].Count == 0) - { - continue; - } - - var op = QueueOperation(ServerApiOperation.HasObjects, (streamId, splitObjectsIds[i])); - tasks.Add(op); - } - Dictionary ret = new(); - foreach (var task in tasks) - { - var taskResult = (IReadOnlyDictionary?)(await task.ConfigureAwait(false)); - foreach (KeyValuePair kv in taskResult.Empty()) - { - ret[kv.Key] = kv.Value; - } - } - - return ret; - } - - public async Task DownloadSingleObject(string streamId, string objectId, IProgress? progress) - { - EnsureStarted(); - Task op = QueueOperation(ServerApiOperation.DownloadSingleObject, (streamId, objectId, progress)); - object? result = await op.ConfigureAwait(false); - return (string?)result; - } - - public async Task DownloadObjects( - string streamId, - IReadOnlyList objectIds, - IProgress? progress, - CbObjectDownloaded onObjectCallback - ) - { - EnsureStarted(); - List> tasks = new(); - IReadOnlyList> splitObjectsIds = SplitList(objectIds, NumThreads); - object callbackLock = new(); - - CbObjectDownloaded callbackWrapper = (id, json) => - { - lock (callbackLock) - { - onObjectCallback(id, json); - } - }; - - for (int i = 0; i < NumThreads; i++) - { - if (splitObjectsIds[i].Count == 0) - { - continue; - } - - Task op = QueueOperation( - ServerApiOperation.DownloadObjects, - (streamId, splitObjectsIds[i], progress, callbackWrapper) - ); - tasks.Add(op); - } - await Task.WhenAll(tasks.ToArray()).ConfigureAwait(false); - } - - public async Task UploadObjects( - string streamId, - IReadOnlyList<(string, string)> objects, - IProgress? progress - ) - { - EnsureStarted(); - List> tasks = new(); - IReadOnlyList> splitObjects; - - // request count optimization: if objects are < 500k, send in 1 request - int totalSize = 0; - foreach ((_, string json) in objects) - { - totalSize += json.Length; - if (totalSize >= 500_000) - { - break; - } - } - splitObjects = - totalSize >= 500_000 ? SplitList(objects, NumThreads) : new List> { objects }; - - for (int i = 0; i < NumThreads; i++) - { - if (splitObjects.Count <= i || splitObjects[i].Count == 0) - { - continue; - } - - var op = QueueOperation(ServerApiOperation.UploadObjects, (streamId, splitObjects[i], progress)); - tasks.Add(op); - } - await Task.WhenAll(tasks.ToArray()).ConfigureAwait(false); - } - - public async Task UploadBlobs( - string streamId, - IReadOnlyList<(string, string)> blobs, - IProgress? progress - ) - { - EnsureStarted(); - var op = QueueOperation(ServerApiOperation.UploadBlobs, (streamId, blobs, progress)); - await op.ConfigureAwait(false); - } - - public async Task DownloadBlobs(string streamId, IReadOnlyList blobIds, IProgress? progress) - { - EnsureStarted(); - var op = QueueOperation(ServerApiOperation.DownloadBlobs, (streamId, blobIds, progress)); - await op.ConfigureAwait(false); - } - - public async Task> HasBlobs(string streamId, IReadOnlyList<(string, string)> blobs) - { - EnsureStarted(); - Task op = QueueOperation(ServerApiOperation.HasBlobs, (streamId, blobs)); - var res = (List?)await op.ConfigureAwait(false); - Debug.Assert(res is not null); - return res.NotNull(); - } - - #endregion - - public void EnsureStarted() - { - if (Threads.Count == 0) - { - Start(); - } - } - - protected override void ThreadMain() - { - using ServerApi serialApi = new(_http, _activityFactory, _baseUri, _authToken, BlobStorageFolder, _timeoutSeconds); - serialApi.CancellationToken = CancellationToken; - serialApi.CompressPayloads = CompressPayloads; - - while (true) - { - if (IsDisposed) - { - return; - } - - var (operation, inputValue, tcs) = Tasks.Take(); - - if (operation == ServerApiOperation.NoOp || tcs == null) - { - return; - } - - try - { - var result = RunOperation(operation, inputValue.NotNull(), serialApi).GetAwaiter().GetResult(); - tcs.SetResult(result); - } - catch (Exception ex) - { - tcs.SetException(ex); - - if (ex.IsFatal()) - { - throw; - } - } - } - } - - private static async Task RunOperation(ServerApiOperation operation, object inputValue, ServerApi serialApi) - { - switch (operation) - { - case ServerApiOperation.DownloadSingleObject: - var (dsoStreamId, dsoObjectId, progress) = ((string, string, IProgress?))inputValue; - return await serialApi.DownloadSingleObject(dsoStreamId, dsoObjectId, progress).ConfigureAwait(false); - case ServerApiOperation.DownloadObjects: - var (doStreamId, doObjectIds, progress2, doCallback) = (( - string, - IReadOnlyList, - IProgress?, - CbObjectDownloaded - ))inputValue; - await serialApi.DownloadObjects(doStreamId, doObjectIds, progress2, doCallback).ConfigureAwait(false); - return null; - case ServerApiOperation.HasObjects: - var (hoStreamId, hoObjectIds) = ((string, IReadOnlyList))inputValue; - return await serialApi.HasObjects(hoStreamId, hoObjectIds).ConfigureAwait(false); - case ServerApiOperation.UploadObjects: - var (uoStreamId, uoObjects, progress3) = (( - string, - IReadOnlyList<(string, string)>, - IProgress? - ))inputValue; - await serialApi.UploadObjects(uoStreamId, uoObjects, progress3).ConfigureAwait(false); - return null; - case ServerApiOperation.UploadBlobs: - var (ubStreamId, ubBlobs, progress4) = (( - string, - IReadOnlyList<(string, string)>, - IProgress? - ))inputValue; - await serialApi.UploadBlobs(ubStreamId, ubBlobs, progress4).ConfigureAwait(false); - return null; - case ServerApiOperation.HasBlobs: - var (hbStreamId, hBlobs) = ((string, IReadOnlyList<(string, string)>))inputValue; - return await serialApi - .HasBlobs(hbStreamId, hBlobs.Select(b => b.Item1.Split(':')[1]).ToList()) - .ConfigureAwait(false); - case ServerApiOperation.DownloadBlobs: - var (dbStreamId, blobIds, progress5) = ((string, IReadOnlyList, IProgress?))inputValue; - await serialApi.DownloadBlobs(dbStreamId, blobIds, progress5).ConfigureAwait(false); - return null; - default: - throw new ArgumentOutOfRangeException(nameof(operation), operation, null); - } - } - - private Task QueueOperation(ServerApiOperation operation, object? inputValue) - { - TaskCompletionSource tcs = new(TaskCreationOptions.RunContinuationsAsynchronously); - Tasks.Add(new(operation, inputValue, tcs)); - return tcs.Task; - } - - private static List> SplitList(IReadOnlyList list, int parts) - { - List> ret = new(parts); - for (int i = 0; i < parts; i++) - { - ret.Add(new List(list.Count / parts + 1)); - } - - for (int i = 0; i < list.Count; i++) - { - ret[i % parts].Add(list[i]); - } - - return ret; - } -} diff --git a/src/Speckle.Sdk/Transports/ServerUtils/ServerAPI.cs b/src/Speckle.Sdk/Transports/ServerUtils/ServerAPI.cs deleted file mode 100644 index 68ac2a74..00000000 --- a/src/Speckle.Sdk/Transports/ServerUtils/ServerAPI.cs +++ /dev/null @@ -1,460 +0,0 @@ -using System.Net; -using System.Net.Http.Headers; -using System.Text; -using Speckle.Newtonsoft.Json; -using Speckle.Newtonsoft.Json.Linq; -using Speckle.Sdk.Common; -using Speckle.Sdk.Helpers; -using Speckle.Sdk.Logging; -using Speckle.Sdk.Models; - -namespace Speckle.Sdk.Transports.ServerUtils; - -public sealed class ServerApi : IDisposable, IServerApi -{ - private readonly ISdkActivityFactory _activityFactory; - private const int BATCH_SIZE_GET_OBJECTS = 10000; - private const int BATCH_SIZE_HAS_OBJECTS = 100000; - - private const int MAX_MULTIPART_COUNT = 5; - private const int MAX_MULTIPART_SIZE = 25_000_000; - private const int MAX_OBJECT_SIZE = 25_000_000; - - private const int MAX_REQUEST_SIZE = 100_000_000; - - private static readonly char[] s_separator = { '\t' }; - private static readonly string[] s_filenameSeparator = { "filename=" }; - - private readonly HttpClient _client; - - public ServerApi( - ISpeckleHttp speckleHttp, - ISdkActivityFactory activityFactory, - Uri baseUri, - string? authorizationToken, - string blobStorageFolder, - int timeoutSeconds = 120 - ) - { - _activityFactory = activityFactory; - CancellationToken = CancellationToken.None; - - BlobStorageFolder = blobStorageFolder; - - _client = speckleHttp.CreateHttpClient( - new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip }, - timeoutSeconds: timeoutSeconds, - authorizationToken: authorizationToken - ); - _client.BaseAddress = baseUri; - } - - public CancellationToken CancellationToken { get; set; } - public bool CompressPayloads { get; set; } = true; - - public string BlobStorageFolder { get; set; } - - public void Dispose() - { - _client.Dispose(); - } - - public async Task DownloadSingleObject(string streamId, string objectId, IProgress? progress) - { - using var _ = _activityFactory.Start(); - CancellationToken.ThrowIfCancellationRequested(); - - // Get root object - using var rootHttpMessage = new HttpRequestMessage - { - RequestUri = new Uri($"/objects/{streamId}/{objectId}/single", UriKind.Relative), - Method = HttpMethod.Get, - }; - - var rootHttpResponse = await _client - .SendAsync(rootHttpMessage, HttpCompletionOption.ResponseContentRead, CancellationToken) - .ConfigureAwait(false); - - string? rootObjectStr = null; - await ResponseProgress(rootHttpResponse, progress, (_, json) => rootObjectStr = json, true).ConfigureAwait(false); - return rootObjectStr; - } - - public async Task DownloadObjects( - string streamId, - IReadOnlyList objectIds, - IProgress? progress, - CbObjectDownloaded onObjectCallback - ) - { - if (objectIds.Count == 0) - { - return; - } - using var _ = _activityFactory.Start(); - - if (objectIds.Count < BATCH_SIZE_GET_OBJECTS) - { - await DownloadObjectsImpl(streamId, objectIds, progress, onObjectCallback).ConfigureAwait(false); - return; - } - - List crtRequest = new(); - foreach (string id in objectIds) - { - if (crtRequest.Count >= BATCH_SIZE_GET_OBJECTS) - { - await DownloadObjectsImpl(streamId, crtRequest, progress, onObjectCallback).ConfigureAwait(false); - crtRequest = new List(); - } - crtRequest.Add(id); - } - await DownloadObjectsImpl(streamId, crtRequest, progress, onObjectCallback).ConfigureAwait(false); - } - - public async Task> HasObjects(string streamId, IReadOnlyList objectIds) - { - if (objectIds.Count <= BATCH_SIZE_HAS_OBJECTS) - { - return await HasObjectsImpl(streamId, objectIds).ConfigureAwait(false); - } - - Dictionary ret = new(); - List crtBatch = new(BATCH_SIZE_HAS_OBJECTS); - foreach (string objectId in objectIds) - { - crtBatch.Add(objectId); - if (crtBatch.Count >= BATCH_SIZE_HAS_OBJECTS) - { - Dictionary batchResult = await HasObjectsImpl(streamId, crtBatch).ConfigureAwait(false); - foreach (KeyValuePair kv in batchResult) - { - ret[kv.Key] = kv.Value; - } - - crtBatch = new List(BATCH_SIZE_HAS_OBJECTS); - } - } - if (crtBatch.Count > 0) - { - Dictionary batchResult = await HasObjectsImpl(streamId, crtBatch).ConfigureAwait(false); - foreach (KeyValuePair kv in batchResult) - { - ret[kv.Key] = kv.Value; - } - } - return ret; - } - - public async Task UploadObjects( - string streamId, - IReadOnlyList<(string, string)> objects, - IProgress? progress - ) - { - if (objects.Count == 0) - { - return; - } - - // 1. Split into parts of MAX_MULTIPART_SIZE size (can be exceptions until a max of MAX_OBJECT_SIZE if a single obj is larger than MAX_MULTIPART_SIZE) - List> multipartedObjects = new(); - List multipartedObjectsSize = new(); - - List<(string, string)> crtMultipart = new(); - int crtMultipartSize = 0; - - foreach ((string id, string json) in objects) - { - int objSize = Encoding.UTF8.GetByteCount(json); - if (objSize > MAX_OBJECT_SIZE) - { - throw new ArgumentException( - $"Object {id} too large (size {objSize}, max size {MAX_OBJECT_SIZE}). Consider using detached/chunked properties", - nameof(objects) - ); - } - - if (crtMultipartSize + objSize <= MAX_MULTIPART_SIZE) - { - crtMultipart.Add((id, json)); - crtMultipartSize += objSize; - continue; - } - - // new multipart - if (crtMultipart.Count > 0) - { - multipartedObjects.Add(crtMultipart); - multipartedObjectsSize.Add(crtMultipartSize); - } - crtMultipart = new List<(string, string)> { (id, json) }; - crtMultipartSize = objSize; - } - multipartedObjects.Add(crtMultipart); - multipartedObjectsSize.Add(crtMultipartSize); - - // 2. Split multiparts into individual server requests of max size MAX_REQUEST_SIZE or max length MAX_MULTIPART_COUNT and send them - List> crtRequest = new(); - int crtRequestSize = 0; - for (int i = 0; i < multipartedObjects.Count; i++) - { - List<(string, string)> multipart = multipartedObjects[i]; - int multipartSize = multipartedObjectsSize[i]; - if (crtRequestSize + multipartSize > MAX_REQUEST_SIZE || crtRequest.Count >= MAX_MULTIPART_COUNT) - { - await UploadObjectsImpl(streamId, crtRequest, progress).ConfigureAwait(false); - crtRequest = new List>(); - crtRequestSize = 0; - } - crtRequest.Add(multipart); - crtRequestSize += multipartSize; - } - if (crtRequest.Count > 0) - { - await UploadObjectsImpl(streamId, crtRequest, progress).ConfigureAwait(false); - } - } - - public async Task UploadBlobs( - string streamId, - IReadOnlyList<(string, string)> objects, - IProgress? progress - ) - { - CancellationToken.ThrowIfCancellationRequested(); - if (objects.Count == 0) - { - return; - } - - var multipartFormDataContent = new MultipartFormDataContent(); - var streams = new List(); - foreach (var (id, filePath) in objects) - { - var fileName = Path.GetFileName(filePath); - var stream = File.OpenRead(filePath); - streams.Add(stream); - StreamContent fsc = new(stream); - var hash = id.Split(':')[1]; - - multipartFormDataContent.Add(fsc, $"hash:{hash}", fileName); - } - - using var message = new HttpRequestMessage(); - message.RequestUri = new Uri($"/api/stream/{streamId}/blob", UriKind.Relative); - message.Method = HttpMethod.Post; - message.Content = new ProgressContent(multipartFormDataContent, progress); - - try - { - var response = await _client.SendAsync(message, CancellationToken).ConfigureAwait(false); - - response.EnsureSuccessStatusCode(); - - foreach (var stream in streams) - { - stream.Dispose(); - } - } - finally - { - foreach (var stream in streams) - { - stream.Dispose(); - } - } - } - - public async Task DownloadBlobs(string streamId, IReadOnlyList blobIds, IProgress? progress) - { - foreach (var blobId in blobIds) - { - try - { - using var blobMessage = new HttpRequestMessage(); - blobMessage.RequestUri = new Uri($"api/stream/{streamId}/blob/{blobId}", UriKind.Relative); - blobMessage.Method = HttpMethod.Get; - - using var response = await _client.SendAsync(blobMessage, CancellationToken).ConfigureAwait(false); - response.Content.Headers.TryGetValues("Content-Disposition", out IEnumerable? cdHeaderValues); - - var cdHeader = cdHeaderValues?.FirstOrDefault(); - string? fileName = cdHeader?.Split(s_filenameSeparator, StringSplitOptions.None)[1].TrimStart('"').TrimEnd('"'); - - string fileLocation = Path.Combine(BlobStorageFolder, $"{blobId[..Blob.LocalHashPrefixLength]}-{fileName}"); - using var source = new ProgressStream( - await response.Content.ReadAsStreamAsync().ConfigureAwait(false), - response.Content.Headers.ContentLength, - progress, - true - ); - using var fs = new FileStream(fileLocation, FileMode.OpenOrCreate); - await source.CopyToAsync(fs).ConfigureAwait(false); - } - catch (Exception ex) when (!ex.IsFatal()) - { - throw new SpeckleException($"Failed to download blob {blobId}", ex); - } - } - } - - private async Task DownloadObjectsImpl( - string streamId, - IReadOnlyList objectIds, - IProgress? progress, - CbObjectDownloaded onObjectCallback - ) - { - // Stopwatch sw = new Stopwatch(); sw.Start(); - - CancellationToken.ThrowIfCancellationRequested(); - - using var childrenHttpMessage = new HttpRequestMessage - { - RequestUri = new Uri($"/api/getobjects/{streamId}", UriKind.Relative), - Method = HttpMethod.Post, - }; - - Dictionary postParameters = new() { { "objects", JsonConvert.SerializeObject(objectIds) } }; - string serializedPayload = JsonConvert.SerializeObject(postParameters); - childrenHttpMessage.Content = new StringContent(serializedPayload, Encoding.UTF8, "application/json"); - childrenHttpMessage.Headers.Add("Accept", "text/plain"); - - HttpResponseMessage childrenHttpResponse = await _client - .SendAsync(childrenHttpMessage, CancellationToken) - .ConfigureAwait(false); - - await ResponseProgress(childrenHttpResponse, progress, onObjectCallback, false).ConfigureAwait(false); - } - - private async Task ResponseProgress( - HttpResponseMessage childrenHttpResponse, - IProgress? progress, - CbObjectDownloaded onObjectCallback, - bool isSingle - ) - { - childrenHttpResponse.EnsureSuccessStatusCode(); - var length = childrenHttpResponse.Content.Headers.ContentLength; - using Stream childrenStream = await childrenHttpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); - - using var reader = new StreamReader(new ProgressStream(childrenStream, length, progress, true), Encoding.UTF8); - while (await reader.ReadLineAsync().ConfigureAwait(false) is { } line) - { - CancellationToken.ThrowIfCancellationRequested(); - - if (!isSingle) - { - var pcs = line.Split(s_separator, 2); - onObjectCallback(pcs[0], pcs[1]); - } - else - { - onObjectCallback(string.Empty, line); - break; - } - } - } - - private async Task> HasObjectsImpl(string streamId, IReadOnlyList objectIds) - { - CancellationToken.ThrowIfCancellationRequested(); - - // Stopwatch sw = new Stopwatch(); sw.Start(); - - string objectsPostParameter = JsonConvert.SerializeObject(objectIds); - var payload = new Dictionary { { "objects", objectsPostParameter } }; - string serializedPayload = JsonConvert.SerializeObject(payload); - var uri = new Uri($"/api/diff/{streamId}", UriKind.Relative); - - using StringContent stringContent = new(serializedPayload, Encoding.UTF8, "application/json"); - var response = await _client.PostAsync(uri, stringContent, CancellationToken).ConfigureAwait(false); - - response.EnsureSuccessStatusCode(); - - var hasObjectsJson = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - Dictionary hasObjects = new(); - - JObject doc = JObject.Parse(hasObjectsJson); - foreach (KeyValuePair prop in doc) - { - hasObjects[prop.Key] = (bool)prop.Value.NotNull(); - } - return hasObjects; - } - - private async Task UploadObjectsImpl( - string streamId, - List> multipartedObjects, - IProgress? progress - ) - { - CancellationToken.ThrowIfCancellationRequested(); - - using HttpRequestMessage message = new() - { - RequestUri = new Uri($"/objects/{streamId}", UriKind.Relative), - Method = HttpMethod.Post, - }; - - MultipartFormDataContent multipart = new(); - - int mpId = 0; - foreach (List<(string, string)> mpData in multipartedObjects) - { - mpId++; - - var ctBuilder = new StringBuilder("["); - for (int i = 0; i < mpData.Count; i++) - { - if (i > 0) - { - ctBuilder.Append(','); - } - - ctBuilder.Append(mpData[i].Item2); - } - ctBuilder.Append(']'); - string ct = ctBuilder.ToString(); - - if (CompressPayloads) - { - var content = new GzipContent(new StringContent(ct, Encoding.UTF8)); - content.Headers.ContentType = new MediaTypeHeaderValue("application/gzip"); - multipart.Add(content, $"batch-{mpId}", $"batch-{mpId}"); - } - else - { - multipart.Add(new StringContent(ct, Encoding.UTF8), $"batch-{mpId}", $"batch-{mpId}"); - } - } - message.Content = new ProgressContent(multipart, progress); - var response = await _client.SendAsync(message, CancellationToken).ConfigureAwait(false); - - response.EnsureSuccessStatusCode(); - } - - public async Task> HasBlobs(string streamId, IReadOnlyList blobIds) - { - CancellationToken.ThrowIfCancellationRequested(); - - var payload = JsonConvert.SerializeObject(blobIds); - var uri = new Uri($"/api/stream/{streamId}/blob/diff", UriKind.Relative); - - using StringContent stringContent = new(payload, Encoding.UTF8, "application/json"); - - var response = await _client.PostAsync(uri, stringContent, CancellationToken).ConfigureAwait(false); - - response.EnsureSuccessStatusCode(); - - var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var parsed = JsonConvert.DeserializeObject>(responseString); - if (parsed is null) - { - throw new SpeckleException($"Failed to deserialize successful response {response.Content}"); - } - - return parsed; - } -} diff --git a/src/Speckle.Sdk/Transports/TransportHelpers.cs b/src/Speckle.Sdk/Transports/TransportHelpers.cs deleted file mode 100644 index cdc52c98..00000000 --- a/src/Speckle.Sdk/Transports/TransportHelpers.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Speckle.Sdk.Serialisation.Utilities; - -namespace Speckle.Sdk.Transports; - -public static class TransportHelpers -{ - public static async Task CopyObjectAndChildrenAsync( - string id, - ITransport sourceTransport, - ITransport targetTransport, - CancellationToken cancellationToken - ) - { - if (string.IsNullOrEmpty(id)) - { - throw new ArgumentException("Cannot copy object with empty id", nameof(id)); - } - - cancellationToken.ThrowIfCancellationRequested(); - - var parent = await sourceTransport.GetObject(id).ConfigureAwait(false); - if (parent is null) - { - throw new TransportException( - $"Requested id {id} was not found within this transport {sourceTransport.TransportName}" - ); - } - - targetTransport.SaveObject(id, parent); - - var closures = ClosureParser.GetChildrenIds(parent, cancellationToken).ToList(); - - foreach (var closure in closures) - { - cancellationToken.ThrowIfCancellationRequested(); - - //skips blobs because ServerTransport downloads things separately - if (closure.StartsWith("blob:")) - { - continue; - } - var child = await sourceTransport.GetObject(closure).ConfigureAwait(false); - if (child is null) - { - throw new TransportException( - $"Closure id {closure} was not found within this transport {sourceTransport.TransportName}" - ); - } - - targetTransport.SaveObject(closure, child); - } - - return parent; - } - - [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Deserialization target for DTO")] - internal sealed class Placeholder - { - public Dictionary? __closure { get; set; } - } -} diff --git a/src/Speckle.Sdk/Transports/Utilities.cs b/src/Speckle.Sdk/Transports/Utilities.cs deleted file mode 100644 index 3fe06faf..00000000 --- a/src/Speckle.Sdk/Transports/Utilities.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Speckle.Sdk.Transports; - -public static class Utilities -{ - /// - /// Waits until the provided function returns true. - /// - /// - /// - /// - public static async Task WaitUntil(Func condition, int frequency = 25) - { - while (!condition()) - { - await Task.Delay(frequency).ConfigureAwait(false); - } - } -} diff --git a/tests/Speckle.Objects.Tests.Unit/ModelPropertySupportedTypes.cs b/tests/Speckle.Objects.Tests.Unit/ModelPropertySupportedTypes.cs index c3e8e805..68936423 100644 --- a/tests/Speckle.Objects.Tests.Unit/ModelPropertySupportedTypes.cs +++ b/tests/Speckle.Objects.Tests.Unit/ModelPropertySupportedTypes.cs @@ -4,7 +4,6 @@ using Speckle.Newtonsoft.Json; using Speckle.Sdk.Host; using Speckle.Sdk.Models; -using Speckle.Sdk.Serialisation; namespace Speckle.Objects.Tests.Unit; @@ -28,7 +27,6 @@ public ModelPropertySupportedTypes() /// /// If you're tempted to add to this list, please ensure both our serializer and deserializer support properties of this type /// Check the - /// Check the /// (or is an interface where all concrete types are supported) /// You should also consider adding a test in SerializerNonBreakingChanges /// diff --git a/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs b/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs index f41a5c10..693c5ab4 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs @@ -48,21 +48,6 @@ public async Task CanSerialize_New_Detached() await VerifyJsonDictionary(objects); } - [Fact] - public async Task CanSerialize_Old_Detached() - { - var @base = new SampleObjectBase(); - @base["dynamicProp"] = 123; - @base.detachedProp = new SamplePropBase() { name = "detachedProp" }; - @base.attachedProp = new SamplePropBase() { name = "attachedProp" }; - - var objects = new ConcurrentDictionary(); - var serializer = new SpeckleObjectSerializer(new[] { new MemoryTransport(objects) }); - serializer.Serialize(@base); - - await VerifyJsonDictionary(objects); - } - [Fact] public async Task GetPropertiesExpected_Detached() { diff --git a/tests/Speckle.Sdk.Serialization.Tests/Framework/TestTransport.cs b/tests/Speckle.Sdk.Serialization.Tests/Framework/TestTransport.cs deleted file mode 100644 index 0ca8d458..00000000 --- a/tests/Speckle.Sdk.Serialization.Tests/Framework/TestTransport.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Speckle.Sdk.Transports; - -namespace Speckle.Sdk.Serialization.Tests.Framework; - -public class TestTransport(IReadOnlyDictionary objects) : ITransport -{ - public string TransportName - { - get => "Test"; - set { } - } - - public Dictionary TransportContext { get; } - public TimeSpan Elapsed { get; } - public int SavedObjectCount { get; } - public CancellationToken CancellationToken { get; set; } - public IProgress? OnProgressAction { get; set; } - public Action? OnErrorAction { get; set; } - - public void BeginWrite() => throw new NotImplementedException(); - - public void EndWrite() => throw new NotImplementedException(); - - public void SaveObject(string id, string serializedObject) => throw new NotImplementedException(); - - public Task WriteComplete() => throw new NotImplementedException(); - - public Task GetObject(string id) => Task.FromResult(objects.GetValueOrDefault(id)); - - public Task CopyObjectAndChildren(string id, ITransport targetTransport) => - throw new NotImplementedException(); - - public Task> HasObjects(IReadOnlyList objectIds) => - throw new NotImplementedException(); -} diff --git a/tests/Speckle.Sdk.Serialization.Tests/Framework/TestTransport2.cs b/tests/Speckle.Sdk.Serialization.Tests/Framework/TestTransport2.cs deleted file mode 100644 index e9a3f1f6..00000000 --- a/tests/Speckle.Sdk.Serialization.Tests/Framework/TestTransport2.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Speckle.Sdk.Transports; - -namespace Speckle.Sdk.Serialization.Tests.Framework; - -public class TestTransport2(IDictionary objects) : ITransport -{ - public IDictionary Objects { get; } = objects; - - public string TransportName - { - get => "Test"; - set { } - } - - public Dictionary TransportContext { get; } - public TimeSpan Elapsed { get; } - public int SavedObjectCount { get; } - public CancellationToken CancellationToken { get; set; } - public IProgress? OnProgressAction { get; set; } - public Action? OnErrorAction { get; set; } - - public void BeginWrite() => throw new NotImplementedException(); - - public void EndWrite() => throw new NotImplementedException(); - - public void SaveObject(string id, string serializedObject) => Objects[id] = serializedObject; - - public Task WriteComplete() => throw new NotImplementedException(); - - public Task GetObject(string id) => Task.FromResult(Objects.TryGetValue(id, out string? o) ? o : null); - - public Task CopyObjectAndChildren(string id, ITransport targetTransport) => - throw new NotImplementedException(); - - public Task> HasObjects(IReadOnlyList objectIds) => - throw new NotImplementedException(); -} diff --git a/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs b/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs deleted file mode 100644 index f2b3bc6a..00000000 --- a/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs +++ /dev/null @@ -1,250 +0,0 @@ -using System.Collections.Concurrent; -using FluentAssertions; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging.Abstractions; -using Speckle.Newtonsoft.Json; -using Speckle.Newtonsoft.Json.Linq; -using Speckle.Objects.Geometry; -using Speckle.Sdk.Common; -using Speckle.Sdk.Host; -using Speckle.Sdk.Models; -using Speckle.Sdk.Serialisation; -using Speckle.Sdk.Serialisation.Utilities; -using Speckle.Sdk.Serialisation.V2; -using Speckle.Sdk.Serialisation.V2.Receive; -using Speckle.Sdk.Serialisation.V2.Send; -using Speckle.Sdk.Serialization.Tests.Framework; -using Speckle.Sdk.SQLite; -using Speckle.Sdk.Testing.Framework; - -namespace Speckle.Sdk.Serialization.Tests; - -public class SerializationTests -{ - private readonly ISerializeProcessFactory _factory; - - public SerializationTests() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSpeckleSdk(new("Tests", "test"), "v3", typeof(TestClass).Assembly, typeof(Polyline).Assembly); - var serviceProvider = serviceCollection.BuildServiceProvider(); - - _factory = serviceProvider.GetRequiredService(); - } - - public class TestObjectLoader(IReadOnlyDictionary idToObject) : IObjectLoader - { - public Task<(Json, IReadOnlyCollection)> GetAndCache(string rootId, DeserializeProcessOptions? options) - { - var json = idToObject.GetValueOrDefault(rootId); - if (json == null) - { - throw new KeyNotFoundException("Root not found"); - } - - var allChildren = ClosureParser.GetChildrenIds(json, default).Select(x => new Id(x)).ToList(); - return Task.FromResult<(Json, IReadOnlyCollection)>((new(json), allChildren)); - } - - public string? LoadId(string id) => idToObject.GetValueOrDefault(id); - - public void Dispose() { } - } - - /* [Theory] - [InlineData("RevitObject.json.gz")] - public async Task Basic_Namespace_Validation(string fileName) - { - var closures = TestFileManager.GetFileAsClosures(fileName); - var deserializer = new SpeckleObjectDeserializer - { - ReadTransport = new TestTransport(closures), - CancellationToken = default, - }; - - foreach (var (id, objJson) in closures) - { - var jObject = JObject.Parse(objJson); - var oldSpeckleType = jObject["speckle_type"].NotNull().Value().NotNull(); - var starts = oldSpeckleType.StartsWith("Speckle.Core.") || oldSpeckleType.StartsWith("Objects."); - starts.Should().BeTrue($"{oldSpeckleType} isn't expected"); - - var baseType = await deserializer.DeserializeAsync(objJson); - baseType.id.Should().Be(id); - - var oldType = TypeLoader.GetAtomicType(oldSpeckleType); - if (oldType == typeof(Base)) - { - oldSpeckleType.Should().NotContain("Base"); - } - else - { - starts = baseType.speckle_type.StartsWith("Speckle.Core.") || baseType.speckle_type.StartsWith("Objects."); - starts.Should().BeTrue($"{baseType.speckle_type} isn't expected"); - - var type = TypeLoader.GetAtomicType(baseType.speckle_type); - type.Should().NotBeNull(); - var name = TypeLoader.GetTypeString(type) ?? throw new ArgumentNullException($"Could not find: {type}"); - starts = name.StartsWith("Speckle.Core") || name.StartsWith("Objects"); - starts.Should().BeTrue($"{name} isn't expected"); - } - } - }*/ - - [Theory] - [InlineData("RevitObject.json.gz")] - public async Task Basic_Namespace_Validation_New(string fileName) - { - var closures = TestFileManager.GetFileAsClosures(fileName); - await using var process = new DeserializeProcess( - new TestObjectLoader(closures), - null, - new BaseDeserializer(new ObjectDeserializerFactory()), - new NullLoggerFactory(), - default - ); - await process.Deserialize("3416d3fe01c9196115514c4a2f41617b"); - foreach (var (id, objJson) in closures) - { - var jObject = JObject.Parse(objJson); - var oldSpeckleType = jObject["speckle_type"].NotNull().Value().NotNull(); - var starts = oldSpeckleType.StartsWith("Speckle.Core.") || oldSpeckleType.StartsWith("Objects."); - starts.Should().BeTrue($"{oldSpeckleType} isn't expected"); - - var oldType = TypeLoader.GetAtomicType(oldSpeckleType); - if (oldType == typeof(Base)) - { - oldSpeckleType.Should().NotContain("Base"); - } - else - { - var baseType = process.BaseCache[new Id(id)]; - - starts = baseType.speckle_type.StartsWith("Speckle.Core.") || baseType.speckle_type.StartsWith("Objects."); - starts.Should().BeTrue($"{baseType.speckle_type} isn't expected"); - - var type = TypeLoader.GetAtomicType(baseType.speckle_type); - type.Should().NotBeNull(); - var name = TypeLoader.GetTypeString(type) ?? throw new ArgumentNullException(); - starts = name.StartsWith("Speckle.Core") || name.StartsWith("Objects"); - starts.Should().BeTrue($"{name} isn't expected"); - } - } - } - - [Theory] - [InlineData( - "{\"applicationId\":null,\"speckle_type\":\"Base\",\"IFC_GUID\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcGUID\",\"units\":null,\"value\":\"18HX_ys0P5uu77f1wwA7bn\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"IFC_GUID\",\"id\":\"1f4e29b7198e25221300c684876ec187\"},\"DOOR_COST\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Cost\",\"units\":\"\u0e3f\",\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:currency-1.0.0\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"DOOR_COST\",\"id\":\"80ff4c5df5170b75916a873a394cfbdf\"},\"ALL_MODEL_URL\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"URL\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_URL\",\"id\":\"140c53fcea5deaa35115b23cd2ba48c6\"},\"IFC_TYPE_GUID\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type IfcGUID\",\"units\":null,\"value\":\"0w69BRwHvBsBXN3bEBjQin\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"IFC_TYPE_GUID\",\"id\":\"99d5d914df5c50c879e73c50246a9249\"},\"KEYNOTE_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Keynote\",\"units\":null,\"value\":\"S0905\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"KEYNOTE_PARAM\",\"id\":\"c2272311800b04ab4d2b0052df68ecdc\"},\"PHASE_CREATED\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Phase Created\",\"units\":null,\"value\":\"0\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"PHASE_CREATED\",\"id\":\"72ecbbd5d29ea1b48df89d8f88b29120\"},\"ALL_MODEL_MARK\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Mark\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_MARK\",\"id\":\"f2e0ed6ebfbab4d4780c5143b774558e\"},\"FUNCTION_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Function\",\"units\":null,\"value\":0,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"FUNCTION_PARAM\",\"id\":\"a43000484f3fa3c5cf60a2ccd79a573c\"},\"UNIFORMAT_CODE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Assembly Code\",\"units\":null,\"value\":\"\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"UNIFORMAT_CODE\",\"id\":\"b797bb20d49af57eecbe718df4ebd411\"},\"WINDOW_TYPE_ID\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Mark\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"WINDOW_TYPE_ID\",\"id\":\"d74f026a13a539bd24369ea78b34aa6b\"},\"ALL_MODEL_IMAGE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Image\",\"units\":null,\"value\":\"-1\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_IMAGE\",\"id\":\"4ef25c5fcd2ee32d9b3d6ce9b1047904\"},\"ALL_MODEL_MODEL\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Model\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_MODEL\",\"id\":\"13597261389f532c0778e134623eff85\"},\"CLEAR_COVER_TOP\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Rebar Cover - Top Face\",\"units\":null,\"value\":\"95743\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"CLEAR_COVER_TOP\",\"id\":\"ed5f5e056314ee8435a1658a54261e94\"},\"ELEM_TYPE_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type\",\"units\":null,\"value\":\"5432827\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ELEM_TYPE_PARAM\",\"id\":\"e502cb5aed1fda357926c7ca9927c42c\"},\"RELATED_TO_MASS\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Related to Mass\",\"units\":null,\"value\":false,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"RELATED_TO_MASS\",\"id\":\"a43b8424564b8f14738f4dbaa78be150\"},\"SYMBOL_ID_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Id\",\"units\":null,\"value\":\"5432827\",\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"SYMBOL_ID_PARAM\",\"id\":\"1947cb98e61f79da57f573a3a785b436\"},\"DESIGN_OPTION_ID\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Design Option\",\"units\":null,\"value\":\"-1\",\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"DESIGN_OPTION_ID\",\"id\":\"cf30f731c41543dd134a4877fbdab105\"},\"PHASE_DEMOLISHED\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Phase Demolished\",\"units\":null,\"value\":\"-1\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"PHASE_DEMOLISHED\",\"id\":\"47066bac7728f9b93f4acdb697284a59\"},\"CLEAR_COVER_OTHER\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Rebar Cover - Other Faces\",\"units\":null,\"value\":\"95743\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"CLEAR_COVER_OTHER\",\"id\":\"1afe7d22a897aff809bd92aea1acafd2\"},\"ELEM_FAMILY_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Family\",\"units\":null,\"value\":\"5432827\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ELEM_FAMILY_PARAM\",\"id\":\"ec3a159572a58b85b3a3650e5cc23e90\"},\"CLEAR_COVER_BOTTOM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Rebar Cover - Bottom Face\",\"units\":null,\"value\":\"95743\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"CLEAR_COVER_BOTTOM\",\"id\":\"476161776fc4c6ceb3c544c792a08120\"},\"HOST_AREA_COMPUTED\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Area\",\"units\":\"m\u00b2\",\"value\":7.128858225722908,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:squareMeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"HOST_AREA_COMPUTED\",\"id\":\"5e7567fea07a98c0cdd4903cabd897a3\"},\"IFC_EXPORT_ELEMENT\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Export to IFC\",\"units\":null,\"value\":0,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"IFC_EXPORT_ELEMENT\",\"id\":\"7de623e201f3fcfb16dbcacefe7f8403\"},\"totalChildrenCount\":0,\"ALL_MODEL_TYPE_NAME\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Name\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_TYPE_NAME\",\"id\":\"4699f3fc2fd4e84cc3b6296ded7225b5\"},\"DESIGN_OPTION_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Design Option\",\"units\":null,\"value\":\"Main Model\",\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"DESIGN_OPTION_PARAM\",\"id\":\"771449eae7f2fb96345b165954b2c797\"},\"ELEM_CATEGORY_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Category\",\"units\":null,\"value\":\"-2000032\",\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ELEM_CATEGORY_PARAM\",\"id\":\"fb0b948f7360b9415ea9ede20fb3cdd2\"},\"ALL_MODEL_TYPE_IMAGE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Image\",\"units\":null,\"value\":\"-1\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_TYPE_IMAGE\",\"id\":\"4c95be61c11f5609f1fa649804bf9814\"},\"ANALYTICAL_ROUGHNESS\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Roughness\",\"units\":null,\"value\":1,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ANALYTICAL_ROUGHNESS\",\"id\":\"eab64895ad089cf272ae6a7431f4cdac\"},\"HOST_VOLUME_COMPUTED\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Volume\",\"units\":\"m\u00b3\",\"value\":1.413211687704679,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:cubicMeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"HOST_VOLUME_COMPUTED\",\"id\":\"4cac83d2757bc70d7e1f299de124d028\"},\"SCHEDULE_LEVEL_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Level\",\"units\":null,\"value\":\"1100600\",\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"SCHEDULE_LEVEL_PARAM\",\"id\":\"d0ab715757ddbaedf5dc2df0726ed38c\"},\"ALL_MODEL_DESCRIPTION\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Description\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_DESCRIPTION\",\"id\":\"4abdaadbe23c12c349c65abcd5979f56\"},\"IFC_EXPORT_ELEMENT_AS\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Export to IFC As\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"IFC_EXPORT_ELEMENT_AS\",\"id\":\"ccacac43b32ffd10c88a870492f98f96\"},\"UNIFORMAT_DESCRIPTION\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Assembly Description\",\"units\":null,\"value\":\"\",\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"UNIFORMAT_DESCRIPTION\",\"id\":\"abfe7173561e8b86cae8aa8dc34743d1\"},\"ALL_MODEL_MANUFACTURER\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Manufacturer\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_MANUFACTURER\",\"id\":\"e280ae740be9133f1001f218a137bb2f\"},\"ANALYTICAL_ABSORPTANCE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Absorptance\",\"units\":null,\"value\":0.1,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ANALYTICAL_ABSORPTANCE\",\"id\":\"e1618d04224fb3e11e650f8854e5eddb\"},\"ELEM_CATEGORY_PARAM_MT\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Category\",\"units\":null,\"value\":\"-2000032\",\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ELEM_CATEGORY_PARAM_MT\",\"id\":\"d4119e43880a3cc8632a137d4f3372ae\"},\"ALL_MODEL_TYPE_COMMENTS\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Comments\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_TYPE_COMMENTS\",\"id\":\"8ea15d6198e1f5c632df36270be5433e\"},\"ANALYTICAL_THERMAL_MASS\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Thermal Mass\",\"units\":\"kJ/(m\u00b2·K)\",\"value\":null,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:kilojoulesPerSquareMeterKelvin-1.0.0\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ANALYTICAL_THERMAL_MASS\",\"id\":\"d8b711b81d9e0ad7f072b60b69bd0239\"},\"HOST_PERIMETER_COMPUTED\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Perimeter\",\"units\":\"mm\",\"value\":11098.801755409942,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"HOST_PERIMETER_COMPUTED\",\"id\":\"eb73365794668bf73b3ffd2c80162ee1\"},\"IFC_EXPORT_ELEMENT_TYPE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Export Type to IFC\",\"units\":null,\"value\":0,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"IFC_EXPORT_ELEMENT_TYPE\",\"id\":\"0a96867bd313e951c229fb92b346b516\"},\"WALL_ATTR_ROOM_BOUNDING\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Room Bounding\",\"units\":null,\"value\":true,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"WALL_ATTR_ROOM_BOUNDING\",\"id\":\"fdf5bd19ac0a9f2878323c71e4ae80ea\"},\"FLOOR_STRUCTURE_ID_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Structure\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"FLOOR_STRUCTURE_ID_PARAM\",\"id\":\"43b858d8cfaf2bd27cb0b466dc6d425b\"},\"SYMBOL_FAMILY_NAME_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Family Name\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"SYMBOL_FAMILY_NAME_PARAM\",\"id\":\"d96f492f43f2b0e11ce86d66c23caf0f\"},\"IFC_EXPORT_PREDEFINEDTYPE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IFC Predefined Type\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"IFC_EXPORT_PREDEFINEDTYPE\",\"id\":\"b927074616bc0e6e323b52a99867b907\"},\"STRUCTURAL_MATERIAL_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Structural Material\",\"units\":null,\"value\":\"215194\",\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"STRUCTURAL_MATERIAL_PARAM\",\"id\":\"1f7ffc00602d1944892885d68dff8867\"},\"ELEM_FAMILY_AND_TYPE_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Family and Type\",\"units\":null,\"value\":\"5432827\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ELEM_FAMILY_AND_TYPE_PARAM\",\"id\":\"9d58f36db73c0c248a6db682a6c6a6a0\"},\"FLOOR_ATTR_THICKNESS_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Thickness\",\"units\":\"mm\",\"value\":200,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"FLOOR_ATTR_THICKNESS_PARAM\",\"id\":\"22c96d409372e700936805b825b574e6\"},\"IFC_EXPORT_ELEMENT_TYPE_AS\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Export Type to IFC As\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"IFC_EXPORT_ELEMENT_TYPE_AS\",\"id\":\"41a53fec385581b6af53942aff3cd2d3\"},\"ALL_MODEL_INSTANCE_COMMENTS\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Comments\",\"units\":null,\"value\":\"\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_INSTANCE_COMMENTS\",\"id\":\"ca8cdcc0b3fd824a34dcb42749151cd1\"},\"STRUCTURAL_ELEVATION_AT_TOP\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Elevation at Top\",\"units\":\"mm\",\"value\":21099.999999999898,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"STRUCTURAL_ELEVATION_AT_TOP\",\"id\":\"b1f293a63ff03c0c7456f8ba7b703f4f\"},\"FLOOR_HEIGHTABOVELEVEL_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Height Offset From Level\",\"units\":\"mm\",\"value\":-100,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"FLOOR_HEIGHTABOVELEVEL_PARAM\",\"id\":\"312773813c84648fc5ff2d78a8d8d8bc\"},\"ANALYTICAL_THERMAL_RESISTANCE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Thermal Resistance (R)\",\"units\":\"(m\u00b2·K)/W\",\"value\":null,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:squareMeterKelvinsPerWatt-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ANALYTICAL_THERMAL_RESISTANCE\",\"id\":\"fcfa5d36d656d4f8ca2b883a17c310b8\"},\"IFC_EXPORT_PREDEFINEDTYPE_TYPE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type IFC Predefined Type\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"IFC_EXPORT_PREDEFINEDTYPE_TYPE\",\"id\":\"ac166cbccbcd8335272956f09d8d5d42\"},\"STRUCTURAL_ELEVATION_AT_BOTTOM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Elevation at Bottom\",\"units\":\"mm\",\"value\":20899.9999999999,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"STRUCTURAL_ELEVATION_AT_BOTTOM\",\"id\":\"d9e8f0e4b57b00ca99d13df99ea6ac26\"},\"COARSE_SCALE_FILL_PATTERN_COLOR\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Coarse Scale Fill Color\",\"units\":null,\"value\":0,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"COARSE_SCALE_FILL_PATTERN_COLOR\",\"id\":\"854d889fd71071f3b81d0e06f7f1095c\"},\"STRUCTURAL_FLOOR_CORE_THICKNESS\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Core Thickness\",\"units\":\"mm\",\"value\":199.99999999999784,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"STRUCTURAL_FLOOR_CORE_THICKNESS\",\"id\":\"6fb6fb65a394c5c68d5a760289c1129d\"},\"STRUCTURAL_ELEVATION_AT_TOP_CORE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Elevation at Top Core\",\"units\":\"mm\",\"value\":21099.999999999898,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"STRUCTURAL_ELEVATION_AT_TOP_CORE\",\"id\":\"aed8eedfb2527594e14ae4e5f74fb5c1\"},\"ANALYTICAL_ELEMENT_HAS_ASSOCIATION\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Has Association\",\"units\":null,\"value\":true,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ANALYTICAL_ELEMENT_HAS_ASSOCIATION\",\"id\":\"c9a6f771f05ef6072100c59c672dfb77\"},\"COARSE_SCALE_FILL_PATTERN_ID_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Coarse Scale Fill Pattern\",\"units\":null,\"value\":\"-1\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"COARSE_SCALE_FILL_PATTERN_ID_PARAM\",\"id\":\"1d271f4d80ffe772f9f8896971050ccc\"},\"FLOOR_ATTR_DEFAULT_THICKNESS_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Default Thickness\",\"units\":\"mm\",\"value\":200,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"FLOOR_ATTR_DEFAULT_THICKNESS_PARAM\",\"id\":\"271b8b7f7e29c45065c1ccaa1095b32e\"},\"STRUCTURAL_ELEVATION_AT_TOP_SURVEY\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Elevation at Top Survey\",\"units\":\"mm\",\"value\":30899.999999999894,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"STRUCTURAL_ELEVATION_AT_TOP_SURVEY\",\"id\":\"8f2b9f55e373736263d14002838194b4\"},\"STRUCTURAL_ELEVATION_AT_BOTTOM_CORE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Elevation at Bottom Core\",\"units\":\"mm\",\"value\":20899.9999999999,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"STRUCTURAL_ELEVATION_AT_BOTTOM_CORE\",\"id\":\"7a0b7e496383d08605ccb8c776cedbbf\"},\"02b58af4-afcd-404b-9011-3a25d6816e1b\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"ClassificationCode\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"02b58af4-afcd-404b-9011-3a25d6816e1b\",\"id\":\"141f6b021c701e5b4b4ee430652f7f91\"},\"042673e7-8ac4-413d-a393-e0785fbf8889\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Data 3\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"042673e7-8ac4-413d-a393-e0785fbf8889\",\"id\":\"f415ff5c972f45d7e0090b0849c54677\"},\"07afa150-f11f-40a1-a173-7a77ea32cf96\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Data 6\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"07afa150-f11f-40a1-a173-7a77ea32cf96\",\"id\":\"0e3acdf0b385d32d68caa8753c710849\"},\"07b6cf99-a3d2-4d7a-9ea4-246058cfae1a\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Classification.OmniClass.22.Description\",\"units\":null,\"value\":\"High-Tolerance Concrete Floor Finishing\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"07b6cf99-a3d2-4d7a-9ea4-246058cfae1a\",\"id\":\"8957914412a138b6255452dd485a25bd\"},\"082d16bb-7cf9-4968-ac22-b6f6ae068028\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcName\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"082d16bb-7cf9-4968-ac22-b6f6ae068028\",\"id\":\"f313bddfb2c5cf9f825ee6653021b04e\"},\"087f96a5-2dd2-42bb-a170-c22485216c09\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Element Condition\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"087f96a5-2dd2-42bb-a170-c22485216c09\",\"id\":\"8665b9cec7a39bffa030e6b415f78fa9\"},\"098e8d4f-1431-49e8-8ef6-69516cf72354\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"3D Model Element GUID\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"098e8d4f-1431-49e8-8ef6-69516cf72354\",\"id\":\"b81c608ec3bd9aa08ea1a2c5a2cea206\"},\"0a004b99-d4e6-4db6-8c88-9b77da33f012\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcDescription\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"0a004b99-d4e6-4db6-8c88-9b77da33f012\",\"id\":\"79b720b93988fd7840213380399408dd\"},\"0bf3a5d2-06c0-4b6c-9ba1-6985ef40c2b0\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Data 6\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"0bf3a5d2-06c0-4b6c-9ba1-6985ef40c2b0\",\"id\":\"622eab526502ce2a848c4aa932554f96\"},\"0c273fd8-260b-4f34-996e-921fa14a47fc\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Data 4\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"0c273fd8-260b-4f34-996e-921fa14a47fc\",\"id\":\"f70edec21839dfc410d94659d18d52c2\"},\"11f34dfe-4592-4c86-a455-2f020d9376e8\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"ClassificationCode\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"11f34dfe-4592-4c86-a455-2f020d9376e8\",\"id\":\"cd817060f1920a0194139bf2cdddecd4\"},\"12e4c976-0b76-4735-8664-e882b410ac7e\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon A1\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"12e4c976-0b76-4735-8664-e882b410ac7e\",\"id\":\"0d2df557e73500e0c3d72c527d4c36fe\"},\"1336888e-1fed-4e9e-b74b-794bff5b6046\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"GrossArea(BaseQuantities)\",\"units\":\"m\u00b2\",\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:squareMeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"1336888e-1fed-4e9e-b74b-794bff5b6046\",\"id\":\"7e0f5932a6c5ad37872436b3ed0cf07b\"},\"15212817-1c39-4c7f-bae4-436acd0e4598\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcDescription\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"15212817-1c39-4c7f-bae4-436acd0e4598\",\"id\":\"f2bba2dcccf5786df17c279367baab39\"},\"18a3daed-8579-45e2-97a0-412159986104\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Data 8\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"18a3daed-8579-45e2-97a0-412159986104\",\"id\":\"173b9745cf4e7cc4b67748c5a03acf04\"},\"18ab825f-ba4c-4a8f-b509-5ddd7c378267\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Item\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"18ab825f-ba4c-4a8f-b509-5ddd7c378267\",\"id\":\"2c130ba41bf9797a0e7e64315695dd7b\"},\"1948bc31-5a23-482a-b337-4bd1fce08aec\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Renovation Status(AC_Pset_RenovationAndPhasing)\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"1948bc31-5a23-482a-b337-4bd1fce08aec\",\"id\":\"59b7e7b981f77ff4e7b01e7d794234f9\"},\"1d9a0983-608b-4aed-b03f-f27e8e0e677a\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon B3\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"1d9a0983-608b-4aed-b03f-f27e8e0e677a\",\"id\":\"ec604873c2b3c4d53f5ea9c2e203fc70\"},\"219c8c15-4722-4b40-9e19-7fbbddeee30f\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Data 1\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"219c8c15-4722-4b40-9e19-7fbbddeee30f\",\"id\":\"abc83e3214698cefe7bbd9326b536b1d\"},\"226b84c2-b3b5-4a04-93f7-9523a21ef4e0\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Discipline\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"226b84c2-b3b5-4a04-93f7-9523a21ef4e0\",\"id\":\"f0e8a4a841062c6b5517b30002fd2325\"},\"244a8c27-edd6-4b09-8905-4cf403c61235\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Schedule Type Code/Number\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"244a8c27-edd6-4b09-8905-4cf403c61235\",\"id\":\"1ff35615eaa5e568fdb3c7c1bb21b72a\"},\"2cbf5041-2b36-4c7f-b65e-439af251d9f7\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Unique Asset ID - Equipment Type\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"2cbf5041-2b36-4c7f-b65e-439af251d9f7\",\"id\":\"1339498cfd39967f51d7d5a31feba974\"},\"2eac0fd8-0c8a-4c5a-9d54-62415d708f37\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Elemental Code\",\"units\":null,\"value\":\"UFSB\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"2eac0fd8-0c8a-4c5a-9d54-62415d708f37\",\"id\":\"e20483f71786c3e27291a3eeaa049b48\"},\"2f1ef0a4-09a2-4e80-ba30-57984a475e1d\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Unique Asset ID\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"2f1ef0a4-09a2-4e80-ba30-57984a475e1d\",\"id\":\"d083dfba92681370be68f19d761a9628\"},\"2fb9b7d9-d0b0-4ce2-bbc0-02464fda354c\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon C1\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"2fb9b7d9-d0b0-4ce2-bbc0-02464fda354c\",\"id\":\"71196d8fbc135c7386841bb439f8aaee\"},\"3490690f-a8be-46d9-9607-47c255e9ee89\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcExportAs [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"3490690f-a8be-46d9-9607-47c255e9ee89\",\"id\":\"cf2c87ffe9b6babcd2659d619fe3a4b7\"},\"35064971-5814-4b17-b572-49ea1320c516\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Data 7\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"35064971-5814-4b17-b572-49ea1320c516\",\"id\":\"ecf761dcb34a3e098f355f401eec5738\"},\"36d9a077-9301-47a0-b049-4f29e17d51dd\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcExportAs [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"36d9a077-9301-47a0-b049-4f29e17d51dd\",\"id\":\"f547ba1d204747353092fccb1970b8ee\"},\"392cae56-cd6b-4946-817f-242686e12441\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Zone\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"392cae56-cd6b-4946-817f-242686e12441\",\"id\":\"37152ed7e04c0ea23c442aad0be9a611\"},\"3c3d55ea-8a2f-41c1-97fd-d222d586b0b1\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Omniclass Classification Type Code\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"3c3d55ea-8a2f-41c1-97fd-d222d586b0b1\",\"id\":\"861b129e42aac2de8166ad76ded0781c\"},\"3d134cec-2e95-4d43-bc4b-e552d382f73c\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcPresentationLayer\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"3d134cec-2e95-4d43-bc4b-e552d382f73c\",\"id\":\"030b7e2842cde1ce8a1d8e545d0e068a\"},\"3f146225-bd3e-448b-b180-034b880bd662\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon A5\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"3f146225-bd3e-448b-b180-034b880bd662\",\"id\":\"3d573a80fc3bd747d292d75c7751c523\"},\"3f9a284a-7485-460c-b827-9df8cd50720e\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Classification.OmniClass.21.Description\",\"units\":null,\"value\":\"Insitu Concrete\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"3f9a284a-7485-460c-b827-9df8cd50720e\",\"id\":\"632da2ce6a52e51df22a05b776421b49\"},\"40e844db-ba22-4ddc-bc15-1fc47f5b12e7\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcSpatialContainer\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"40e844db-ba22-4ddc-bc15-1fc47f5b12e7\",\"id\":\"a8e9d80b93cd5f965d00aaffad6786e9\"},\"444d97fc-d9b6-4424-943d-37ac498a46c4\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Unique Asset ID - Item Number\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"444d97fc-d9b6-4424-943d-37ac498a46c4\",\"id\":\"d7cf7e867db30b45ac62757a6cc11b1b\"},\"46baf2f0-9232-4c37-aa7c-57e37fd5db17\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Product Type\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"46baf2f0-9232-4c37-aa7c-57e37fd5db17\",\"id\":\"b87714b8187aabde851b900a3755655a\"},\"4803c7b6-ded1-46b1-b5eb-ffe9ddcdc20b\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcSpatialContainer\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"4803c7b6-ded1-46b1-b5eb-ffe9ddcdc20b\",\"id\":\"9d6e8c6691db06081c2332c589ed2a31\"},\"48e76a50-9a4f-47a9-8074-79cc7fce9f14\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcPropertySetList\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"48e76a50-9a4f-47a9-8074-79cc7fce9f14\",\"id\":\"38621e3cf898c0fa404d29b88cf6ea5a\"},\"4ac5fa74-7864-45a3-9d89-1ab998b7731a\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon A3\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"4ac5fa74-7864-45a3-9d89-1ab998b7731a\",\"id\":\"34e84e5eed664e1ca94385e405141ef1\"},\"4c575161-247d-46a5-8ae2-72829f37725f\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"NetArea(BaseQuantities)\",\"units\":\"m\u00b2\",\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:squareMeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"4c575161-247d-46a5-8ae2-72829f37725f\",\"id\":\"c5741774b669ce685c5e53a704cc0320\"},\"4d293b70-da1a-4830-80a0-4f63b356ff61\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon A2\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"4d293b70-da1a-4830-80a0-4f63b356ff61\",\"id\":\"e629dd3ed399e1b8328aeaa2e90afae9\"},\"4dabccff-7cc0-42ff-a6db-29a28162d3f3\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type IfcPropertySetList\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"4dabccff-7cc0-42ff-a6db-29a28162d3f3\",\"id\":\"3aa01feade7f65bbd785f7083c04bacf\"},\"4fc2bd83-f0b1-41ba-8663-89e3d7f3e660\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcDescription [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"4fc2bd83-f0b1-41ba-8663-89e3d7f3e660\",\"id\":\"e7639f4355cebda699431a60345609c4\"},\"50a015d9-917f-4ac5-884e-42f7f36b47b1\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon B5\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"50a015d9-917f-4ac5-884e-42f7f36b47b1\",\"id\":\"9a71039a2e2272f527084f2096f9429b\"},\"51778754-e984-42cf-8a6d-a2226baf316f\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcPresentationLayer\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"51778754-e984-42cf-8a6d-a2226baf316f\",\"id\":\"392005ecea2408b03939ef80fbb34a8f\"},\"5188c780-2bf1-460c-8bbf-043dcb4649eb\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcExportAs\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"5188c780-2bf1-460c-8bbf-043dcb4649eb\",\"id\":\"f38369c4acdfa96b7d45ac6187f8a183\"},\"5402c013-1b09-474f-b399-344a0e55a182\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Material\",\"units\":null,\"value\":\"PT\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"5402c013-1b09-474f-b399-344a0e55a182\",\"id\":\"e8252b753c969222390cf77fc56237ef\"},\"579138d4-c882-45f1-bfd6-5ec6f8189161\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Rate Reo Area\",\"units\":null,\"value\":12,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"579138d4-c882-45f1-bfd6-5ec6f8189161\",\"id\":\"09ca602feb656474737d2cf84e89e26f\"},\"5d8a425f-4cff-44fe-9896-932e8e5639ef\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Data 5\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"5d8a425f-4cff-44fe-9896-932e8e5639ef\",\"id\":\"805c08afb49a184af8649f63531be0e3\"},\"6248687a-e43d-4380-9f28-b98a14157187\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcExportAs [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"6248687a-e43d-4380-9f28-b98a14157187\",\"id\":\"c300499e47eea08ec5bfd25acc9abae3\"},\"6cbcfae1-3598-4ddd-a606-41f3788c0362\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Managed Asset YesNo\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"6cbcfae1-3598-4ddd-a606-41f3788c0362\",\"id\":\"b9473955cae7232a6d5ba4a2c7669e7c\"},\"76daee20-cdee-48b9-bf5f-1dc46079927e\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Data 1\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"76daee20-cdee-48b9-bf5f-1dc46079927e\",\"id\":\"90563b7e8ed0acad2128d08383966907\"},\"7949a6c6-d3e4-45bf-bad0-208f3ba33483\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Unique Asset ID - Discipline Abbreviation\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"7949a6c6-d3e4-45bf-bad0-208f3ba33483\",\"id\":\"e427ecb62bb5a5f69dc56b12dfd4583a\"},\"79dbaeea-7a11-4cb4-a521-bc04d1b7a25b\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcName [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"79dbaeea-7a11-4cb4-a521-bc04d1b7a25b\",\"id\":\"d8a8a1f5d6315d9748e29bdd293d77a5\"},\"7a4a2609-0307-4c38-9a8c-4ffcd19a2d00\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Location\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"7a4a2609-0307-4c38-9a8c-4ffcd19a2d00\",\"id\":\"a4c9b4d5ce413090459c99efad3f1832\"},\"7d013fce-228f-4f3c-aa01-db40e458cc6e\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Data 8\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"7d013fce-228f-4f3c-aa01-db40e458cc6e\",\"id\":\"07281437b9e050cd5ade4cca8a96227b\"},\"834ba6cf-7f91-49e5-ad0c-717d52a2507a\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcExportAs\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"834ba6cf-7f91-49e5-ad0c-717d52a2507a\",\"id\":\"7783908b6376a626de1c39d735b6cbfc\"},\"86d20f83-240f-44b9-8b27-6311eab2abcd\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon B6\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"86d20f83-240f-44b9-8b27-6311eab2abcd\",\"id\":\"006791a1d0d5566c3b892202b08943fd\"},\"8a91c179-c4cb-471a-b108-ad540b8267e3\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Global Inherited Properties.Level Number\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"8a91c179-c4cb-471a-b108-ad540b8267e3\",\"id\":\"3d3d901b52bacdd137ac67f702680f3d\"},\"8c59bf6c-99ff-455b-bdfa-aeb7861e522e\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcTag\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"8c59bf6c-99ff-455b-bdfa-aeb7861e522e\",\"id\":\"363ec752016b710969c8b4454fd2d35f\"},\"8f645e8b-7523-4462-a0af-858ffeaf44dc\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon D\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"8f645e8b-7523-4462-a0af-858ffeaf44dc\",\"id\":\"0e36907ad605957b17ba8556547d8ceb\"},\"91b7eb2f-0caf-45b0-a65d-83ce1eaca70e\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcExportAs\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"91b7eb2f-0caf-45b0-a65d-83ce1eaca70e\",\"id\":\"04140c6ff8418bd2c731affd5691de8e\"},\"93b76d01-67c3-4799-a5f3-296f97489bd3\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Calc Room Number\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"93b76d01-67c3-4799-a5f3-296f97489bd3\",\"id\":\"4e270753bda04b81fd1c11bf1da5d852\"},\"9898cedf-179a-42e7-8cdc-c6c4212ec3e8\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcPresentationLayer\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"9898cedf-179a-42e7-8cdc-c6c4212ec3e8\",\"id\":\"7fd3f00069f6f1fcdab2ea0307661061\"},\"9a208060-4948-49b2-a1bf-1ab383705469\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcDescription\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"9a208060-4948-49b2-a1bf-1ab383705469\",\"id\":\"75340685a08f1a15df5c7aebbc41a85a\"},\"9a49a9a9-21c9-4f52-aeab-ae4727be6e1d\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type IfcPropertySetList\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"9a49a9a9-21c9-4f52-aeab-ae4727be6e1d\",\"id\":\"93f8195337085871545af0176f0ba282\"},\"9c2f84e0-2489-4a03-b4c8-eb44bb26fb0a\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Data 2\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"9c2f84e0-2489-4a03-b4c8-eb44bb26fb0a\",\"id\":\"fa24d55d23008ef6be1dbf3e8636c67e\"},\"9dd225d2-722b-4cb1-b972-babca7520f7e\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"System Name\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"9dd225d2-722b-4cb1-b972-babca7520f7e\",\"id\":\"056eec36214c733f8ecc448c27785434\"},\"9f484ce3-e8ae-4c20-b21c-0210b770935c\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Unique Asset ID - Tenancy Identifier\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"9f484ce3-e8ae-4c20-b21c-0210b770935c\",\"id\":\"416b24b324db272078942a4420775ec0\"},\"ANALYTICAL_HEAT_TRANSFER_COEFFICIENT\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Heat Transfer Coefficient (U)\",\"units\":\"W/(m\u00b2·K)\",\"value\":null,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:wattsPerSquareMeterKelvin-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ANALYTICAL_HEAT_TRANSFER_COEFFICIENT\",\"id\":\"8d290ae8d6ec3896b8dda96a83bb2d12\"},\"a5cb3364-d1f0-4ea5-a2d2-44114efbcf65\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcName\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"a5cb3364-d1f0-4ea5-a2d2-44114efbcf65\",\"id\":\"23ac4c2000599c233e5c390330e9108e\"},\"a77ddcdc-4c89-42c3-9d28-bc9e476c0fbe\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcName [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"a77ddcdc-4c89-42c3-9d28-bc9e476c0fbe\",\"id\":\"c60915bcbf4e2e070ccabc17694ee836\"},\"aa967357-e0b5-49f3-95a0-085e5d7d8951\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcMaterial\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"aa967357-e0b5-49f3-95a0-085e5d7d8951\",\"id\":\"08650ab84cd1be1235a60fccfcb1f39e\"},\"ac9b78b9-e138-483b-9796-6214cf7a5bd8\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Documentation.Home Story Name\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ac9b78b9-e138-483b-9796-6214cf7a5bd8\",\"id\":\"759d341196e21570a2f2d6199b5a9f2f\"},\"aeb679c1-1b82-4476-9099-7d13fd8ae3b8\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type IfcPropertySetList\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"aeb679c1-1b82-4476-9099-7d13fd8ae3b8\",\"id\":\"0a235c1836cd2ce0c5fd3869b938946e\"},\"af8efc07-fec4-4419-8513-4a268c4141c8\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon C3\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"af8efc07-fec4-4419-8513-4a268c4141c8\",\"id\":\"181c0245de21c9820a83396914565762\"},\"afb0a36f-1fa3-4f07-9b05-f86f48b3c3f0\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon C2\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"afb0a36f-1fa3-4f07-9b05-f86f48b3c3f0\",\"id\":\"cd929dfb8d143639a136ff3716800dfc\"},\"b13fb213-450c-4f92-859a-05cd5779daf1\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Data 7\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"b13fb213-450c-4f92-859a-05cd5779daf1\",\"id\":\"1972d1f9e161892f2ba47ccb04f5e784\"},\"b41f116c-c6f7-418e-bf4b-61cc815f8d99\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Level Number(Global Inherited Properties)\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"b41f116c-c6f7-418e-bf4b-61cc815f8d99\",\"id\":\"09caeb5d7565c779852e7802437af4a2\"},\"b594497d-7b5e-4221-aa1d-063f073aa326\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Data 2\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"b594497d-7b5e-4221-aa1d-063f073aa326\",\"id\":\"ee718148bffebbcfb7e50f5f2be7f91f\"},\"b753aced-e142-45f9-9bb6-d7edce1df108\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Calc Location\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"b753aced-e142-45f9-9bb6-d7edce1df108\",\"id\":\"ab32fc38096b2f538301b596be3ab123\"},\"b90ec63a-c51f-400a-bff1-66b8d0765f47\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Rate Reo Volume\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"b90ec63a-c51f-400a-bff1-66b8d0765f47\",\"id\":\"5642967c62f928019c93b1bbc0e81c26\"},\"b958fd3c-5ea1-43a2-bc5c-df212ed8cf33\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcDescription [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"b958fd3c-5ea1-43a2-bc5c-df212ed8cf33\",\"id\":\"ea40993e42d6379ab80b5b7d526347c2\"},\"b9d05d9c-a5f5-4a92-8811-9c5e2eefabd8\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"ClassificationCode\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"b9d05d9c-a5f5-4a92-8811-9c5e2eefabd8\",\"id\":\"c93f07ca1b6c3ac1c114474dc9aeeaca\"},\"bf3519b8-7b28-497e-97d8-afd4bf76203b\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcDescription [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"bf3519b8-7b28-497e-97d8-afd4bf76203b\",\"id\":\"3c46724925e301e2f88d4ecf4b6150f9\"},\"bfb5b2c0-aa1f-47ae-9cc9-70f7feaef0ea\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcName [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"bfb5b2c0-aa1f-47ae-9cc9-70f7feaef0ea\",\"id\":\"e8a6f622ac2589ad9fadd82e76c4c10c\"},\"bfd7311c-35b9-447d-9683-8ce244f8c1ad\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Remarks\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"bfd7311c-35b9-447d-9683-8ce244f8c1ad\",\"id\":\"05d34f2e81b12d25b60b1cd6a3788468\"},\"c4520aa3-cdf4-46b7-9539-180edc16d223\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon A4\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"c4520aa3-cdf4-46b7-9539-180edc16d223\",\"id\":\"54c0d9d278b045cbcb57fe4b2d477b86\"},\"c5b0f410-b4ee-4552-ac04-06fa0c13ec3b\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Unique Asset ID - Level/Floor\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"c5b0f410-b4ee-4552-ac04-06fa0c13ec3b\",\"id\":\"7365bc4a64abd37f8dbed69316983d60\"},\"c67d4cb4-b2d6-426f-8d05-ac6fbe0bd267\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Data 5\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"c67d4cb4-b2d6-426f-8d05-ac6fbe0bd267\",\"id\":\"7caf73cac38e203374836cef4827f0ae\"},\"c7ce9441-9aba-45ab-acbb-74e687481466\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Classification.OmniClass.22.Number\",\"units\":null,\"value\":\"22-03 35 13\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"c7ce9441-9aba-45ab-acbb-74e687481466\",\"id\":\"8b96e2130eddfb81c6a35635c5654079\"},\"cab1938b-5da8-4b53-9f0c-bb473c1966a4\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Level Number(Global Inherited Properties)\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"cab1938b-5da8-4b53-9f0c-bb473c1966a4\",\"id\":\"baed41d276e750b374a5ca62366d0e56\"},\"cda17719-cef4-4c69-92f1-e111e85353b1\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcTag\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"cda17719-cef4-4c69-92f1-e111e85353b1\",\"id\":\"a00fb37b573181a2bb6070b416ac2c87\"},\"ce24f3b1-369d-42bb-987e-ac0b45c4f8da\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Classification.OmniClass.23.Description\",\"units\":null,\"value\":\"Concrete Structural Floor Decks\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ce24f3b1-369d-42bb-987e-ac0b45c4f8da\",\"id\":\"57445c90784f528c2b0f414f9233a42d\"},\"ce25a030-e3d0-4856-bbc0-a1cdd8f4d4ff\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon B4\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ce25a030-e3d0-4856-bbc0-a1cdd8f4d4ff\",\"id\":\"deb71e96bcf8157d78c59266aaaa86d1\"},\"d05b3c99-0643-409d-ad3b-2704d324bbcd\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon B1\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"d05b3c99-0643-409d-ad3b-2704d324bbcd\",\"id\":\"34d4a2c23bc26a1545523ae8597223ca\"},\"d608342a-e8f5-4ec9-9626-771914eb3da2\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Data 4\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"d608342a-e8f5-4ec9-9626-771914eb3da2\",\"id\":\"b640302338235776966b78b0735abcca\"},\"d8b20410-414f-4777-8614-a7564519c6cd\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Classification.OmniClass.21.Number\",\"units\":null,\"value\":\"21-02 10 10 20 04\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"d8b20410-414f-4777-8614-a7564519c6cd\",\"id\":\"60e194032a186046e1a6db66376b97fb\"},\"db575143-7118-4f29-813e-2ced4535a170\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon B2\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"db575143-7118-4f29-813e-2ced4535a170\",\"id\":\"29e7f431d56d49569acb66b3f0621eb5\"},\"dd0cf380-59d8-4d9f-82da-3e2be59e23a1\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon B7\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"dd0cf380-59d8-4d9f-82da-3e2be59e23a1\",\"id\":\"b2887224f02b48130a75948e3f924e61\"},\"dedec34c-f507-4242-a85f-07a816ff1128\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcName\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"dedec34c-f507-4242-a85f-07a816ff1128\",\"id\":\"eef57991b50ee252c88dcee0dc06fddd\"},\"e4af54de-6137-43b0-97d4-c2260a1a68c3\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcTag\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"e4af54de-6137-43b0-97d4-c2260a1a68c3\",\"id\":\"f00202e050fa8a6ea223906e11e870bd\"},\"ea3ba87c-ae3c-47ed-886d-754f2359389c\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcMaterial\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ea3ba87c-ae3c-47ed-886d-754f2359389c\",\"id\":\"0cd769e3fd9905a59cae6cea37f77b46\"},\"ed6b5c87-b77c-45ab-9d90-8f26e365bca1\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcSpatialContainer\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ed6b5c87-b77c-45ab-9d90-8f26e365bca1\",\"id\":\"76cd1f61df7f11197ead15a92693abae\"},\"ee8153af-4866-45f2-a9a2-b6342ccb1dd6\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcMaterial\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ee8153af-4866-45f2-a9a2-b6342ccb1dd6\",\"id\":\"c32a465b6682dbc0908107858c6b9b6b\"},\"ef2d5da9-0b71-4617-a78c-cf4395808169\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Data 3\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ef2d5da9-0b71-4617-a78c-cf4395808169\",\"id\":\"28aa3437bdf40659590c753a3e842193\"},\"f3b20cd0-059c-48a6-b744-7a9babf1cb29\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon C4\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"f3b20cd0-059c-48a6-b744-7a9babf1cb29\",\"id\":\"094df4c6e1d89bda1c3d32d19b859e57\"},\"f69d55e2-e23c-4a77-999d-45bae64d5856\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcPropertySetList\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"f69d55e2-e23c-4a77-999d-45bae64d5856\",\"id\":\"3fffe0348a0f398a26003c9552c4dbc9\"},\"f6a1fceb-536f-4261-ad25-4f1fb4dcda76\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Concrete Grade\",\"units\":null,\"value\":\"40 MPa\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"f6a1fceb-536f-4261-ad25-4f1fb4dcda76\",\"id\":\"acd05a5a6632bf49f56fe8d9b32ca1ff\"},\"f7c3a959-7884-46fd-97a6-cca3cea07fe7\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcPropertySetList\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"f7c3a959-7884-46fd-97a6-cca3cea07fe7\",\"id\":\"131c5cc690b61877eac2f73f73cd69ad\"},\"fac5d675-3756-485d-9500-4f2aa3096a38\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Product Name\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"fac5d675-3756-485d-9500-4f2aa3096a38\",\"id\":\"b5ba47412a41ee8f0e0b6309435f08e7\"},\"fb16e643-73bd-4c8d-a506-99506b010546\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Rate PT Area\",\"units\":null,\"value\":4.5,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"fb16e643-73bd-4c8d-a506-99506b010546\",\"id\":\"8607ae9e4c1350ec9eee23830a12c77e\"},\"fb272f85-666a-45a4-ae16-fa4d620d81b7\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Classification.OmniClass.23.Number\",\"units\":null,\"value\":\"23-13 35 23 11 11\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"fb272f85-666a-45a4-ae16-fa4d620d81b7\",\"id\":\"5bcbae5ccbbdc5366970db0d9dd64eca\"},\"STRUCTURAL_ELEVATION_AT_BOTTOM_SURVEY\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Elevation at Bottom Survey\",\"units\":\"mm\",\"value\":30699.999999999898,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"STRUCTURAL_ELEVATION_AT_BOTTOM_SURVEY\",\"id\":\"830d5e76c5b5b84bbab7f9f52e80dfef\"},\"id\":\"e24896645d6932e8d2edc7b56bcd65b2\"}" - )] - [InlineData( - "{\n \"applicationId\": null,\n \"speckle_type\": \"Base\",\n \"name\": \"Physically Based (1)\",\n \"diffuse\": -11810867,\n \"opacity\": 1,\n \"emissive\": -16777216,\n \"metalness\": 0,\n \"roughness\": 0.2,\n \"totalChildrenCount\": 0,\n \"id\": \"3ef9f1e3deb7e8057f9eceb29ff2ea88\"\n}" - )] - public void Serialize_Id_Stable(string json) - { - var jObject = JObject.Parse(json); - var id = jObject["id"].NotNull().Value(); - jObject.Remove("id"); - jObject.Remove("__closure"); - var jsonWithoutId = jObject.ToString(Formatting.None); - var newId = IdGenerator.ComputeId(new Json(jsonWithoutId)); - id.Should().Be(newId.Value); - } - - [Theory] - [InlineData("RevitObject.json.gz", "3416d3fe01c9196115514c4a2f41617b", 7818)] - public async Task Roundtrip_Test_Old(string fileName, string _, int count) - { - var closures = TestFileManager.GetFileAsClosures(fileName); - var deserializer = new SpeckleObjectDeserializer - { - ReadTransport = new TestTransport(closures), - CancellationToken = default, - }; - - var writtenObjects = new Dictionary(); - var writeTransport = new TestTransport2(writtenObjects); - var serializer = new SpeckleObjectSerializer([writeTransport]); - var newIds = new Dictionary(); - var oldIds = new Dictionary(); - var idToBase = new Dictionary(); - closures.Count.Should().Be(count); - foreach (var (id, objJson) in closures) - { - var base1 = await deserializer.DeserializeAsync(objJson); - base1.id.Should().Be(id); - var j = serializer.Serialize(base1); - //j.Should().Be(objJson); - JToken.DeepEquals(JObject.Parse(j), JObject.Parse(objJson)); - newIds.Add(base1.id.NotNull(), j); - oldIds.Add(id, j); - idToBase.Add(id, base1); - } - newIds.Count.Should().Be(count); - oldIds.Count.Should().Be(count); - idToBase.Count.Should().Be(count); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - [InlineData(4)] - public async Task Roundtrip_Test_New(int concurrency) - { - string fileName = "RevitObject.json.gz"; - string rootId = "3416d3fe01c9196115514c4a2f41617b"; - int oldCount = 7818; - int newCount = 4674; - var closures = TestFileManager.GetFileAsClosures(fileName); - closures.Count.Should().Be(oldCount); - - Base root; - using ( - var o = new ObjectLoader( - new DummySqLiteReceiveManager(closures), - new DummyReceiveServerObjectManager(closures), - null, - new NullLogger(), - default - ) - ) - { - await using var process = new DeserializeProcess( - o, - null, - new BaseDeserializer(new ObjectDeserializerFactory()), - new NullLoggerFactory(), - default, - new(true) - ); - root = await process.Deserialize(rootId); - process.BaseCache.Count.Should().Be(oldCount); - process.Total.Should().Be(oldCount); - } - - var newIdToJson = new ConcurrentDictionary(); - - await using ( - var serializeProcess = _factory.CreateSerializeProcess( - SqLiteJsonCacheManager.FromMemory(1), - new MemoryServerObjectManager(newIdToJson), - null, - default, - new SerializeProcessOptions(false, false, false, true) { MaxCacheBatchSize = 1, MaxParallelism = concurrency } - ) - ) - { - var (rootId2, _) = await serializeProcess.Serialize(root); - rootId2.Should().Be(root.id); - } - newIdToJson.Count.Should().Be(newCount); - - foreach (var newKvp in newIdToJson) - { - if (closures.TryGetValue(newKvp.Key, out var newValue)) - { - JToken.DeepEquals(JObject.Parse(newValue), JObject.Parse(newKvp.Value)); - } - } - } -} diff --git a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/CommentResourceTests.cs b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/CommentResourceTests.cs index a3670d09..0fd6c42f 100644 --- a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/CommentResourceTests.cs +++ b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/CommentResourceTests.cs @@ -91,7 +91,7 @@ public async Task Archive() [Fact(Skip = SERVER_SKIP_MESSAGE)] public async Task Edit() { - var blobs = await Fixtures.SendBlobData(_testUser.Account, _project.id); + var blobs = await Fixtures.SendBlobData(_testUser, _model.id); var blobIds = blobs.Select(b => b.id.NotNull()).ToList(); var input = new EditCommentInput(new(blobIds, null), _comment.id, _project.id); @@ -107,7 +107,7 @@ public async Task Edit() [Fact(Skip = SERVER_SKIP_MESSAGE)] public async Task Reply() { - var blobs = await Fixtures.SendBlobData(_testUser.Account, _project.id); + var blobs = await Fixtures.SendBlobData(_testUser, _model.id); var blobIds = blobs.Select(b => b.id.NotNull()).ToList(); var input = new CreateCommentReplyInput(new(blobIds, null), _comment.id, _project.id); diff --git a/tests/Speckle.Sdk.Tests.Integration/Fixtures.cs b/tests/Speckle.Sdk.Tests.Integration/Fixtures.cs index 49af3a6a..babde30a 100644 --- a/tests/Speckle.Sdk.Tests.Integration/Fixtures.cs +++ b/tests/Speckle.Sdk.Tests.Integration/Fixtures.cs @@ -12,7 +12,6 @@ using Speckle.Sdk.Host; using Speckle.Sdk.Models; using Speckle.Sdk.Tests.Integration.API.GraphQL.Resources; -using Speckle.Sdk.Transports; using Version = Speckle.Sdk.Api.GraphQL.Models.Version; namespace Speckle.Sdk.Tests.Integration; @@ -42,10 +41,9 @@ public static async Task SeedUserWithClient() public static async Task CreateVersion(IClient client, string projectId, string modelId) { - using var remote = ServiceProvider.GetRequiredService().Create(client.Account, projectId); var (objectId, _) = await ServiceProvider .GetRequiredService() - .Send(new() { applicationId = "ASDF" }, remote, false); + .Send2(client.ServerUrl, modelId, client.Account.token, new() { applicationId = "ASDF" }, null, default); CreateVersionInput input = new(objectId, modelId, projectId); return await client.Version.Create(input); } @@ -146,18 +144,19 @@ private static Blob GenerateBlob(string content) [Obsolete(CommentResourceTests.SERVER_SKIP_MESSAGE)] internal static async Task CreateComment(IClient client, string projectId, string modelId, string versionId) { - var blobs = await SendBlobData(client.Account, projectId); + var blobs = await SendBlobData(client, modelId); var blobIds = blobs.Select(b => b.id.NotNull()).ToList(); CreateCommentInput input = new(new(blobIds, null), projectId, $"{projectId},{modelId},{versionId}", null, null); return await client.Comment.Create(input); } - internal static async Task SendBlobData(Account account, string projectId) + internal static async Task SendBlobData(IClient client, string modelId) { - using var remote = ServiceProvider.GetRequiredService().Create(account, projectId); var blobs = Fixtures.GenerateThreeBlobs(); Base myObject = new() { ["blobs"] = blobs }; - await ServiceProvider.GetRequiredService().Send(myObject, remote, false); + await ServiceProvider + .GetRequiredService() + .Send2(client.ServerUrl, modelId, client.Account.token, myObject, null, default); return blobs; } } diff --git a/tests/Speckle.Sdk.Tests.Integration/MemoryTransportTests.cs b/tests/Speckle.Sdk.Tests.Integration/MemoryTransportTests.cs deleted file mode 100644 index f1c7cc8b..00000000 --- a/tests/Speckle.Sdk.Tests.Integration/MemoryTransportTests.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Reflection; -using FluentAssertions; -using Microsoft.Extensions.DependencyInjection; -using Speckle.Sdk.Api; -using Speckle.Sdk.Host; -using Speckle.Sdk.Models; -using Speckle.Sdk.Transports; - -namespace Speckle.Sdk.Tests.Integration; - -public class MemoryTransportTests : IDisposable -{ - private readonly MemoryTransport _memoryTransport = new(blobStorageEnabled: true); - private IOperations _operations; - - public MemoryTransportTests() - { - CleanData(); - TypeLoader.Reset(); - TypeLoader.Initialize(typeof(Base).Assembly, Assembly.GetExecutingAssembly()); - var serviceProvider = TestServiceSetup.GetServiceProvider(); - _operations = serviceProvider.GetRequiredService(); - } - - public void Dispose() => CleanData(); - - private void CleanData() - { - if (Directory.Exists(_memoryTransport.BlobStorageFolder)) - { - Directory.Delete(_memoryTransport.BlobStorageFolder, true); - } - - Directory.CreateDirectory(_memoryTransport.BlobStorageFolder); - } - - [Fact] - public async Task SendAndReceiveObjectWithBlobs() - { - var myObject = Fixtures.GenerateSimpleObject(); - myObject["blobs"] = Fixtures.GenerateThreeBlobs(); - - var sendResult = await _operations.Send(myObject, _memoryTransport, false); - - // NOTE: used to debug diffing - // await Operations.Send(myObject, new List { transport }); - - var receivedObject = await _operations.Receive(sendResult.rootObjId, _memoryTransport, new MemoryTransport()); - - var allFiles = Directory - .GetFiles(_memoryTransport.BlobStorageFolder) - .Select(fp => fp.Split(Path.DirectorySeparatorChar).Last()) - .ToList(); - - var blobPaths = allFiles - .Where(fp => fp.Length > Blob.LocalHashPrefixLength) // excludes things like .DS_store - .ToList(); - - // Check that there are three downloaded blobs! - blobPaths.Count.Should().Be(3); - - var objectBlobs = receivedObject["blobs"] as IList; - objectBlobs.Should().NotBeNull(); - - var blobs = objectBlobs!.Cast().ToList(); - // Check that we have three blobs - blobs.Count.Should().Be(3); - - // Check that received blobs point to local path (where they were received) - blobs[0].filePath.Should().Contain(_memoryTransport.BlobStorageFolder); - blobs[1].filePath.Should().Contain(_memoryTransport.BlobStorageFolder); - blobs[2].filePath.Should().Contain(_memoryTransport.BlobStorageFolder); - } -} diff --git a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs deleted file mode 100644 index eb55c025..00000000 --- a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs +++ /dev/null @@ -1,82 +0,0 @@ -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Engines; -using Microsoft.Extensions.DependencyInjection; -using Speckle.Objects.Geometry; -using Speckle.Sdk.Credentials; -using Speckle.Sdk.Helpers; -using Speckle.Sdk.Host; -using Speckle.Sdk.Logging; -using Speckle.Sdk.Models; -using Speckle.Sdk.Serialisation.V2; -using Speckle.Sdk.Serialisation.V2.Receive; -using Speckle.Sdk.SQLite; - -namespace Speckle.Sdk.Tests.Performance.Benchmarks; - -/// -/// How many threads on our Deserializer is optimal -/// -[MemoryDiagnoser] -[SimpleJob(RunStrategy.Monitoring, 0, 0, 2)] -public class GeneralDeserializer : IDisposable -{ - private const bool skipCache = true; - - /* - private const string url = "https://latest.speckle.systems/projects/a3ac1b2706/models/59d3b0f3c6"; //small? - private const string streamId = "a3ac1b2706"; - private const string rootId = "7d53bcf28c6696ecac8781684a0aa006";*/ - - private const string url = "https://latest.speckle.systems/projects/2099ac4b5f/models/da511c4d1e"; //perf? - private const string streamId = "2099ac4b5f"; - private const string rootId = "30fb4cbe6eb2202b9e7b4a4fcc3dd2b6"; - private TestDataHelper _dataSource; - - [GlobalSetup] - public async Task Setup() - { - TypeLoader.Initialize(typeof(Base).Assembly, typeof(Point).Assembly); - _dataSource = new TestDataHelper(); - await _dataSource - .SeedTransport(new Account() { serverInfo = new() { url = url } }, streamId, rootId, skipCache) - .ConfigureAwait(false); - } - - [Benchmark] - public async Task RunTest_New() - { - var sqlite = TestDataHelper - .ServiceProvider.GetRequiredService() - .CreateFromStream(streamId); - var serverObjects = new ServerObjectManager( - TestDataHelper.ServiceProvider.GetRequiredService(), - TestDataHelper.ServiceProvider.GetRequiredService(), - new Uri(url), - streamId, - null - ); - await using var process = new DeserializeProcess( - sqlite, - serverObjects, - null, - new BaseDeserializer(new ObjectDeserializerFactory()), - default, - new(skipCache) - ); - return await process.Deserialize(rootId).ConfigureAwait(false); - } - - /* - [Benchmark] - public async Task RunTest_Old() - { - SpeckleObjectDeserializer sut = new() { ReadTransport = _dataSource.Transport }; - string data = await _dataSource.Transport.GetObject(_dataSource.ObjectId)!; - return await sut.DeserializeAsync(data); - } - */ - [GlobalCleanup] - public void Cleanup() => Dispose(); - - public void Dispose() => _dataSource.Dispose(); -} diff --git a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralReceiveTest.cs b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralReceiveTest.cs deleted file mode 100644 index fbb060ca..00000000 --- a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralReceiveTest.cs +++ /dev/null @@ -1,64 +0,0 @@ -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Engines; -using Microsoft.Extensions.DependencyInjection; -using Speckle.Objects.Geometry; -using Speckle.Sdk.Api; -using Speckle.Sdk.Credentials; -using Speckle.Sdk.Host; -using Speckle.Sdk.Models; -using Speckle.Sdk.Transports; - -namespace Speckle.Sdk.Tests.Performance.Benchmarks; - -/// -/// How many threads on our Deserializer is optimal -/// -[MemoryDiagnoser] -[SimpleJob(RunStrategy.Monitoring, 0, 0, 1)] -public class GeneralReceiveTest : IDisposable -{ - /* - private const string url = "https://latest.speckle.systems/projects/a3ac1b2706/models/59d3b0f3c6"; //small? - private const string streamId = "a3ac1b2706";S - private const string rootId = "7d53bcf28c6696ecac8781684a0aa006";*/ - - private const string url = "https://latest.speckle.systems/projects/2099ac4b5f/models/da511c4d1e"; //perf? - private readonly Uri _baseUrl = new("https://latest.speckle.systems"); - private const string streamId = "2099ac4b5f"; - private const string rootId = "30fb4cbe6eb2202b9e7b4a4fcc3dd2b6"; - private TestDataHelper _dataSource; - private IOperations _operations; - private ITransport remoteTransport; - - [GlobalSetup] - public async Task Setup() - { - TypeLoader.Initialize(typeof(Base).Assembly, typeof(Point).Assembly); - _dataSource = new TestDataHelper(); - var acc = new Account() { serverInfo = new() { url = url } }; - await _dataSource.SeedTransport(acc, streamId, rootId, true).ConfigureAwait(false); - _operations = TestDataHelper.ServiceProvider.GetRequiredService(); - // await _operations.Receive2(_baseUrl, streamId, rootId, null); - - remoteTransport = TestDataHelper - .ServiceProvider.GetRequiredService() - .Create(acc, streamId); - } - - [Benchmark] - public async Task RunTest_Receive() - { - return await _operations.Receive(rootId, remoteTransport, _dataSource.Transport); - } - - [Benchmark] - public async Task RunTest_Receive2() - { - return await _operations.Receive2(_baseUrl, streamId, rootId, null, null, default); - } - - [GlobalCleanup] - public void Cleanup() => Dispose(); - - public void Dispose() => _dataSource.Dispose(); -} diff --git a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSendTest.cs b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSendTest.cs deleted file mode 100644 index d70c9735..00000000 --- a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSendTest.cs +++ /dev/null @@ -1,85 +0,0 @@ -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Engines; -using Microsoft.Extensions.DependencyInjection; -using Speckle.Objects.Geometry; -using Speckle.Sdk.Api; -using Speckle.Sdk.Api.GraphQL.Enums; -using Speckle.Sdk.Api.GraphQL.Models; -using Speckle.Sdk.Common; -using Speckle.Sdk.Credentials; -using Speckle.Sdk.Host; -using Speckle.Sdk.Models; -using Speckle.Sdk.Serialisation; -using Speckle.Sdk.Transports; -using Version = Speckle.Sdk.Api.GraphQL.Models.Version; - -namespace Speckle.Sdk.Tests.Performance.Benchmarks; - -/// -/// How many threads on our Deserializer is optimal -/// -[MemoryDiagnoser] -[SimpleJob(RunStrategy.Monitoring, iterationCount: 1)] -public class GeneralSendTest -{ - private Base _testData; - private IOperations _operations; - private ServerTransport _remote; - private Account acc; - private IClient client; - - private Project _project; - - [GlobalSetup] - public async Task Setup() - { - TypeLoader.Initialize(typeof(Base).Assembly, typeof(Point).Assembly); - using var dataSource = new TestDataHelper(); - await dataSource - .SeedTransport( - new Account() { serverInfo = new() { url = "https://latest.speckle.systems/" } }, - "2099ac4b5f", - "30fb4cbe6eb2202b9e7b4a4fcc3dd2b6", - false - ) - .ConfigureAwait(false); - - SpeckleObjectDeserializer deserializer = new() { ReadTransport = dataSource.Transport }; - string data = await dataSource.Transport.GetObject(dataSource.ObjectId).NotNull(); - _testData = await deserializer.DeserializeAsync(data).NotNull(); - _operations = TestDataHelper.ServiceProvider.GetRequiredService(); - - acc = TestDataHelper - .ServiceProvider.GetRequiredService() - .GetAccounts("https://latest.speckle.systems") - .First(); - - client = TestDataHelper.ServiceProvider.GetRequiredService().Create(acc); - - _project = await client.Project.Create( - new($"General Send Test run {Guid.NewGuid()}", null, ProjectVisibility.Public) - ); - _remote = TestDataHelper.ServiceProvider.GetRequiredService().Create(acc, _project.id); - } - - [Benchmark(Baseline = true)] - public async Task Send_old() - { - using SQLiteTransport local = new(); - var res = await _operations.Send(_testData, [_remote, local]); - return await TagVersion($"Send_old {Guid.NewGuid()}", res.rootObjId); - } - - [Benchmark] - public async Task Send_new() - { - var res = await _operations.Send2(new(acc.serverInfo.url), _project.id, acc.token, _testData, null, default); - return await TagVersion($"Send_new {Guid.NewGuid()}", res.RootId); - } - - private async Task TagVersion(string name, string objectId) - { - var model = await client.Model.Create(new(name, null, _project.id)); - return await client.Version.Create(new(objectId, model.id, _project.id)); - } -} diff --git a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs deleted file mode 100644 index 48b544dd..00000000 --- a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs +++ /dev/null @@ -1,80 +0,0 @@ -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Engines; -using Speckle.Objects.Geometry; -using Speckle.Sdk.Common; -using Speckle.Sdk.Credentials; -using Speckle.Sdk.Host; -using Speckle.Sdk.Models; -using Speckle.Sdk.Serialisation; -using Speckle.Sdk.Transports; - -namespace Speckle.Sdk.Tests.Performance.Benchmarks; - -/// -/// How many threads on our Deserializer is optimal -/// -[MemoryDiagnoser] -[SimpleJob(RunStrategy.Monitoring)] -public class GeneralSerializerTest -{ - private Base _testData; - - [GlobalSetup] - public async Task Setup() - { - TypeLoader.Initialize(typeof(Base).Assembly, typeof(Point).Assembly); - using var dataSource = new TestDataHelper(); - await dataSource - .SeedTransport( - new Account() - { - serverInfo = new() { url = "https://latest.speckle.systems/projects/2099ac4b5f/models/da511c4d1e" }, - }, - "2099ac4b5f", - "30fb4cbe6eb2202b9e7b4a4fcc3dd2b6", - false - ) - .ConfigureAwait(false); - - SpeckleObjectDeserializer deserializer = new() { ReadTransport = dataSource.Transport }; - string data = await dataSource.Transport.GetObject(dataSource.ObjectId).NotNull(); - _testData = await deserializer.DeserializeAsync(data).NotNull(); - } - - [Benchmark] - public string RunTest() - { - var remote = new NullTransport(); - SpeckleObjectSerializer sut = new([remote]); - var x = sut.Serialize(_testData); - return x; - } -} - -public class NullTransport : ITransport -{ - public string TransportName { get; set; } = ""; - public Dictionary TransportContext { get; } = new(); - public TimeSpan Elapsed { get; } = TimeSpan.Zero; - public CancellationToken CancellationToken { get; set; } - public IProgress OnProgressAction { get; set; } - - public void BeginWrite() { } - - public void EndWrite() { } - - public void SaveObject(string id, string serializedObject) { } - - public Task WriteComplete() - { - return Task.CompletedTask; - } - - public Task GetObject(string id) => throw new NotImplementedException(); - - public Task CopyObjectAndChildren(string id, ITransport targetTransport) => - throw new NotImplementedException(); - - public Task> HasObjects(IReadOnlyList objectIds) => - throw new NotImplementedException(); -} diff --git a/tests/Speckle.Sdk.Tests.Performance/TestDataHelper.cs b/tests/Speckle.Sdk.Tests.Performance/TestDataHelper.cs deleted file mode 100644 index a82dd5e0..00000000 --- a/tests/Speckle.Sdk.Tests.Performance/TestDataHelper.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Microsoft.Data.Sqlite; -using Microsoft.Extensions.DependencyInjection; -using Speckle.Sdk.Api; -using Speckle.Sdk.Credentials; -using Speckle.Sdk.Models; -using Speckle.Sdk.Transports; - -namespace Speckle.Sdk.Tests.Performance; - -public sealed class TestDataHelper : IDisposable -{ - private static readonly string s_basePath = $"./temp {Guid.NewGuid()}"; - public SQLiteTransport Transport { get; private set; } - - public static IServiceProvider ServiceProvider { get; set; } - public string ObjectId { get; private set; } - - public TestDataHelper() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSpeckleSdk(new("Tests", "test"), "v3"); - ServiceProvider = serviceCollection.BuildServiceProvider(); - } - - public async Task SeedTransport(Account account, string streamId, string objectId, bool skipCache) - { - // Transport = new SQLiteTransport(s_basePath, APPLICATION_NAME); - Transport = new SQLiteTransport(); - - //seed SQLite transport with test data - ObjectId = await SeedTransport(account, streamId, objectId, Transport, skipCache).ConfigureAwait(false); - } - - public async Task SeedTransport( - Account account, - string streamId, - string objectId, - ITransport transport, - bool skipCache - ) - { - if (!skipCache) - { - using ServerTransport remoteTransport = ServiceProvider - .GetRequiredService() - .Create(account, streamId); - transport.BeginWrite(); - await remoteTransport.CopyObjectAndChildren(objectId, transport).ConfigureAwait(false); - transport.EndWrite(); - await transport.WriteComplete().ConfigureAwait(false); - } - - return objectId; - } - - public async Task DeserializeBase() - { - return await ServiceProvider - .GetRequiredService() - .Receive(ObjectId, null, Transport) - .ConfigureAwait(false); - } - - public void Dispose() - { - Transport.Dispose(); - SqliteConnection.ClearAllPools(); - if (Directory.Exists(s_basePath)) - { - Directory.Delete(s_basePath, true); - } - } -} diff --git a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/ClosureTests.cs b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/ClosureTests.cs deleted file mode 100644 index 1397a9ec..00000000 --- a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/ClosureTests.cs +++ /dev/null @@ -1,129 +0,0 @@ -using FluentAssertions; -using Microsoft.Extensions.DependencyInjection; -using Newtonsoft.Json; -using Speckle.Sdk.Api; -using Speckle.Sdk.Common; -using Speckle.Sdk.Host; -using Speckle.Sdk.Models; -using Speckle.Sdk.Tests.Unit.Host; -using Speckle.Sdk.Transports; - -namespace Speckle.Sdk.Tests.Unit.Api.Operations; - -public class Closures -{ - private readonly IOperations _operations; - - public Closures() - { - TypeLoader.Reset(); - TypeLoader.Initialize(typeof(Base).Assembly, typeof(TableLegFixture).Assembly); - var serviceProvider = TestServiceSetup.GetServiceProvider(); - _operations = serviceProvider.GetRequiredService(); - } - - [Fact(DisplayName = "Checks whether closures are generated correctly by the serialiser.")] - public async Task CorrectDecompositionTracking() - { - var d5 = new Base(); - ((dynamic)d5).name = "depth five"; // end v - - var d4 = new Base(); - ((dynamic)d4).name = "depth four"; - ((dynamic)d4)["@detach"] = d5; - - var d3 = new Base(); - ((dynamic)d3).name = "depth three"; - ((dynamic)d3)["@detach"] = d4; - - var d2 = new Base(); - ((dynamic)d2).name = "depth two"; - ((dynamic)d2)["@detach"] = d3; - ((dynamic)d2)["@joker"] = new object[] { d5 }; - - var d1 = new Base(); - ((dynamic)d1).name = "depth one"; - ((dynamic)d1)["@detach"] = d2; - ((dynamic)d1)["@joker"] = d5; // consequently, d5 depth in d1 should be 1 - - var transport = new MemoryTransport(); - - var sendResult = await _operations.Send(d1, transport, false); - - var test = await _operations.Receive(sendResult.rootObjId, localTransport: transport); - - test.id.NotNull(); - d1.GetId(true).Should().BeEquivalentTo((test.id)); - - var d1_ = NotNullExtensions.NotNull(JsonConvert.DeserializeObject(transport.Objects[d1.GetId(true)])); - var d2_ = NotNullExtensions.NotNull(JsonConvert.DeserializeObject(transport.Objects[d2.GetId(true)])); - var d3_ = NotNullExtensions.NotNull(JsonConvert.DeserializeObject(transport.Objects[d3.GetId(true)])); - JsonConvert.DeserializeObject(transport.Objects[d4.GetId(true)]); - JsonConvert.DeserializeObject(transport.Objects[d5.GetId(true)]); - - var depthOf_d5_in_d1 = int.Parse((string)d1_.__closure[d5.GetId(true)]); - depthOf_d5_in_d1.Should().Be(1); - - var depthOf_d4_in_d1 = int.Parse((string)d1_.__closure[d4.GetId(true)]); - depthOf_d4_in_d1.Should().Be(3); - - var depthOf_d5_in_d3 = int.Parse((string)d3_.__closure[d5.GetId(true)]); - depthOf_d5_in_d3.Should().Be(2); - - var depthOf_d4_in_d3 = int.Parse((string)d3_.__closure[d4.GetId(true)]); - depthOf_d4_in_d3.Should().Be(1); - - var depthOf_d5_in_d2 = int.Parse((string)d2_.__closure[d5.GetId(true)]); - depthOf_d5_in_d2.Should().Be(1); - } - - [Fact] - public void DescendantsCounting() - { - Base myBase = new(); - - var myList = new List(); - // These should be counted! - for (int i = 0; i < 100; i++) - { - var smolBase = new Base(); - smolBase["test"] = i; - myList.Add(smolBase); - } - - // Primitives should not be counted! - for (int i = 0; i < 10; i++) - { - myList.Add(i); - } - - myList.Add("Hello"); - myList.Add(new { hai = "bai" }); - - myBase["@detachTheList"] = myList; - - var dictionary = new Dictionary(); - for (int i = 0; i < 10; i++) - { - var smolBase = new Base { applicationId = i.ToString() }; - dictionary[$"key {i}"] = smolBase; - } - - dictionary["string value"] = "bol"; - dictionary["int value"] = 42; - dictionary["THIS IS RECURSIVE SURPRISE"] = myBase; - - myBase["@detachTheDictionary"] = dictionary; - - myBase.GetTotalChildrenCount().Should().Be(112); - - var tableTest = new DiningTable(); - tableTest.GetTotalChildrenCount().Should().Be(10); - - // Explicitely test for recurisve references! - var recursiveRef = new Base { applicationId = "random" }; - recursiveRef["@recursive"] = recursiveRef; - - recursiveRef.GetTotalChildrenCount().Should().Be(2); - } -} diff --git a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/OperationsReceiveTests.Exceptional.cs b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/OperationsReceiveTests.Exceptional.cs deleted file mode 100644 index 00288a36..00000000 --- a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/OperationsReceiveTests.Exceptional.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Speckle.Sdk.Transports; - -namespace Speckle.Sdk.Tests.Unit.Api.Operations; - -public partial class OperationsReceiveTests -{ - [Theory, MemberData(nameof(TestCases))] - public async Task Receive_ObjectsDontExist_ExceptionThrown(string id) - { - MemoryTransport emptyTransport1 = new(); - MemoryTransport emptyTransport2 = new(); - await Assert.ThrowsAsync(async () => - { - await _operations.Receive(id, emptyTransport1, emptyTransport2); - }); - } - - [Theory, MemberData(nameof(TestCases))] - public async Task Receive_ObjectsDontExistNullRemote_ExceptionThrown(string id) - { - MemoryTransport emptyTransport = new(); - await Assert.ThrowsAsync(async () => - { - await _operations.Receive(id, null, emptyTransport); - }); - } - - [Theory, MemberData(nameof(TestCases))] - public async Task Receive_OperationCanceled_ExceptionThrown(string id) - { - using CancellationTokenSource ctc = new(); - ctc.Cancel(); - - MemoryTransport emptyTransport2 = new(); - await Assert.ThrowsAsync(async () => - { - await _operations.Receive(id, _testCaseTransport, emptyTransport2, cancellationToken: ctc.Token); - }); - } -} diff --git a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/OperationsReceiveTests.cs b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/OperationsReceiveTests.cs deleted file mode 100644 index 8ca3a9a9..00000000 --- a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/OperationsReceiveTests.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System.Reflection; -using Microsoft.Extensions.DependencyInjection; -using Speckle.Sdk.Api; -using Speckle.Sdk.Host; -using Speckle.Sdk.Models; -using Speckle.Sdk.Transports; - -namespace Speckle.Sdk.Tests.Unit.Api.Operations; - -public sealed partial class OperationsReceiveTests : IDisposable -{ - private static readonly Base[] s_testObjects; - private readonly IOperations _operations; - private readonly MemoryTransport _testCaseTransport; - - static OperationsReceiveTests() - { - Reset(); - s_testObjects = - [ - new() { ["string prop"] = "simple test case", ["numerical prop"] = 123 }, - new() { ["@detachedProp"] = new Base() { ["the best prop"] = "1234!" } }, - new() - { - ["@detachedList"] = new List { new() { ["the worst prop"] = null } }, - ["dictionaryProp"] = new Dictionary { ["dict"] = new() { ["the best prop"] = "" } }, - }, - ]; - } - - public OperationsReceiveTests() - { - Reset(); - var serviceProvider = TestServiceSetup.GetServiceProvider(); - _operations = serviceProvider.GetRequiredService(); - _testCaseTransport = new MemoryTransport(); - - // Simulate a one-time setup action - foreach (var b in s_testObjects) - { - _ = _operations.Send(b, _testCaseTransport, false).GetAwaiter().GetResult(); - } - } - - private static void Reset() - { - TypeLoader.Reset(); - TypeLoader.Initialize(typeof(Base).Assembly, Assembly.GetExecutingAssembly()); - } - - public static IEnumerable TestCases() - { - foreach (var s in s_testObjects) - { - yield return [s.GetId(true)]; - } - } - - [Theory] - [MemberData(nameof(TestCases))] - public async Task Receive_FromLocal_ExistingObjects(string id) - { - Base result = await _operations.Receive(id, null, _testCaseTransport); - - Assert.NotNull(result); - Assert.Equal(id, result.id); - } - - [Theory] - [MemberData(nameof(TestCases))] - public async Task Receive_FromRemote_ExistingObjects(string id) - { - MemoryTransport localTransport = new(); - Base result = await _operations.Receive(id, _testCaseTransport, localTransport); - - Assert.NotNull(result); - Assert.Equal(id, result.id); - } - - [Theory] - [MemberData(nameof(TestCases))] - public async Task Receive_FromLocal_OnProgressActionCalled(string id) - { - bool wasCalled = false; - _ = await _operations.Receive( - id, - null, - _testCaseTransport, - onProgressAction: new UnitTestProgress(_ => wasCalled = true) - ); - - Assert.True(wasCalled); - } - - public void Dispose() - { - // Cleanup resources if necessary - } -} diff --git a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendObjectReferences.cs b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendObjectReferences.cs deleted file mode 100644 index 94704b0c..00000000 --- a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendObjectReferences.cs +++ /dev/null @@ -1,71 +0,0 @@ -using FluentAssertions; -using Microsoft.Extensions.DependencyInjection; -using Speckle.Sdk.Api; -using Speckle.Sdk.Host; -using Speckle.Sdk.Models; -using Speckle.Sdk.Transports; - -namespace Speckle.Sdk.Tests.Unit.Api.Operations; - -public class SendObjectReferences -{ - private readonly IOperations _operations; - - public SendObjectReferences() - { - TypeLoader.Reset(); - TypeLoader.Initialize(typeof(Base).Assembly, typeof(DataChunk).Assembly); - var serviceProvider = TestServiceSetup.GetServiceProvider(); - _operations = serviceProvider.GetRequiredService(); - } - - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(10)] - public async Task SendObjectsWithApplicationIds(int testDepth) - { - Base testData = GenerateTestCase(testDepth, true); - MemoryTransport transport = new(); - var result = await _operations.Send(testData, [transport]); - - result.rootObjId.Should().NotBeNull(); - - result.rootObjId.Length.Should().Be(32); - - result.convertedReferences.Count.Should().Be((int)(Math.Pow(2, testDepth + 1) - 2)); - } - - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(10)] - public async Task SendObjectsWithoutApplicationIds(int testDepth) - { - Base testData = GenerateTestCase(testDepth, false); - MemoryTransport transport = new(); - var result = await _operations.Send(testData, [transport]); - - result.rootObjId.Should().NotBeNull(); - - result.rootObjId.Length.Should().Be(32); - - result.convertedReferences.Should().BeEmpty(); - } - - private Base GenerateTestCase(int depth, bool withAppId) - { - var appId = withAppId ? $"{Guid.NewGuid()}" : null; - var ret = new Base() { applicationId = appId }; - if (depth > 0) - { - ret["@elements"] = new List - { - GenerateTestCase(depth - 1, withAppId), - GenerateTestCase(depth - 1, withAppId), - }; - } - - return ret; - } -} diff --git a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendReceiveLocal.cs b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendReceiveLocal.cs deleted file mode 100644 index ab85188d..00000000 --- a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendReceiveLocal.cs +++ /dev/null @@ -1,215 +0,0 @@ -using FluentAssertions; -using Microsoft.Extensions.DependencyInjection; -using Speckle.Sdk.Api; -using Speckle.Sdk.Common; -using Speckle.Sdk.Host; -using Speckle.Sdk.Models; -using Speckle.Sdk.Tests.Unit.Host; -using Speckle.Sdk.Transports; - -namespace Speckle.Sdk.Tests.Unit.Api.Operations; - -public sealed class SendReceiveLocal : IDisposable -{ - private readonly IOperations _operations; - - public SendReceiveLocal() - { - TypeLoader.Reset(); - TypeLoader.Initialize(typeof(Base).Assembly, typeof(Point).Assembly); - var serviceProvider = TestServiceSetup.GetServiceProvider(); - _operations = serviceProvider.GetRequiredService(); - } - - private const int NUM_OBJECTS = 3001; - - private readonly SQLiteTransport _sut = new(); - - public void Dispose() => _sut.Dispose(); - - [Fact(DisplayName = "Pushing a commit locally")] - public async Task LocalUploadAndDownload() - { - var myObject = new Base(); - var rand = new Random(); - - myObject["@items"] = new List(); - - for (int i = 0; i < NUM_OBJECTS; i++) - { - ((List)myObject["@items"].NotNull()).Add( - new Point(i, i, i + rand.NextDouble()) { applicationId = i + "-___/---" } - ); - } - - using SQLiteTransport localTransport = new(); - (var objId01, var references) = await _operations.Send(myObject, localTransport, false); - - objId01.Should().NotBeNull(); - references.Count.Should().Be(NUM_OBJECTS); - - var commitPulled = await _operations.Receive(objId01.NotNull()); - - ((List)commitPulled["@items"].NotNull())[0].Should().BeOfType(); - ((List)commitPulled["@items"].NotNull()).Count.Should().Be(NUM_OBJECTS); - } - - [Fact(DisplayName = "Pushing and Pulling a commit locally")] - public async Task LocalUploadDownload() - { - var myObject = new Base(); - myObject["@items"] = new List(); - - var rand = new Random(); - - for (int i = 0; i < NUM_OBJECTS; i++) - { - ((List)myObject["@items"].NotNull()).Add( - new Point(i, i, i + rand.NextDouble()) { applicationId = i + "-___/---" } - ); - } - - (var objId01, _) = await _operations.Send(myObject, _sut, false); - - var commitPulled = await _operations.Receive(objId01); - List items = (List)commitPulled["@items"].NotNull(); - items.Should().AllSatisfy(x => x.Should().BeOfType()); - items.Count.Should().Be(NUM_OBJECTS); - } - - [Fact(DisplayName = "Pushing and pulling a commit locally")] - public async Task LocalUploadDownloadSmall() - { - var myObject = new Base(); - myObject["@items"] = new List(); - - var rand = new Random(); - - for (int i = 0; i < 30; i++) - { - ((List)myObject["@items"].NotNull()).Add( - new Point(i, i, i + rand.NextDouble()) { applicationId = i + "-ugh/---" } - ); - } - - (var objId01, _) = await _operations.Send(myObject, _sut, false); - - objId01.Should().NotBeNull(); - - var objsPulled = await _operations.Receive(objId01); - ((List)objsPulled["@items"].NotNull()).Count.Should().Be(30); - } - - [Fact(DisplayName = "Pushing and pulling a commit locally")] - public async Task LocalUploadDownloadListDic() - { - var myList = new List { 1, 2, 3, "ciao" }; - var myDic = new Dictionary - { - { "a", myList }, - { "b", 2 }, - { "c", "ciao" }, - }; - - var myObject = new Base(); - myObject["@dictionary"] = myDic; - myObject["@list"] = myList; - - (var _objId01, _) = await _operations.Send(myObject, _sut, false); - - _objId01.Should().NotBeNull(); - - var objsPulled = await _operations.Receive(_objId01); - ((List)((Dictionary)objsPulled["@dictionary"].NotNull())["a"]).First().Should().Be(1); - ((List)objsPulled["@list"].NotNull()).Last().Should().Be("ciao"); - } - - [Fact(DisplayName = "Pushing and pulling a random object, with or without detachment")] - public async Task UploadDownloadNonCommitObject() - { - var obj = new Base(); - // Here we are creating a "non-standard" object to act as a base for our multiple objects. - ((dynamic)obj).LayerA = new List(); // Layer a and b will be stored "in" the parent object, - ((dynamic)obj).LayerB = new List(); - ((dynamic)obj)["@LayerC"] = new List(); // whereas this "layer" will be stored as references only. - ((dynamic)obj)["@LayerD"] = new Point[] { new(), new(12, 3, 4) }; - var rand = new Random(); - - for (int i = 0; i < 30; i++) - { - ((List)((dynamic)obj).LayerA).Add(new Point(i, i, i + rand.NextDouble()) { applicationId = i + "foo" }); - } - - for (int i = 0; i < 30; i++) - { - ((List)((dynamic)obj).LayerB).Add(new Point(i, i, i + rand.NextDouble()) { applicationId = i + "bar" }); - } - - for (int i = 0; i < 30; i++) - { - ((List)((dynamic)obj)["@LayerC"]).Add(new Point(i, i, i + rand.NextDouble()) { applicationId = i + "baz" }); - } - - (var objId01, _) = await _operations.Send(obj, _sut, false); - - objId01.Should().NotBeNull(); - - var objPulled = await _operations.Receive(objId01); - - objPulled.Should().BeOfType(); - - // Note: even if the layers were originally declared as lists of "Base" objects, on deserialisation we cannot know that, - // as it's a dynamic property. Dynamic properties, if their content value is ambigous, will default to a common-sense standard. - // This specifically manifests in the case of lists and dictionaries: List will become List, and - // Dictionary will deserialize to Dictionary. - var layerA = ((dynamic)objPulled)["LayerA"] as List; - layerA?.Count.Should().Be(30); - - var layerC = (List)((dynamic)objPulled)["@LayerC"]; - layerC.Count.Should().Be(30); - layerC[0].Should().BeOfType(); - - var layerD = ((dynamic)objPulled)["@LayerD"] as List; - layerD?.Count.Should().Be(2); - } - - [Fact(DisplayName = "Should show progress!")] - public async Task UploadAndDownloadProgressReports() - { - Base myObject = new() { ["items"] = new List() }; - var rand = new Random(); - - for (int i = 0; i < 30; i++) - { - ((List)myObject["items"].NotNull()).Add( - new Point(i, i, i + rand.NextDouble()) { applicationId = i + "-fab/---" } - ); - } - - (var commitId02, _) = await _operations.Send(myObject, _sut, false); - - ProgressArgs? progress = null; - await _operations.Receive( - commitId02.NotNull(), - onProgressAction: new UnitTestProgress(x => - { - progress = x; - }) - ); - progress.Should().NotBeNull(); - } - - [Fact(DisplayName = "Should not dispose of transports if so specified.")] - public async Task ShouldNotDisposeTransports() - { - var @base = new Base(); - @base["test"] = "the best"; - - SQLiteTransport myLocalTransport = new(); - var sendResult = await _operations.Send(@base, myLocalTransport, false); - await _operations.Send(@base, myLocalTransport, false); - - _ = await _operations.Receive(sendResult.rootObjId, null, myLocalTransport); - await _operations.Receive(sendResult.rootObjId, null, myLocalTransport); - } -} diff --git a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SerializationTests.cs b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SerializationTests.cs deleted file mode 100644 index b119382d..00000000 --- a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SerializationTests.cs +++ /dev/null @@ -1,272 +0,0 @@ -using System.Drawing; -using FluentAssertions; -using Microsoft.Extensions.DependencyInjection; -using Speckle.Sdk.Api; -using Speckle.Sdk.Host; -using Speckle.Sdk.Models; -using Speckle.Sdk.Tests.Unit.Host; -using Point = Speckle.Sdk.Tests.Unit.Host.Point; - -namespace Speckle.Sdk.Tests.Unit.Api.Operations; - -public class ObjectSerialization -{ - private readonly IOperations _operations; - - public ObjectSerialization() - { - TypeLoader.Reset(); - TypeLoader.Initialize(typeof(Base).Assembly, typeof(DataChunk).Assembly, typeof(ColorMock).Assembly); - var serviceProvider = TestServiceSetup.GetServiceProvider(); - _operations = serviceProvider.GetRequiredService(); - } - - [Fact] - public async Task IgnoreCircularReferences() - { - var pt = new Point(1, 2, 3); - pt["circle"] = pt; - - var test = _operations.Serialize(pt); - - var result = await _operations.DeserializeAsync(test); - var circle = result["circle"]; - circle.Should().BeNull(); - } - - [Fact] - public async Task InterfacePropHandling() - { - Line tail = new() { Start = new Point(0, 0, 0), End = new Point(42, 42, 42) }; - PolygonalFeline cat = new() { Tail = tail }; - - for (int i = 0; i < 10; i++) - { - cat.Claws[$"Claw number {i}"] = new Line - { - Start = new Point(i, i, i), - End = new Point(i + 3.14, i + 3.14, i + 3.14), - }; - - if (i % 2 == 0) - { - cat.Whiskers.Add( - new Line { Start = new Point(i / 2, i / 2, i / 2), End = new Point(i + 3.14, i + 3.14, i + 3.14) } - ); - } - else - { - var brokenWhisker = new Polyline(); - brokenWhisker.Points.Add(new Point(-i, 0, 0)); - brokenWhisker.Points.Add(new Point(0, 0, 0)); - brokenWhisker.Points.Add(new Point(i, 0, 0)); - cat.Whiskers.Add(brokenWhisker); - } - - cat.Fur[i] = new Line { Start = new Point(i, i, i), End = new Point(i + 3.14, i + 3.14, i + 3.14) }; - } - - var result = _operations.Serialize(cat); - - var deserialisedFeline = await _operations.DeserializeAsync(result); - - deserialisedFeline.GetId().Should().Be(cat.GetId()); - } - - [Fact] - public async Task InheritanceTests() - { - var superPoint = new SuperPoint - { - X = 10, - Y = 10, - Z = 10, - W = 42, - }; - - var str = _operations.Serialize(superPoint); - var sstr = await _operations.DeserializeAsync(str); - - sstr.speckle_type.Should().Be(superPoint.speckle_type); - } - - [Fact] - public async Task ListDynamicProp() - { - var point = new Point(); - var test = new List(); - - for (var i = 0; i < 100; i++) - { - test.Add(new SuperPoint { W = i }); - } - - point["test"] = test; - - var str = _operations.Serialize(point); - var dsrls = await _operations.DeserializeAsync(str); - - var list = dsrls["test"] as List; - list.Should().NotBeNull(); // Ensure the list isn't null in first place - list!.Count.Should().Be(100); - } - - [Fact] - public async Task ChunkSerialisation() - { - var baseBasedChunk = new DataChunk() { data = new() }; - for (var i = 0; i < 200; i++) - { - baseBasedChunk.data.Add(new SuperPoint { W = i }); - } - - var stringBasedChunk = new DataChunk() { data = new() }; - for (var i = 0; i < 200; i++) - { - stringBasedChunk.data.Add(i + "_hai"); - } - - var doubleBasedChunk = new DataChunk() { data = new() }; - for (var i = 0; i < 200; i++) - { - doubleBasedChunk.data.Add(i + 0.33); - } - - var baseChunkString = _operations.Serialize(baseBasedChunk); - var stringChunkString = _operations.Serialize(stringBasedChunk); - var doubleChunkString = _operations.Serialize(doubleBasedChunk); - - var baseChunkDeserialised = (DataChunk)await _operations.DeserializeAsync(baseChunkString); - var stringChunkDeserialised = (DataChunk)await _operations.DeserializeAsync(stringChunkString); - var doubleChunkDeserialised = (DataChunk)await _operations.DeserializeAsync(doubleChunkString); - - baseChunkDeserialised.data.Count.Should().Be(baseBasedChunk.data.Count); - stringChunkDeserialised.data.Count.Should().Be(stringBasedChunk.data.Count); - doubleChunkDeserialised.data.Count.Should().Be(doubleBasedChunk.data.Count); - } - - [Fact] - public async Task ObjectWithChunksSerialisation() - { - const int MAX_NUM = 2020; - var mesh = new FakeMesh { ArrayOfDoubles = new double[MAX_NUM], ArrayOfLegs = new TableLeg[MAX_NUM] }; - - var customChunk = new List(); - var defaultChunk = new List(); - - for (int i = 0; i < MAX_NUM; i++) - { - mesh.Vertices.Add(i / 2); - customChunk.Add(i / 2); - defaultChunk.Add(i / 2); - mesh.Tables.Add(new Tabletop { length = 2000 }); - mesh.ArrayOfDoubles[i] = i * 3.3; - mesh.ArrayOfLegs[i] = new TableLeg { height = 2 + i }; - } - - mesh["@(800)CustomChunk"] = customChunk; - mesh["@()DefaultChunk"] = defaultChunk; - - var serialised = _operations.Serialize(mesh); - var deserialised = await _operations.DeserializeAsync(serialised); - - mesh.GetId().Should().Be(deserialised.GetId()); - } - - [Fact] - public void EmptyListSerialisationTests() - { - // NOTE: expected behaviour is that empty lists should serialize as empty lists. Don't ask why, it's complicated. - // Regarding chunkable empty lists, to prevent empty chunks, the expected behaviour is to have an empty lists, with no chunks inside. - var test = new Base(); - - test["@(5)emptyChunks"] = new List(); - test["emptyList"] = new List(); - test["@emptyDetachableList"] = new List(); - - // Note: nested empty lists should be preserved. - test["nestedList"] = new List { new List { new List() } }; - test["@nestedDetachableList"] = new List { new List { new List() } }; - - var serialised = _operations.Serialize(test); - var isCorrect = - serialised.Contains("\"@(5)emptyChunks\":[]") - && serialised.Contains("\"emptyList\":[]") - && serialised.Contains("\"@emptyDetachableList\":[]") - && serialised.Contains("\"nestedList\":[[[]]]") - && serialised.Contains("\"@nestedDetachableList\":[[[]]]"); - - isCorrect.Should().BeTrue(); - } - - [SpeckleType("Speckle.Core.Tests.Unit.Api.Operations.ObjectSerialization+DateMock")] - private class DateMock : Base - { - public DateTime TestField { get; set; } - } - - [Fact] - public async Task DateSerialisation() - { - var date = new DateTime(2020, 1, 14); - var mockBase = new DateMock { TestField = date }; - - var result = _operations.Serialize(mockBase); - var test = (DateMock)await _operations.DeserializeAsync(result); - - test.TestField.Should().Be(date); - } - - [SpeckleType("Speckle.Core.Tests.Unit.Api.Operations.ObjectSerialization+GUIDMock")] - private class GUIDMock : Base - { - public Guid TestField { get; set; } - } - - [Fact] - public async Task GuidSerialisation() - { - var guid = Guid.NewGuid(); - var mockBase = new GUIDMock { TestField = guid }; - - var result = _operations.Serialize(mockBase); - var test = (GUIDMock)await _operations.DeserializeAsync(result); - - test.TestField.Should().Be(guid); - } - - [SpeckleType("Speckle.Core.Tests.Unit.Api.Operations.ObjectSerialization+ColorMock")] - private class ColorMock : Base - { - public Color TestField { get; set; } - } - - [Fact] - public async Task ColorSerialisation() - { - var color = Color.FromArgb(255, 4, 126, 251); - var mockBase = new ColorMock { TestField = color }; - - var result = _operations.Serialize(mockBase); - var test = (ColorMock)await _operations.DeserializeAsync(result); - - test.TestField.Should().Be(color); - } - - [SpeckleType("Speckle.Core.Tests.Unit.Api.Operations.ObjectSerialization+StringDateTimeRegressionMock")] - private class StringDateTimeRegressionMock : Base - { - public string TestField { get; set; } - } - - [Fact] - public async Task StringDateTimeRegression() - { - var mockBase = new StringDateTimeRegressionMock { TestField = "2021-11-12T11:32:01" }; - - var result = _operations.Serialize(mockBase); - var test = (StringDateTimeRegressionMock)await _operations.DeserializeAsync(result); - - test.TestField.Should().Be(mockBase.TestField); - } -} diff --git a/tests/Speckle.Sdk.Tests.Unit/Fixtures.cs b/tests/Speckle.Sdk.Tests.Unit/Fixtures.cs index 65f49f12..3d4c004c 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Fixtures.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Fixtures.cs @@ -1,14 +1,27 @@ +using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Speckle.Sdk.Common; using Speckle.Sdk.Credentials; using Speckle.Sdk.Logging; -using Speckle.Sdk.Transports; +using Speckle.Sdk.SQLite; +using Speckle.Sdk.Tests.Unit.Host; +using Xunit.Sdk; namespace Speckle.Sdk.Tests.Unit; public abstract class Fixtures { - private static readonly SQLiteTransport s_accountStorage = new(scope: "Accounts"); + static Fixtures() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSpeckleSdk(new("Tests", "test"), "v3", typeof(TestClass).Assembly, typeof(Polyline).Assembly); + var serviceProvider = serviceCollection.BuildServiceProvider(); + + var factory = serviceProvider.GetRequiredService(); + s_accountStorage = factory.CreateForUser("Accounts"); + } + + private static readonly ISqLiteJsonCacheManager s_accountStorage; private static readonly string s_accountPath = Path.Combine( SpecklePathProvider.AccountsFolderPath, @@ -19,7 +32,7 @@ public static void UpdateOrSaveAccount(Account account) { DeleteLocalAccount(account.id.NotNull()); string serializedObject = JsonConvert.SerializeObject(account); - s_accountStorage.SaveObjectSync(account.id, serializedObject); + s_accountStorage.SaveObject(account.id, serializedObject); } public static void SaveLocalAccount(Account account) diff --git a/tests/Speckle.Sdk.Tests.Unit/Models/Hashing.cs b/tests/Speckle.Sdk.Tests.Unit/Models/Hashing.cs deleted file mode 100644 index 9de09c2d..00000000 --- a/tests/Speckle.Sdk.Tests.Unit/Models/Hashing.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System.Diagnostics; -using FluentAssertions; -using Speckle.Sdk.Host; -using Speckle.Sdk.Models; -using Speckle.Sdk.Tests.Unit.Host; - -namespace Speckle.Sdk.Tests.Unit.Models; - -// Removed [TestFixture] and [TestOf] annotations as they are NUnit specific -public class Hashing -{ - public Hashing() - { - TypeLoader.Reset(); - TypeLoader.Initialize(typeof(Base).Assembly, typeof(DiningTable).Assembly); - } - - [Fact(DisplayName = "Checks that hashing (as represented by object IDs) actually works.")] - public void HashChangeCheck_Test() - { - var table = new DiningTable(); - var secondTable = new DiningTable(); - - secondTable.GetId().Should().Be(table.GetId(), "Object IDs of identical objects should match."); - - ((dynamic)secondTable).testProp = "wonderful"; - - secondTable.GetId().Should().NotBe(table.GetId(), "Changing a property should alter the object ID."); - } - - [Fact(DisplayName = "Verifies that dynamic properties with '__' prefix are ignored during hashing.")] - public void IgnoredDynamicPropertiesCheck_Test() - { - var table = new DiningTable(); - var originalHash = table.GetId(); - - ((dynamic)table).__testProp = "wonderful"; - - table - .GetId() - .Should() - .Be(originalHash, "Hashing of table should not change when '__' prefixed properties are added."); - } - - [Fact(DisplayName = "Performance test: Hash computation time for large and small objects.")] - public void HashingPerformance_Test() - { - var polyline = new Polyline(); - - for (int i = 0; i < 1000; i++) - { - polyline.Points.Add(new Point { X = i * 2, Y = i % 2 }); - } - - var stopWatch = new Stopwatch(); - stopWatch.Start(); - - // Warm-up: first hashing always takes longer due to json serialisation init - _ = polyline.GetId(); - var stopWatchStep = stopWatch.ElapsedMilliseconds; - _ = polyline.GetId(); - - var diff1 = stopWatch.ElapsedMilliseconds - stopWatchStep; - diff1.Should().BeLessThan(300, $"Hashing shouldn't take that long ({diff1} ms) for the test object used."); - Console.WriteLine($"Big obj hash duration: {diff1} ms"); - - var pt = new Point - { - X = 10, - Y = 12, - Z = 30, - }; - stopWatchStep = stopWatch.ElapsedMilliseconds; - _ = pt.GetId(); - - var diff2 = stopWatch.ElapsedMilliseconds - stopWatchStep; - diff2.Should().BeLessThan(10, $"Hashing shouldn't take that long ({diff2} ms) for the point object used."); - Console.WriteLine($"Small obj hash duration: {diff2} ms"); - } - - [Fact(DisplayName = "Verifies that decomposed and non-decomposed objects have different hashes.")] - public void DecompositionHashes_Test() - { - var table = new DiningTable(); - ((dynamic)table)["@decomposeMePlease"] = new Point(); - - var hash1 = table.GetId(); - var hash2 = table.GetId(true); - - hash2.Should().NotBe(hash1, "Hash values should differ for decomposed and non-decomposed objects."); - } -} diff --git a/tests/Speckle.Sdk.Tests.Unit/Serialisation/ChunkingTests.cs b/tests/Speckle.Sdk.Tests.Unit/Serialisation/ChunkingTests.cs deleted file mode 100644 index 3fa44af0..00000000 --- a/tests/Speckle.Sdk.Tests.Unit/Serialisation/ChunkingTests.cs +++ /dev/null @@ -1,55 +0,0 @@ -using FluentAssertions; -using Speckle.Newtonsoft.Json; -using Speckle.Sdk.Host; -using Speckle.Sdk.Models; -using Speckle.Sdk.Serialisation; -using Speckle.Sdk.Transports; - -namespace Speckle.Sdk.Tests.Unit.Serialisation; - -public class ChunkingTests -{ - public static IEnumerable TestCases() - { - // Initialize type loader - TypeLoader.Reset(); - TypeLoader.Initialize(typeof(Base).Assembly, typeof(IgnoreTest).Assembly); - - // Return test data as a collection of objects for xUnit - yield return [CreateDynamicTestCase(10, 100), 10]; - yield return [CreateDynamicTestCase(0.5, 100), 1]; - yield return [CreateDynamicTestCase(20.5, 100), 21]; - - yield return [CreateDynamicTestCase(10, 1000), 10]; - yield return [CreateDynamicTestCase(0.5, 1000), 1]; - yield return [CreateDynamicTestCase(20.5, 1000), 21]; - } - - [Theory] - [MemberData(nameof(TestCases))] - public void ChunkSerializationTest(Base testCase, int expectedChunkCount) - { - // Arrange - var transport = new MemoryTransport(); - var sut = new SpeckleObjectSerializer([transport]); - - // Act - _ = sut.Serialize(testCase); - var serializedObjects = transport - .Objects.Values.Select(json => JsonConvert.DeserializeObject>(json)) - .ToList(); - - var numberOfChunks = serializedObjects.Count(x => - x!.TryGetValue("speckle_type", out var speckleType) && ((string)speckleType!) == "Speckle.Core.Models.DataChunk" - ); - - numberOfChunks.Should().Be(expectedChunkCount); - } - - private static Base CreateDynamicTestCase(double numberOfChunks, int chunkSize) - { - // Helper method to create the dynamic test case - var value = Enumerable.Range(0, (int)Math.Floor(chunkSize * numberOfChunks)).ToList(); - return new Base { [$"@({chunkSize})chunkedProperty"] = value }; - } -} diff --git a/tests/Speckle.Sdk.Tests.Unit/Serialisation/JsonIgnoreAttributeTests.cs b/tests/Speckle.Sdk.Tests.Unit/Serialisation/JsonIgnoreAttributeTests.cs index 7861b723..3e288701 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Serialisation/JsonIgnoreAttributeTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Serialisation/JsonIgnoreAttributeTests.cs @@ -1,10 +1,6 @@ -using FluentAssertions; using Speckle.Newtonsoft.Json; -using Speckle.Sdk.Common; using Speckle.Sdk.Host; using Speckle.Sdk.Models; -using Speckle.Sdk.Serialisation; -using Speckle.Sdk.Transports; namespace Speckle.Sdk.Tests.Unit.Serialisation; @@ -38,87 +34,33 @@ public static IEnumerable IgnoredCompoundTestCases() yield return ["this one is not", EXPECTED_PAYLOAD, EXPECTED_HASH]; } - [Theory] - [MemberData(nameof(IgnoredTestCases))] - public void IgnoredProperties_NotIncludedInJson(string ignoredPayload, string expectedPayload, string expectedHash) + [SpeckleType("Speckle.Sdk.Test.Unit.Serialisation.IgnoredCompoundTest")] + public sealed class IgnoredCompoundTest(string ignoredPayload, string expectedPayload) : Base { - IgnoreTest testData = new(ignoredPayload, expectedPayload); + [JsonIgnore] + public Base ShouldBeIgnored => new IgnoreTest(ignoredPayload, expectedPayload) { ["override"] = ignoredPayload }; - SpeckleObjectSerializer sut = new(); + public Base ShouldBeIncluded => new IgnoreTest(ignoredPayload, expectedPayload); - var result = sut.SerializeBase(testData); - result.Should().NotBeNull(); - result!.Value.Id.Should().NotBeNull(); + [JsonIgnore, DetachProperty] + public Base ShouldBeIgnoredDetached => ShouldBeIgnored; - var jsonString = result.Value.Json.ToString(); - jsonString.Should().NotContain(nameof(testData.ShouldBeIgnored)); - jsonString.Should().NotContain(ignoredPayload); + [DetachProperty] + public Base ShouldBeIncludedDetached => ShouldBeIncluded; - jsonString.Should().Contain(nameof(testData.ShouldBeIncluded)); - jsonString.Should().Contain(expectedPayload); + [JsonIgnore] + public List ShouldBeIgnoredList => [ShouldBeIgnored]; - result.Value.Id!.Value.Value.Should().Be(expectedHash); - } - - [Theory] - [MemberData(nameof(IgnoredCompoundTestCases))] - public void IgnoredProperties_Compound_NotIncludedInJson( - string ignoredPayload, - string expectedPayload, - string expectedHash - ) - { - IgnoredCompoundTest testData = new(ignoredPayload, expectedPayload); - - MemoryTransport savedObjects = new(); - SpeckleObjectSerializer sut = new(writeTransports: [savedObjects]); - - var result = sut.SerializeBase(testData); - var (json, id) = result.NotNull(); - json.Value.Should().NotBeNull(); - id.Should().NotBeNull(); + [JsonIgnore, DetachProperty] + public List ShouldBeIgnoredDetachedList => ShouldBeIgnoredList; - savedObjects.SaveObject(id!.Value.Value.NotNull(), json.Value); + public List ShouldBeIncludedList => [ShouldBeIncluded]; - foreach ((_, string childJson) in savedObjects.Objects) - { - childJson.Should().NotContain(nameof(testData.ShouldBeIgnored)); - childJson.Should().NotContain(ignoredPayload); - - childJson.Should().Contain(nameof(testData.ShouldBeIncluded)); - childJson.Should().Contain(expectedPayload); - } - - id.Value.Value.Should().Be(expectedHash); + [DetachProperty] + public List ShouldBeIncludedDetachedList => ShouldBeIncludedList; } } -[SpeckleType("Speckle.Sdk.Test.Unit.Serialisation.IgnoredCompoundTest")] -public sealed class IgnoredCompoundTest(string ignoredPayload, string expectedPayload) : Base -{ - [JsonIgnore] - public Base ShouldBeIgnored => new IgnoreTest(ignoredPayload, expectedPayload) { ["override"] = ignoredPayload }; - - public Base ShouldBeIncluded => new IgnoreTest(ignoredPayload, expectedPayload); - - [JsonIgnore, DetachProperty] - public Base ShouldBeIgnoredDetached => ShouldBeIgnored; - - [DetachProperty] - public Base ShouldBeIncludedDetached => ShouldBeIncluded; - - [JsonIgnore] - public List ShouldBeIgnoredList => [ShouldBeIgnored]; - - [JsonIgnore, DetachProperty] - public List ShouldBeIgnoredDetachedList => ShouldBeIgnoredList; - - public List ShouldBeIncludedList => [ShouldBeIncluded]; - - [DetachProperty] - public List ShouldBeIncludedDetachedList => ShouldBeIncludedList; -} - [SpeckleType("Speckle.Sdk.Tests.Unit.Serialisation.IgnoreTest")] public sealed class IgnoreTest(string shouldBeIgnoredPayload, string shouldBeIncludedPayload) : Base { diff --git a/tests/Speckle.Sdk.Tests.Unit/Serialisation/SimpleRoundTripTests.cs b/tests/Speckle.Sdk.Tests.Unit/Serialisation/SimpleRoundTripTests.cs index 16d80a9e..cc19cd5b 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Serialisation/SimpleRoundTripTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Serialisation/SimpleRoundTripTests.cs @@ -1,5 +1,4 @@ -using FluentAssertions; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Speckle.Sdk.Api; using Speckle.Sdk.Models; using Speckle.Sdk.Tests.Unit.Host; @@ -29,14 +28,4 @@ public static IEnumerable TestDataInternal() } yield return polyline; } - - [Theory] - [MemberData(nameof(TestData))] - public async Task SimpleSerialization(Base testData) - { - var result = _operations.Serialize(testData); - var test = await _operations.DeserializeAsync(result); - - testData.GetId().Should().Be(test.GetId()); - } } diff --git a/tests/Speckle.Sdk.Tests.Unit/Transports/MemoryTransportTests.cs b/tests/Speckle.Sdk.Tests.Unit/Transports/MemoryTransportTests.cs deleted file mode 100644 index a3567f69..00000000 --- a/tests/Speckle.Sdk.Tests.Unit/Transports/MemoryTransportTests.cs +++ /dev/null @@ -1,22 +0,0 @@ -// MemoryTransportTests.cs - -using FluentAssertions; -using Speckle.Sdk.Common; -using Speckle.Sdk.Transports; - -namespace Speckle.Sdk.Tests.Unit.Transports; - -public sealed class MemoryTransportTests : TransportTests -{ - protected override ITransport Sut => _memoryTransport.NotNull(); - private readonly MemoryTransport _memoryTransport; - - // Constructor used for setup in xUnit - public MemoryTransportTests() - { - _memoryTransport = new MemoryTransport(); - } - - [Fact] - public void TransportName_ShouldSetProperly() => _memoryTransport.TransportName.Should().Be("Memory"); -} diff --git a/tests/Speckle.Sdk.Tests.Unit/Transports/SQLiteTransport2Tests.cs b/tests/Speckle.Sdk.Tests.Unit/Transports/SQLiteTransport2Tests.cs deleted file mode 100644 index 55b46392..00000000 --- a/tests/Speckle.Sdk.Tests.Unit/Transports/SQLiteTransport2Tests.cs +++ /dev/null @@ -1,148 +0,0 @@ -using FluentAssertions; -using Microsoft.Data.Sqlite; -using Speckle.Sdk.Caching; -using Speckle.Sdk.Common; -using Speckle.Sdk.Transports; - -namespace Speckle.Sdk.Tests.Unit.Transports; - -public sealed class SQLiteTransport2Tests : TransportTests, IDisposable -{ - protected override ITransport? Sut => _sqlite; - - private SQLiteTransport2? _sqlite; - - private static readonly string s_name = $"test-{Guid.NewGuid()}"; - private static readonly string s_basePath = ModelCacheManager.GetDbPath(s_name); - - public SQLiteTransport2Tests() - { - _sqlite = new SQLiteTransport2(s_name); - } - - public void Dispose() - { - _sqlite?.Dispose(); - SqliteConnection.ClearAllPools(); - if (File.Exists(s_basePath)) - { - File.Delete(s_basePath); - } - - _sqlite = null; - } - - [Fact] - public void DbCreated_AfterInitialization() - { - bool fileExists = File.Exists(s_basePath); - fileExists.Should().BeTrue(); - } - - [Fact(DisplayName = "Tests that an object can be updated")] - public async Task UpdateObject_AfterAdd() - { - const string PAYLOAD_ID = "MyTestObjectId"; - const string PAYLOAD_DATA = "MyTestObjectData"; - - _sqlite.NotNull().SaveObject(PAYLOAD_ID, PAYLOAD_DATA); - await _sqlite.WriteComplete(); - - const string NEW_PAYLOAD = "MyEvenBetterObjectData"; - _sqlite.UpdateObject(PAYLOAD_ID, NEW_PAYLOAD); - await _sqlite.WriteComplete(); - - var result = await _sqlite.GetObject(PAYLOAD_ID); - result.Should().Be(NEW_PAYLOAD); - } - - [Fact(DisplayName = "Tests that updating an object that hasn't been saved previously adds the object to the DB")] - public async Task UpdateObject_WhenMissing() - { - const string PAYLOAD_ID = "MyTestObjectId"; - const string PAYLOAD_DATA = "MyTestObjectData"; - - var preUpdate = await _sqlite.NotNull().GetObject(PAYLOAD_ID); - preUpdate.Should().BeNull(); - - _sqlite.UpdateObject(PAYLOAD_ID, PAYLOAD_DATA); - await _sqlite.WriteComplete(); - - var postUpdate = await _sqlite.GetObject(PAYLOAD_ID); - postUpdate.Should().Be(PAYLOAD_DATA); - } - - [Fact] - public async Task SaveAndRetrieveObject_Sync() - { - const string PAYLOAD_ID = "MyTestObjectId"; - const string PAYLOAD_DATA = "MyTestObjectData"; - - var preAdd = await Sut.NotNull().GetObject(PAYLOAD_ID); - preAdd.Should().BeNull(); - - _sqlite.NotNull().SaveObjectSync(PAYLOAD_ID, PAYLOAD_DATA); - - { - var postAdd = await Sut.GetObject(PAYLOAD_ID); - postAdd.Should().Be(PAYLOAD_DATA); - } - } - - [Fact(DisplayName = "Tests enumerating through all objects while updating them without infinite loop")] - public void UpdateObject_WhileEnumerating() - { - const string UPDATE_STRING = "_new"; - Dictionary testData = new() - { - { "a", "This is object a" }, - { "b", "This is object b" }, - { "c", "This is object c" }, - { "d", "This is object d" }, - }; - int length = testData.Values.First().Length; - - foreach (var (key, data) in testData) - { - _sqlite.NotNull().SaveObjectSync(key, data); - } - - foreach (var o in _sqlite.NotNull().GetAllObjects()) - { - string newData = o + UPDATE_STRING; - string key = $"{o[length - 1]}"; - - _sqlite.UpdateObject(key, newData); - } - - // Assert that objects were updated - _sqlite.GetAllObjects().ToList().Should().AllSatisfy(o => o.Should().Contain(UPDATE_STRING)); - // Assert that objects were only updated once - _sqlite.GetAllObjects().ToList().Should().AllSatisfy(o => o.Should().HaveLength(length + UPDATE_STRING.Length)); - } - - [Theory(DisplayName = "Tests that GetAllObjects can be called concurrently from multiple threads")] - [InlineData(6, 32)] - public void GetAllObjects_IsThreadSafe(int dataSize, int parallelism) - { - foreach (int i in Enumerable.Range(0, dataSize)) - { - _sqlite.NotNull().SaveObjectSync(i.ToString(), Guid.NewGuid().ToString()); - } - - List[] results = new List[parallelism]; - Parallel.ForEach( - Enumerable.Range(0, parallelism), - i => - { - results[i] = _sqlite.NotNull().GetAllObjects().ToList(); - } - ); - - foreach (var result in results) - { - result.Should().BeEquivalentTo(results[0]); - result.Count.Should().Be(dataSize); - } - } -} diff --git a/tests/Speckle.Sdk.Tests.Unit/Transports/SQLiteTransportTests.cs b/tests/Speckle.Sdk.Tests.Unit/Transports/SQLiteTransportTests.cs deleted file mode 100644 index 9c759b6f..00000000 --- a/tests/Speckle.Sdk.Tests.Unit/Transports/SQLiteTransportTests.cs +++ /dev/null @@ -1,143 +0,0 @@ -using FluentAssertions; -using Microsoft.Data.Sqlite; -using Speckle.Sdk.Transports; - -namespace Speckle.Sdk.Tests.Unit.Transports; - -public sealed class SQLiteTransportTests : TransportTests, IDisposable -{ - protected override ITransport? Sut => _sqlite; - - private readonly SQLiteTransport _sqlite; - - private static readonly string s_basePath = $"./temp {Guid.NewGuid()}"; - private const string APPLICATION_NAME = "Speckle Integration Tests"; - - // Constructor replaces [SetUp] - public SQLiteTransportTests() - { - _sqlite = new SQLiteTransport(s_basePath, APPLICATION_NAME); - } - - // Disposal replaces [TearDown] for cleanup - public void Dispose() - { - _sqlite?.Dispose(); - SqliteConnection.ClearAllPools(); - Directory.Delete(s_basePath, true); - } - - [Fact] - public void DbCreated_AfterInitialization() - { - bool fileExists = File.Exists($"{s_basePath}/{APPLICATION_NAME}/Data.db"); - fileExists.Should().BeTrue(); - } - - [Fact] - public async Task UpdateObject_AfterAdd() - { - const string PAYLOAD_ID = "MyTestObjectId"; - const string PAYLOAD_DATA = "MyTestObjectData"; - - _sqlite!.SaveObject(PAYLOAD_ID, PAYLOAD_DATA); - await _sqlite.WriteComplete(); - - const string NEW_PAYLOAD = "MyEvenBetterObjectData"; - _sqlite.UpdateObject(PAYLOAD_ID, NEW_PAYLOAD); - await _sqlite.WriteComplete(); - - var result = await _sqlite.GetObject(PAYLOAD_ID); - result.Should().Be(NEW_PAYLOAD); - } - - [Fact] - public async Task UpdateObject_WhenMissing() - { - const string PAYLOAD_ID = "MyTestObjectId"; - const string PAYLOAD_DATA = "MyTestObjectData"; - - var preUpdate = await _sqlite!.GetObject(PAYLOAD_ID); - preUpdate.Should().BeNull(); - - _sqlite.UpdateObject(PAYLOAD_ID, PAYLOAD_DATA); - await _sqlite.WriteComplete(); - - var postUpdate = await _sqlite.GetObject(PAYLOAD_ID); - postUpdate.Should().Be(PAYLOAD_DATA); - } - - [Fact] - public async Task SaveAndRetrieveObject_Sync() - { - const string PAYLOAD_ID = "MyTestObjectId"; - const string PAYLOAD_DATA = "MyTestObjectData"; - - var preAdd = await Sut!.GetObject(PAYLOAD_ID); - preAdd.Should().BeNull(); - - _sqlite!.SaveObjectSync(PAYLOAD_ID, PAYLOAD_DATA); - - { - var postAdd = await Sut.GetObject(PAYLOAD_ID); - postAdd.Should().Be(PAYLOAD_DATA); - } - } - - [Fact] // No xUnit [Timeout], so this is purely indicative - public void UpdateObject_WhileEnumerating() - { - const string UPDATE_STRING = "_new"; - Dictionary testData = new() - { - { "a", "This is object a" }, - { "b", "This is object b" }, - { "c", "This is object c" }, - { "d", "This is object d" }, - }; - int length = testData.Values.First().Length; - - foreach (var (key, data) in testData) - { - _sqlite!.SaveObjectSync(key, data); - } - - foreach (var o in _sqlite.GetAllObjects()) - { - string newData = o + UPDATE_STRING; - string key = $"{o[length - 1]}"; - - _sqlite.UpdateObject(key, newData); - } - - // Assert that objects were updated - _sqlite.GetAllObjects().ToList().Should().AllSatisfy(o => o.Should().Contain(UPDATE_STRING)); - // Assert that objects were only updated once - _sqlite.GetAllObjects().ToList().Should().AllSatisfy(o => o.Should().HaveLength(length + UPDATE_STRING.Length)); - } - - [Theory] - [InlineData(6, 32)] - public void GetAllObjects_IsThreadSafe(int dataSize, int parallelism) - { - foreach (int i in Enumerable.Range(0, dataSize)) - { - _sqlite!.SaveObjectSync(i.ToString(), Guid.NewGuid().ToString()); - } - - List[] results = new List[parallelism]; - Parallel.ForEach( - Enumerable.Range(0, parallelism), - i => - { - results[i] = _sqlite.GetAllObjects().ToList(); - } - ); - - foreach (var result in results) - { - result.Should().BeEquivalentTo(results[0]); - result.Count.Should().Be(dataSize); - } - } -} diff --git a/tests/Speckle.Sdk.Tests.Unit/Transports/TransportTests.cs b/tests/Speckle.Sdk.Tests.Unit/Transports/TransportTests.cs deleted file mode 100644 index 4a409b1b..00000000 --- a/tests/Speckle.Sdk.Tests.Unit/Transports/TransportTests.cs +++ /dev/null @@ -1,155 +0,0 @@ -using FluentAssertions; -using Speckle.Newtonsoft.Json; -using Speckle.Sdk.Common; -using Speckle.Sdk.Transports; - -namespace Speckle.Sdk.Tests.Unit.Transports; - -public abstract class TransportTests -{ - protected abstract ITransport? Sut { get; } - - [Fact] - public async Task SaveAndRetrieveObject() - { - const string PAYLOAD_ID = "MyTestObjectId"; - const string PAYLOAD_DATA = "MyTestObjectData"; - - { - var preAdd = await Sut.NotNull().GetObject(PAYLOAD_ID); - preAdd.Should().BeNull(); - } - - Sut.SaveObject(PAYLOAD_ID, PAYLOAD_DATA); - await Sut.WriteComplete(); - - { - var postAdd = await Sut.GetObject(PAYLOAD_ID); - postAdd.Should().Be(PAYLOAD_DATA); - } - } - - [Fact] - public async Task HasObject() - { - const string PAYLOAD_ID = "MyTestObjectId"; - const string PAYLOAD_DATA = "MyTestObjectData"; - - { - var preAdd = await Sut.NotNull().HasObjects([PAYLOAD_ID]); - preAdd.Count.Should().Be(1); - preAdd.Values.Should().NotContain(true); - preAdd.Keys.Should().Contain(PAYLOAD_ID); - } - - Sut.SaveObject(PAYLOAD_ID, PAYLOAD_DATA); - await Sut.WriteComplete(); - - { - var postAdd = await Sut.HasObjects([PAYLOAD_ID]); - postAdd.Count.Should().Be(1); - postAdd.Values.Should().NotContain(false); - postAdd.Keys.Should().Contain(PAYLOAD_ID); - } - } - - [Fact] - public async Task SaveObject_ConcurrentWrites() - { - const int TEST_DATA_COUNT = 100; - List<(string id, string data)> testData = Enumerable - .Range(0, TEST_DATA_COUNT) - .Select(_ => (Guid.NewGuid().ToString(), Guid.NewGuid().ToString())) - .ToList(); - - Parallel.ForEach( - testData, - x => - { - Sut.NotNull().SaveObject(x.id, x.data); - } - ); - - await Sut.NotNull().WriteComplete(); - - //Test: HasObjects - var ids = testData.Select(x => x.id).ToList(); - var hasObjectsResult = await Sut.HasObjects(ids); - - hasObjectsResult.Values.Should().NotContain(false); - hasObjectsResult.Keys.Should().BeEquivalentTo(ids); - - //Test: GetObjects - foreach (var x in testData) - { - var res = await Sut.GetObject(x.id); - res.Should().Be(x.data); - } - } - - [Fact] - public void ToString_IsNotEmpty() - { - var toString = Sut.NotNull().ToString(); - toString.Should().NotBeNullOrEmpty(); - } - - [Fact] - public void TransportName_IsNotEmpty() - { - var toString = Sut.NotNull().TransportName; - toString.Should().NotBeNullOrEmpty(); - } - - [Fact] - public async Task SaveObject_ExceptionThrown_TaskIsCanceled() - { - using CancellationTokenSource tokenSource = new(); - Sut.NotNull().CancellationToken = tokenSource.Token; - - await tokenSource.CancelAsync(); - - await FluentActions - .Invoking(async () => - { - Sut.SaveObject("abcdef", "fake payload data"); - await Sut.WriteComplete(); - }) - .Should() - .ThrowAsync(); - } - - [Fact] - public async Task CopyObjectAndChildren() - { - //Assemble - const int TEST_DATA_COUNT = 100; - List<(string id, string data)> testData = Enumerable - .Range(0, TEST_DATA_COUNT) - .Select(_ => (Guid.NewGuid().ToString(), Guid.NewGuid().ToString())) - .ToList(); - - foreach (var x in testData) - { - Sut.NotNull().SaveObject(x.id, x.data); - } - - var parent = JsonConvert.SerializeObject( - new TransportHelpers.Placeholder() { __closure = testData.Select(x => x.id).ToDictionary(x => x, _ => 1) } - ); - Sut.NotNull().SaveObject("root", parent); - - await Sut.WriteComplete(); - - // Act - MemoryTransport destination = new(); - await Sut.CopyObjectAndChildren("root", destination); - - //Assert - foreach (var (expectedId, expectedData) in testData) - { - var actual = await destination.GetObject(expectedId); - actual.Should().Be(expectedData); - } - } -}