Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c3f944dcf1 | |||
| 8890f8cb36 | |||
| a0eae88479 | |||
| 8785e49f73 | |||
| 6e35d6af6d | |||
| c2735f0a32 | |||
| 0b01091209 | |||
| 98223e251c | |||
| 08f702794a | |||
| 879ebf7e3c | |||
| 1046e2aafc | |||
| 5cb0eddf4e | |||
| e97ce83c6b | |||
| ea23e72c77 | |||
| 37358570ec | |||
| 02b9a73164 | |||
| 530387b87c | |||
| b16e32d1ff | |||
| d91fccbb10 | |||
| 62f687b589 | |||
| c0d4d951df | |||
| 4e1753e1fd | |||
| 4c615ae8c6 | |||
| d5ee9fb76c | |||
| ea118bcdbb | |||
| ba456ee3eb | |||
| 10d283a9f7 | |||
| 647c4733cb |
@@ -3,11 +3,11 @@
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"csharpier": {
|
||||
"version": "1.0.2",
|
||||
"version": "1.1.2",
|
||||
"commands": [
|
||||
"csharpier"
|
||||
],
|
||||
"rollForward": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: 8.x.x
|
||||
|
||||
|
||||
@@ -2,17 +2,22 @@ name: .NET Build and Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ["3.*"]
|
||||
tags: ["3.*.*"]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: 'nuget.org'
|
||||
permissions:
|
||||
id-token: write # enable GitHub OIDC token issuance for this job
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: 8.x.x
|
||||
|
||||
@@ -20,7 +25,7 @@ jobs:
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
|
||||
|
||||
|
||||
- id: set-version
|
||||
name: Set version to output
|
||||
run: |
|
||||
@@ -37,18 +42,24 @@ jobs:
|
||||
|
||||
echo $SEMVER
|
||||
echo $FILE_VERSION
|
||||
|
||||
|
||||
- name: 🔫 Build and Pack
|
||||
run: ./build.sh pack
|
||||
env:
|
||||
SEMVER: ${{ steps.set-version.outputs.SEMVER }}
|
||||
FILE_VERSION: ${{ steps.set-version.outputs.FILE_VERSION }}
|
||||
|
||||
|
||||
- name: Upload coverage reports to Codecov with GitHub Action
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
files: tests/**/coverage.xml
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
- name: NuGet login (OIDC → temp API key)
|
||||
uses: NuGet/login@v1
|
||||
id: login
|
||||
with:
|
||||
user: ${{ secrets.NUGET_USER }}
|
||||
|
||||
- name: Push to nuget.org
|
||||
run: dotnet nuget push output/*.nupkg --source "https://api.nuget.org/v3/index.json" --api-key ${{secrets.CONNECTORS_NUGET_TOKEN }} --skip-duplicate
|
||||
run: dotnet nuget push output/*.nupkg --source "https://api.nuget.org/v3/index.json" --api-key ${{steps.login.outputs.NUGET_API_KEY}}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup Condition="'$(IsTestProject)' == 'true'">
|
||||
<NoWarn>
|
||||
<!-- Things we need to test -->
|
||||
CS0618;CA1034;CA2201;CA1051;CA1040;CA1724;
|
||||
CS0618;CA1034;CA2201;CA1051;CA1040;CA1724;CA1065;
|
||||
IDE0044;IDE0130;CA1508;
|
||||
<!-- Analysers that provide no tangeable value to a test project -->
|
||||
CA5394;CA2007;CA1852;CA1819;CA1711;CA1063;CA1816;CA2234;CS8618;CA1054;CA1810;CA2208;CA1019;CA1831;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using Speckle.Objects.Geometry;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Other;
|
||||
|
||||
/// <summary>
|
||||
/// Camera class to represent a perspective camera for a 3D view.
|
||||
/// </summary>
|
||||
/// <remarks>Assumes a Z-up, right-handed convention for orientation vectors</remarks>
|
||||
[SpeckleType("Objects.Other.Camera")]
|
||||
public class Camera : Base
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the view that is created by this camera
|
||||
/// </summary>
|
||||
public required string name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The location of the camera
|
||||
/// </summary>
|
||||
public required Point position { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The unit up vector of the camera
|
||||
/// </summary>
|
||||
public required Vector up { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The unit forward vector of the camera
|
||||
/// </summary>
|
||||
public required Vector forward { get; set; }
|
||||
}
|
||||
@@ -35,6 +35,6 @@ public class RenderMaterial : Base
|
||||
public Color emissiveColor
|
||||
{
|
||||
get => Color.FromArgb(emissive);
|
||||
set => diffuse = value.ToArgb();
|
||||
set => emissive = value.ToArgb();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ public sealed class Comment
|
||||
public string rawText { get; init; }
|
||||
public ResourceCollection<Comment> replies { get; init; }
|
||||
public CommentReplyAuthorCollection replyAuthors { get; init; }
|
||||
public List<ResourceIdentifier> resources { get; init; }
|
||||
public List<ResourceIdentifier> resources { get; init; } //todo: add resourceIds/baseResourceIds
|
||||
public string? screenshot { get; init; }
|
||||
public DateTime updatedAt { get; init; }
|
||||
public DateTime? viewedAt { get; init; }
|
||||
|
||||
@@ -19,6 +19,9 @@ public sealed class ServerInfo
|
||||
[Obsolete("Don't use")]
|
||||
public bool frontend2 { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// The URL that should be used to talk with the server
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This field is not returned from the GQL API,
|
||||
/// it should be populated after construction.
|
||||
|
||||
@@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging;
|
||||
using Speckle.Sdk.Logging;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Serialisation;
|
||||
using Speckle.Sdk.Serialisation.V2.Receive;
|
||||
using Speckle.Sdk.Transports;
|
||||
|
||||
namespace Speckle.Sdk.Api;
|
||||
@@ -15,6 +16,7 @@ public partial class Operations
|
||||
/// <exception cref="ArgumentException">No transports were specified</exception>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="objectId"/> was <see langword="null"/></exception>
|
||||
/// <exception cref="SpeckleException">Serialization or Send operation was unsuccessful</exception>
|
||||
/// <exception cref="HttpRequestException">HTTP layer errors</exception>
|
||||
/// <exception cref="OperationCanceledException">The <paramref name="cancellationToken"/> requested cancellation</exception>
|
||||
public async Task<Base> Receive2(
|
||||
Uri url,
|
||||
@@ -22,7 +24,8 @@ public partial class Operations
|
||||
string objectId,
|
||||
string? authorizationToken,
|
||||
IProgress<ProgressArgs>? onProgressAction,
|
||||
CancellationToken cancellationToken
|
||||
CancellationToken cancellationToken,
|
||||
DeserializeProcessOptions? options = null
|
||||
)
|
||||
{
|
||||
using var receiveActivity = activityFactory.Start("Operations.Receive");
|
||||
@@ -36,7 +39,8 @@ public partial class Operations
|
||||
streamId,
|
||||
authorizationToken,
|
||||
onProgressAction,
|
||||
cancellationToken
|
||||
cancellationToken,
|
||||
options
|
||||
);
|
||||
try
|
||||
{
|
||||
@@ -44,6 +48,11 @@ public partial class Operations
|
||||
receiveActivity?.SetStatus(SdkActivityStatusCode.Ok);
|
||||
return result;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
//this is handled by the caller
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
receiveActivity?.SetStatus(SdkActivityStatusCode.Error);
|
||||
|
||||
@@ -25,7 +25,8 @@ public partial class Operations
|
||||
string? authorizationToken,
|
||||
Base value,
|
||||
IProgress<ProgressArgs>? onProgressAction,
|
||||
CancellationToken cancellationToken
|
||||
CancellationToken cancellationToken,
|
||||
SerializeProcessOptions? options = null
|
||||
)
|
||||
{
|
||||
using var receiveActivity = activityFactory.Start("Operations.Send");
|
||||
@@ -38,7 +39,8 @@ public partial class Operations
|
||||
streamId,
|
||||
authorizationToken,
|
||||
onProgressAction,
|
||||
cancellationToken
|
||||
cancellationToken,
|
||||
options
|
||||
);
|
||||
try
|
||||
{
|
||||
|
||||
@@ -59,15 +59,20 @@ public class Account : IEquatable<Account>
|
||||
|
||||
#region public methods
|
||||
|
||||
|
||||
/// <remarks>The logic is aligned with <c>distinct_id</c> mixpanel property</remarks>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <see name="userInfo.email"/> was <see langword="null"/></exception>
|
||||
public string GetHashedEmail()
|
||||
{
|
||||
string email = userInfo?.email ?? "unknown";
|
||||
string email = userInfo.email.NotNull();
|
||||
return "@" + Md5.GetString(email).ToUpperInvariant();
|
||||
}
|
||||
|
||||
/// <remarks>The logic is aligned with <c>server</c> mixpanel property</remarks>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <see name="serverInfo.url"/> was <see langword="null"/></exception>
|
||||
public string GetHashedServer()
|
||||
{
|
||||
string url = serverInfo?.url ?? AccountManager.DEFAULT_SERVER_URL;
|
||||
string url = serverInfo.url.NotNull();
|
||||
return Md5.GetString(CleanURL(url)).ToUpperInvariant();
|
||||
}
|
||||
|
||||
@@ -97,6 +102,8 @@ public class Account : IEquatable<Account>
|
||||
|
||||
#endregion
|
||||
|
||||
internal const string LOCAL_IDENTIFIER_DEPRECATION_MESSAGE = "Local identifiers no longer nesseary";
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the local identifier for the current user.
|
||||
/// </summary>
|
||||
@@ -121,5 +128,6 @@ public class Account : IEquatable<Account>
|
||||
/// https://speckle.xyz?id=123
|
||||
/// </code>
|
||||
/// </example>
|
||||
[Obsolete(LOCAL_IDENTIFIER_DEPRECATION_MESSAGE)]
|
||||
internal Uri GetLocalIdentifier() => new($"{serverInfo.url}?id={userInfo.id}");
|
||||
}
|
||||
|
||||
@@ -419,6 +419,7 @@ public sealed class AccountManager(
|
||||
/// <remarks>
|
||||
/// <inheritdoc cref="Account.GetLocalIdentifier"/>
|
||||
/// </remarks>
|
||||
[Obsolete(Account.LOCAL_IDENTIFIER_DEPRECATION_MESSAGE)]
|
||||
public Uri? GetLocalIdentifierForAccount(Account account)
|
||||
{
|
||||
var identifier = account.GetLocalIdentifier();
|
||||
@@ -440,6 +441,7 @@ public sealed class AccountManager(
|
||||
/// </summary>
|
||||
/// <param name="localIdentifier">The local identifier of the account.</param>
|
||||
/// <returns>The account that matches the local identifier, or null if no match is found.</returns>
|
||||
[Obsolete(Account.LOCAL_IDENTIFIER_DEPRECATION_MESSAGE)]
|
||||
public Account? GetAccountForLocalIdentifier(Uri localIdentifier)
|
||||
{
|
||||
var searchResult = GetAccounts()
|
||||
|
||||
@@ -9,6 +9,8 @@ public static class SpecklePathProvider
|
||||
{
|
||||
private const string APPLICATION_NAME = "Speckle";
|
||||
|
||||
private const string LOG_FOLDER_NAME = "Logs";
|
||||
|
||||
private const string BLOB_FOLDER_NAME = "Blobs";
|
||||
|
||||
private const string ACCOUNTS_FOLDER_NAME = "Accounts";
|
||||
@@ -43,7 +45,7 @@ public static class SpecklePathProvider
|
||||
/// <see cref="Environment.SpecialFolder.ApplicationData"/> path usually maps to
|
||||
/// <ul>
|
||||
/// <li>win: <c>%appdata%/</c></li>
|
||||
/// <li>MacOS: <c>~/.config/</c></li>
|
||||
/// <li>MacOS: <c>~/Library/Application Support</c></li>
|
||||
/// <li>Linux: <c>~/.config/</c></li>
|
||||
/// </ul>
|
||||
/// </remarks>
|
||||
@@ -57,29 +59,18 @@ public static class SpecklePathProvider
|
||||
return pathOverride;
|
||||
}
|
||||
|
||||
// on desktop linux and macos we use the appdata.
|
||||
// but we might not have write access to the disk
|
||||
// so the catch falls back to the user profile
|
||||
try
|
||||
{
|
||||
return Environment.GetFolderPath(
|
||||
Environment.SpecialFolder.ApplicationData,
|
||||
// if the folder doesn't exist, we get back an empty string on OSX,
|
||||
// which in turn, breaks other stuff down the line.
|
||||
// passing in the Create option ensures that this directory exists,
|
||||
// which is not a given on all OS-es.
|
||||
// It's not a given that the folder is already there on all OS-es, so we'll create it
|
||||
Environment.SpecialFolderOption.Create
|
||||
);
|
||||
}
|
||||
catch (SystemException ex) when (ex is PlatformNotSupportedException or ArgumentException)
|
||||
catch (PlatformNotSupportedException)
|
||||
{
|
||||
//Adding this log just so we confidently know which Exception type to catch here.
|
||||
// TODO: Must re-add log call when (and if) this get's made as a service
|
||||
//SpeckleLog.Logger.Warning(ex, "Falling back to user profile path");
|
||||
|
||||
// on server linux, there might not be a user setup, things can run under root
|
||||
// in that case, the appdata variable is most probably not set up
|
||||
// we fall back to the value of the home folder
|
||||
// We might not have write access to the disk to create the folder,
|
||||
// so we'll fall back to the user profile
|
||||
return Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
}
|
||||
}
|
||||
@@ -96,4 +87,7 @@ public static class SpecklePathProvider
|
||||
Directory.CreateDirectory(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
public static string LogFolderPath(string applicationAndVersion) =>
|
||||
EnsureFolderExists(UserSpeckleFolderPath, LOG_FOLDER_NAME, applicationAndVersion);
|
||||
}
|
||||
|
||||
@@ -169,8 +169,7 @@ public class Base : DynamicBase, ISpeckleObject
|
||||
|
||||
return count;
|
||||
}
|
||||
case IEnumerable e
|
||||
and not string:
|
||||
case IEnumerable e and not string:
|
||||
{
|
||||
foreach (var arrValue in e)
|
||||
{
|
||||
|
||||
@@ -109,9 +109,9 @@ public abstract class GraphTraversal<T>
|
||||
break;
|
||||
case IList list:
|
||||
{
|
||||
foreach (object? obj in list)
|
||||
for (int i = list.Count - 1; i >= 0; i--)
|
||||
{
|
||||
TraverseMemberToStack(stack, obj, memberName, parent);
|
||||
TraverseMemberToStack(stack, list[i], memberName, parent);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -9,6 +9,7 @@ public class MemoryServerObjectManager(ConcurrentDictionary<string, string> obje
|
||||
{
|
||||
public virtual async IAsyncEnumerable<(string, string)> DownloadObjects(
|
||||
IReadOnlyCollection<string> objectIds,
|
||||
string? attributeMask,
|
||||
IProgress<ProgressArgs>? progress,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ using Speckle.Sdk.Transports;
|
||||
namespace Speckle.Sdk.Serialisation.V2.Receive;
|
||||
|
||||
public record DeserializeProcessOptions(
|
||||
bool SkipCache = false,
|
||||
bool SkipCache = false, //TODO: This appears to be bugged when set to `true`, `LoadId` depends on sqlite
|
||||
bool ThrowOnMissingReferences = true,
|
||||
bool SkipInvalidConverts = false,
|
||||
int? MaxParallelism = null,
|
||||
|
||||
@@ -19,9 +19,7 @@ public sealed class ObjectLoader(
|
||||
IProgress<ProgressArgs>? progress,
|
||||
ILogger<ObjectLoader> logger,
|
||||
CancellationToken cancellationToken
|
||||
#pragma warning disable CS9107 // Parameter is captured into the state of the enclosing type and its value is also passed to the base constructor. The value might be captured by the base class as well.
|
||||
) : ChannelLoader<BaseItem>(cancellationToken), IObjectLoader
|
||||
#pragma warning restore CS9107 // Parameter is captured into the state of the enclosing type and its value is also passed to the base constructor. The value might be captured by the base class as well.
|
||||
{
|
||||
private int? _allChildrenCount;
|
||||
private long _checkCache;
|
||||
@@ -29,6 +27,7 @@ public sealed class ObjectLoader(
|
||||
private long _downloaded;
|
||||
private long _totalToDownload;
|
||||
private DeserializeProcessOptions _options = new();
|
||||
private readonly CancellationToken _cancellationToken = cancellationToken;
|
||||
|
||||
[AutoInterfaceIgnore]
|
||||
public void Dispose() => sqLiteJsonCacheManager.Dispose();
|
||||
@@ -46,7 +45,7 @@ public sealed class ObjectLoader(
|
||||
{
|
||||
//assume everything exists as the root is there.
|
||||
var allChildren = ClosureParser
|
||||
.GetClosuresSorted(rootJson, cancellationToken)
|
||||
.GetClosuresSorted(rootJson, _cancellationToken)
|
||||
.Select(x => new Id(x.Item1))
|
||||
.ToList();
|
||||
//this probably yields away from the Main thread to let host apps update progress
|
||||
@@ -59,11 +58,11 @@ public sealed class ObjectLoader(
|
||||
if (!options.SkipServer)
|
||||
{
|
||||
rootJson = await serverObjectManager
|
||||
.DownloadSingleObject(rootId, progress, cancellationToken)
|
||||
.DownloadSingleObject(rootId, progress, _cancellationToken)
|
||||
.NotNull()
|
||||
.ConfigureAwait(false);
|
||||
IReadOnlyCollection<Id> allChildrenIds = ClosureParser
|
||||
.GetClosures(rootJson, cancellationToken)
|
||||
.GetClosures(rootJson, _cancellationToken)
|
||||
.OrderByDescending(x => x.Item2)
|
||||
.Select(x => new Id(x.Item1))
|
||||
.Where(x => !x.Value.StartsWith("blob", StringComparison.Ordinal))
|
||||
@@ -111,12 +110,13 @@ public sealed class ObjectLoader(
|
||||
await foreach (
|
||||
var (id, json) in serverObjectManager.DownloadObjects(
|
||||
ids.Select(x => x.NotNull()).ToList(),
|
||||
null, //TODO: Implement attribute masking in a safe way that will not poison SQLite DB.
|
||||
progress,
|
||||
cancellationToken
|
||||
_cancellationToken
|
||||
)
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
_cancellationToken.ThrowIfCancellationRequested();
|
||||
Interlocked.Increment(ref _downloaded);
|
||||
progress?.Report(new(ProgressEvent.DownloadObjects, _downloaded, _totalToDownload));
|
||||
toCache.Add(new(new(id), new(json), true, null));
|
||||
@@ -138,7 +138,7 @@ public sealed class ObjectLoader(
|
||||
{
|
||||
if (!_options.SkipCache)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
_cancellationToken.ThrowIfCancellationRequested();
|
||||
sqLiteJsonCacheManager.SaveObjects(batch.Select(x => (x.Id.Value, x.Json.Value)));
|
||||
Interlocked.Exchange(ref _cached, _cached + batch.Count);
|
||||
progress?.Report(new(ProgressEvent.CachedToLocal, _cached, _allChildrenCount));
|
||||
@@ -168,7 +168,7 @@ public sealed class ObjectLoader(
|
||||
private void ThrowIfFailed()
|
||||
{
|
||||
//always check for cancellation first
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
_cancellationToken.ThrowIfCancellationRequested();
|
||||
if (Exception is not null)
|
||||
{
|
||||
throw new SpeckleException($"Error while loading: {Exception.Message}", Exception);
|
||||
|
||||
@@ -21,12 +21,7 @@ public class BaseSerializer(
|
||||
public IReadOnlyDictionary<Id, ObjectReference> ObjectReferences => _objectReferences;
|
||||
|
||||
//leave this sync
|
||||
public IEnumerable<BaseItem> Serialise(
|
||||
Base obj,
|
||||
IReadOnlyDictionary<Id, NodeInfo> childInfo,
|
||||
bool skipCacheRead,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
public IEnumerable<BaseItem> Serialise(Base obj, bool skipCacheRead, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!skipCacheRead && obj.id != null)
|
||||
{
|
||||
@@ -38,7 +33,7 @@ public class BaseSerializer(
|
||||
}
|
||||
}
|
||||
|
||||
using var serializer2 = objectSerializerFactory.Create(childInfo, cancellationToken);
|
||||
using var serializer2 = objectSerializerFactory.Create(cancellationToken);
|
||||
var items = _pool.Get();
|
||||
try
|
||||
{
|
||||
|
||||
@@ -20,10 +20,10 @@ public sealed class ObjectSaver(
|
||||
ISqLiteJsonCacheManager sqLiteJsonCacheManager,
|
||||
IServerObjectManager serverObjectManager,
|
||||
ILogger<ObjectSaver> logger,
|
||||
CancellationToken cancellationToken,
|
||||
SerializeProcessOptions options,
|
||||
CancellationToken cancellationToken
|
||||
#pragma warning disable CS9107
|
||||
#pragma warning disable CA2254
|
||||
SerializeProcessOptions? options = null
|
||||
) : ChannelSaver<BaseItem>, IObjectSaver
|
||||
#pragma warning restore CA2254
|
||||
#pragma warning restore CS9107
|
||||
@@ -100,9 +100,9 @@ public sealed class ObjectSaver(
|
||||
{
|
||||
if (!_options.SkipCacheWrite && batch.Count != 0)
|
||||
{
|
||||
sqLiteJsonCacheManager.SaveObjects(batch.Select(x => (x.Id.Value, x.Json.Value)));
|
||||
Interlocked.Add(ref _cached, batch.Count);
|
||||
progress?.Report(new(ProgressEvent.CachedToLocal, _cached, _objectsSerialized));
|
||||
sqLiteJsonCacheManager.SaveObjects(batch.Select(x => (x.Id.Value, x.Json.Value)));
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
|
||||
@@ -26,8 +26,6 @@ public sealed class ObjectSerializer : IObjectSerializer
|
||||
{
|
||||
private HashSet<object> _parentObjects = new();
|
||||
|
||||
private readonly IReadOnlyDictionary<Id, NodeInfo> _childCache;
|
||||
|
||||
private readonly IBasePropertyGatherer _propertyGatherer;
|
||||
private readonly CancellationToken _cancellationToken;
|
||||
|
||||
@@ -52,7 +50,6 @@ public sealed class ObjectSerializer : IObjectSerializer
|
||||
/// <param name="cancellationToken"></param>
|
||||
public ObjectSerializer(
|
||||
IBasePropertyGatherer propertyGatherer,
|
||||
IReadOnlyDictionary<Id, NodeInfo> childCache,
|
||||
Pool<List<(Id, Json, Closures)>> chunksPool,
|
||||
Pool<List<DataChunk>> chunks2Pool,
|
||||
Pool<List<object?>> chunks3Pool,
|
||||
@@ -60,7 +57,6 @@ public sealed class ObjectSerializer : IObjectSerializer
|
||||
)
|
||||
{
|
||||
_propertyGatherer = propertyGatherer;
|
||||
_childCache = childCache;
|
||||
_chunksPool = chunksPool;
|
||||
_chunks2Pool = chunks2Pool;
|
||||
_chunks3Pool = chunks3Pool;
|
||||
@@ -299,28 +295,14 @@ public sealed class ObjectSerializer : IObjectSerializer
|
||||
|
||||
private (Id, Json)? SerializeDetachedBase(Base baseObj, Closures closures)
|
||||
{
|
||||
Closures childClosures;
|
||||
Id id;
|
||||
Json json;
|
||||
//avoid multiple serialization to get closures
|
||||
if (baseObj.id != null && _childCache.TryGetValue(new(baseObj.id), out var info))
|
||||
{
|
||||
id = new Id(baseObj.id);
|
||||
childClosures = info.GetClosures(_cancellationToken);
|
||||
json = info.Json;
|
||||
closures.IncrementClosures(childClosures);
|
||||
}
|
||||
else
|
||||
{
|
||||
childClosures = [];
|
||||
var sb = Pools.StringBuilders.Get();
|
||||
using var writer = new StringWriter(sb);
|
||||
using var jsonWriter = SpeckleObjectSerializerPool.Instance.GetJsonTextWriter(writer);
|
||||
id = SerializeBaseWithClosures(baseObj, jsonWriter, childClosures, true);
|
||||
closures.IncrementClosures(childClosures);
|
||||
json = new Json(writer.ToString());
|
||||
Pools.StringBuilders.Return(sb);
|
||||
}
|
||||
Closures childClosures = [];
|
||||
var sb = Pools.StringBuilders.Get();
|
||||
using var writer = new StringWriter(sb);
|
||||
using var jsonWriter = SpeckleObjectSerializerPool.Instance.GetJsonTextWriter(writer);
|
||||
var id = SerializeBaseWithClosures(baseObj, jsonWriter, childClosures, true);
|
||||
var json = new Json(writer.ToString());
|
||||
Pools.StringBuilders.Return(sb);
|
||||
closures.IncrementClosures(childClosures);
|
||||
var json2 = ReferenceGenerator.CreateReference(id);
|
||||
closures.MergeClosure(id);
|
||||
// add to obj refs to return
|
||||
|
||||
@@ -12,6 +12,6 @@ public class ObjectSerializerFactory(IBasePropertyGatherer propertyGatherer) : I
|
||||
private readonly Pool<List<DataChunk>> _chunk2Pool = Pools.CreateListPool<DataChunk>();
|
||||
private readonly Pool<List<object?>> _chunk3Pool = Pools.CreateListPool<object?>();
|
||||
|
||||
public IObjectSerializer Create(IReadOnlyDictionary<Id, NodeInfo> baseCache, CancellationToken cancellationToken) =>
|
||||
new ObjectSerializer(propertyGatherer, baseCache, _chunkPool, _chunk2Pool, _chunk3Pool, cancellationToken);
|
||||
public IObjectSerializer Create(CancellationToken cancellationToken) =>
|
||||
new ObjectSerializer(propertyGatherer, _chunkPool, _chunk2Pool, _chunk3Pool, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Speckle.InterfaceGenerator;
|
||||
@@ -37,8 +36,8 @@ public sealed class SerializeProcess(
|
||||
IBaseChildFinder baseChildFinder,
|
||||
IBaseSerializer baseSerializer,
|
||||
ILoggerFactory loggerFactory,
|
||||
CancellationToken cancellationToken,
|
||||
SerializeProcessOptions? options = null
|
||||
SerializeProcessOptions options,
|
||||
CancellationToken cancellationToken
|
||||
) : ISerializeProcess
|
||||
{
|
||||
private static readonly Dictionary<Id, NodeInfo> EMPTY_CLOSURES = new();
|
||||
@@ -64,13 +63,8 @@ public sealed class SerializeProcess(
|
||||
ThreadPriority.BelowNormal,
|
||||
Environment.ProcessorCount * 2
|
||||
);
|
||||
private readonly SerializeProcessOptions _options = options ?? new();
|
||||
|
||||
private readonly Pool<Dictionary<Id, NodeInfo>> _currentClosurePool = Pools.CreateDictionaryPool<Id, NodeInfo>();
|
||||
private readonly Pool<ConcurrentDictionary<Id, NodeInfo>> _childClosurePool = Pools.CreateConcurrentDictionaryPool<
|
||||
Id,
|
||||
NodeInfo
|
||||
>();
|
||||
|
||||
private readonly Pool<List<Task<Dictionary<Id, NodeInfo>>>> _taskResultPool = Pools.CreateListPool<
|
||||
Task<Dictionary<Id, NodeInfo>>
|
||||
@@ -113,13 +107,13 @@ public sealed class SerializeProcess(
|
||||
try
|
||||
{
|
||||
var channelTask = objectSaver.Start(
|
||||
options?.MaxParallelism,
|
||||
options?.MaxHttpSendBatchSize,
|
||||
options?.MaxCacheBatchSize,
|
||||
options.MaxParallelism,
|
||||
options.MaxHttpSendBatchSize,
|
||||
options.MaxCacheBatchSize,
|
||||
_processSource.Token
|
||||
);
|
||||
var findTotalObjectsTask = Task.CompletedTask;
|
||||
if (!_options.SkipFindTotalObjects)
|
||||
if (!options.SkipFindTotalObjects)
|
||||
{
|
||||
ThrowIfFailed();
|
||||
findTotalObjectsTask = Task.Factory.StartNew(
|
||||
@@ -225,7 +219,6 @@ public sealed class SerializeProcess(
|
||||
return EMPTY_CLOSURES;
|
||||
}
|
||||
|
||||
var childClosures = _childClosurePool.Get();
|
||||
foreach (var childClosure in taskClosures)
|
||||
{
|
||||
if (IsCancelled())
|
||||
@@ -234,7 +227,6 @@ public sealed class SerializeProcess(
|
||||
}
|
||||
foreach (var kvp in childClosure)
|
||||
{
|
||||
childClosures[kvp.Key] = kvp.Value;
|
||||
if (IsCancelled())
|
||||
{
|
||||
return EMPTY_CLOSURES;
|
||||
@@ -249,7 +241,7 @@ public sealed class SerializeProcess(
|
||||
return EMPTY_CLOSURES;
|
||||
}
|
||||
|
||||
var items = baseSerializer.Serialise(obj, childClosures, _options.SkipCacheRead, _processSource.Token);
|
||||
var items = baseSerializer.Serialise(obj, options.SkipCacheRead, _processSource.Token);
|
||||
|
||||
if (IsCancelled())
|
||||
{
|
||||
@@ -257,32 +249,26 @@ public sealed class SerializeProcess(
|
||||
}
|
||||
|
||||
var currentClosures = _currentClosurePool.Get();
|
||||
try
|
||||
|
||||
Interlocked.Increment(ref _objectCount);
|
||||
progress?.Report(new(ProgressEvent.FromCacheOrSerialized, _objectCount, Math.Max(_objectCount, _objectsFound)));
|
||||
foreach (var item in items)
|
||||
{
|
||||
Interlocked.Increment(ref _objectCount);
|
||||
progress?.Report(new(ProgressEvent.FromCacheOrSerialized, _objectCount, Math.Max(_objectCount, _objectsFound)));
|
||||
foreach (var item in items)
|
||||
if (IsCancelled())
|
||||
{
|
||||
if (IsCancelled())
|
||||
{
|
||||
return EMPTY_CLOSURES;
|
||||
}
|
||||
|
||||
if (item.NeedsStorage)
|
||||
{
|
||||
Interlocked.Increment(ref _objectsSerialized);
|
||||
await objectSaver.SaveAsync(item).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!currentClosures.ContainsKey(item.Id))
|
||||
{
|
||||
currentClosures.Add(item.Id, new NodeInfo(item.Json, item.Closures));
|
||||
}
|
||||
return EMPTY_CLOSURES;
|
||||
}
|
||||
|
||||
if (item.NeedsStorage)
|
||||
{
|
||||
Interlocked.Increment(ref _objectsSerialized);
|
||||
await objectSaver.SaveAsync(item).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!currentClosures.ContainsKey(item.Id))
|
||||
{
|
||||
currentClosures.Add(item.Id, new NodeInfo(item.Json, item.Closures));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_childClosurePool.Return(childClosures);
|
||||
}
|
||||
|
||||
return currentClosures;
|
||||
|
||||
@@ -44,13 +44,14 @@ public class SerializeProcessFactory(
|
||||
sqLiteJsonCacheManager,
|
||||
serverObjectManager,
|
||||
loggerFactory.CreateLogger<ObjectSaver>(),
|
||||
options ?? new SerializeProcessOptions(),
|
||||
cancellationToken
|
||||
),
|
||||
baseChildFinder,
|
||||
new BaseSerializer(sqLiteJsonCacheManager, objectSerializerFactory),
|
||||
loggerFactory,
|
||||
cancellationToken,
|
||||
options
|
||||
options ?? new SerializeProcessOptions(),
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
public ISerializeProcess CreateSerializeProcess(
|
||||
|
||||
@@ -51,6 +51,7 @@ public class ServerObjectManager : IServerObjectManager
|
||||
|
||||
public async IAsyncEnumerable<(string, string)> DownloadObjects(
|
||||
IReadOnlyCollection<string> objectIds,
|
||||
string? attributeMask,
|
||||
IProgress<ProgressArgs>? progress,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken
|
||||
)
|
||||
@@ -59,10 +60,14 @@ public class ServerObjectManager : IServerObjectManager
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
using var childrenHttpMessage = new HttpRequestMessage();
|
||||
childrenHttpMessage.RequestUri = new Uri($"/api/getobjects/{_streamId}", UriKind.Relative);
|
||||
childrenHttpMessage.RequestUri = new Uri($"/api/v2/projects/{_streamId}/object-stream/", UriKind.Relative);
|
||||
childrenHttpMessage.Method = HttpMethod.Post;
|
||||
|
||||
Dictionary<string, string> postParameters = new() { { "objects", JsonConvert.SerializeObject(objectIds) } };
|
||||
Dictionary<string, object> postParameters = new() { { "objectIds", objectIds } };
|
||||
if (!string.IsNullOrWhiteSpace(attributeMask))
|
||||
{
|
||||
postParameters.Add("attributeMask", attributeMask.NotNull());
|
||||
}
|
||||
string serializedPayload = JsonConvert.SerializeObject(postParameters);
|
||||
childrenHttpMessage.Content = new StringContent(serializedPayload, Encoding.UTF8, "application/json");
|
||||
childrenHttpMessage.Headers.Add("Accept", "text/plain");
|
||||
|
||||
@@ -20,7 +20,7 @@ internal sealed class ProgressStream(
|
||||
{
|
||||
int n = _stream.Read(buffer, offset, count);
|
||||
_position += n;
|
||||
progress?.Report(new(ProgressEvent.DownloadBytes, _position, streamLength));
|
||||
progress?.Report(new(ProgressEvent.DownloadBytes, n, streamLength));
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ internal sealed class ProgressStream(
|
||||
{
|
||||
_stream.Write(buffer, offset, count);
|
||||
_position += count;
|
||||
progress?.Report(new(ProgressEvent.UploadBytes, _position, streamLength));
|
||||
progress?.Report(new(ProgressEvent.UploadBytes, count, streamLength));
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
@@ -18,10 +18,7 @@ public sealed class ObjectsSerializationTest
|
||||
|
||||
private static IReadOnlyList<(Id, Json, Dictionary<Id, int>)> Serialize(Base data)
|
||||
{
|
||||
using var serializer = new ObjectSerializerFactory(new BasePropertyGatherer()).Create(
|
||||
new Dictionary<Id, NodeInfo>(),
|
||||
default
|
||||
);
|
||||
using var serializer = new ObjectSerializerFactory(new BasePropertyGatherer()).Create(default);
|
||||
return serializer.Serialize(data).ToList();
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ public class DataObjectTests
|
||||
new DummyServerObjectManager(),
|
||||
null,
|
||||
default,
|
||||
new SerializeProcessOptions(true, true, false, true)
|
||||
new SerializeProcessOptions(false, false, true, true)
|
||||
);
|
||||
await serializeProcess.Serialize(x);
|
||||
await VerifyJson(json.Single().Value.Value).UseParameters(type);
|
||||
|
||||
@@ -41,7 +41,7 @@ public class DetachedTests
|
||||
objects,
|
||||
null,
|
||||
default,
|
||||
new SerializeProcessOptions(false, false, true, true)
|
||||
new SerializeProcessOptions(true, true, false, true)
|
||||
);
|
||||
await serializeProcess.Serialize(@base);
|
||||
|
||||
@@ -123,7 +123,7 @@ public class DetachedTests
|
||||
objects,
|
||||
null,
|
||||
default,
|
||||
new SerializeProcessOptions(false, false, true, true) { MaxParallelism = 1, MaxHttpSendBatchSize = 1 }
|
||||
new SerializeProcessOptions(true, true, false, true) { MaxParallelism = 1, MaxHttpSendBatchSize = 1 }
|
||||
);
|
||||
var results = await serializeProcess.Serialize(@base);
|
||||
|
||||
@@ -150,7 +150,7 @@ public class DetachedTests
|
||||
objects,
|
||||
null,
|
||||
default,
|
||||
new SerializeProcessOptions(false, false, true, true) { MaxParallelism = 1, MaxHttpSendBatchSize = 1 }
|
||||
new SerializeProcessOptions(true, true, false, true) { MaxParallelism = 1, MaxHttpSendBatchSize = 1 }
|
||||
);
|
||||
var results = await serializeProcess.Serialize(@base);
|
||||
|
||||
@@ -172,7 +172,7 @@ public class DetachedTests
|
||||
objects,
|
||||
null,
|
||||
default,
|
||||
new SerializeProcessOptions(false, false, true, true) { MaxParallelism = 1, MaxHttpSendBatchSize = 1 }
|
||||
new SerializeProcessOptions(true, true, false, true) { MaxParallelism = 1, MaxHttpSendBatchSize = 1 }
|
||||
);
|
||||
var results = await serializeProcess.Serialize(@base);
|
||||
|
||||
@@ -239,7 +239,7 @@ public class DetachedTests
|
||||
objects,
|
||||
null,
|
||||
default,
|
||||
new SerializeProcessOptions(false, false, true, true)
|
||||
new SerializeProcessOptions(true, true, false, true)
|
||||
);
|
||||
|
||||
var results = await serializeProcess.Serialize(@base);
|
||||
@@ -272,7 +272,7 @@ public class DetachedTests
|
||||
objects,
|
||||
null,
|
||||
default,
|
||||
new SerializeProcessOptions(false, false, true, true)
|
||||
new SerializeProcessOptions(true, true, false, true)
|
||||
);
|
||||
var results = await serializeProcess.Serialize(@base);
|
||||
await VerifyJsonDictionary(objects);
|
||||
@@ -338,6 +338,7 @@ public class DummyServerObjectManager : IServerObjectManager
|
||||
{
|
||||
public IAsyncEnumerable<(string, string)> DownloadObjects(
|
||||
IReadOnlyCollection<string> objectIds,
|
||||
string? attributeMask,
|
||||
IProgress<ProgressArgs>? progress,
|
||||
CancellationToken cancellationToken
|
||||
) => throw new NotImplementedException();
|
||||
|
||||
@@ -113,14 +113,14 @@ public class ExceptionTests
|
||||
new ExceptionServerObjectManager(),
|
||||
null,
|
||||
new NullLogger<ObjectLoader>(),
|
||||
default
|
||||
CancellationToken.None
|
||||
);
|
||||
await using var process = new DeserializeProcess(
|
||||
o,
|
||||
null,
|
||||
new BaseDeserializer(new ObjectDeserializerFactory()),
|
||||
new NullLoggerFactory(),
|
||||
default,
|
||||
CancellationToken.None,
|
||||
new(SkipCache: true, MaxParallelism: 1, SkipServer: true)
|
||||
);
|
||||
|
||||
@@ -144,7 +144,7 @@ public class ExceptionTests
|
||||
null,
|
||||
new BaseDeserializer(new ObjectDeserializerFactory()),
|
||||
new NullLoggerFactory(),
|
||||
default,
|
||||
CancellationToken.None,
|
||||
new(true, MaxParallelism: 1)
|
||||
);
|
||||
|
||||
@@ -169,7 +169,7 @@ public class ExceptionTests
|
||||
null,
|
||||
new BaseDeserializer(new ObjectDeserializerFactory()),
|
||||
new NullLoggerFactory(),
|
||||
default,
|
||||
CancellationToken.None,
|
||||
new(MaxParallelism: 1)
|
||||
);
|
||||
|
||||
@@ -194,16 +194,14 @@ public class ExceptionTests
|
||||
[SpeckleType("Objects.Geometry.BadBase")]
|
||||
public class BadBase : Base
|
||||
{
|
||||
#pragma warning disable CA1065
|
||||
public string BadProp => throw new NotImplementedException();
|
||||
#pragma warning restore CA1065
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Test_SpeckleSerializerException()
|
||||
{
|
||||
var factory = new ObjectSerializerFactory(new BasePropertyGatherer());
|
||||
var serializer = factory.Create(new Dictionary<Id, NodeInfo>(), default);
|
||||
var serializer = factory.Create(default);
|
||||
Assert.Throws<SpeckleSerializeException>(() =>
|
||||
{
|
||||
var _ = serializer.Serialize(new BadBase()).ToList();
|
||||
|
||||
@@ -3,7 +3,6 @@ using Speckle.Objects.Primitive;
|
||||
using Speckle.Sdk.Host;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Models.Extensions;
|
||||
using Speckle.Sdk.Serialisation;
|
||||
using Speckle.Sdk.Serialisation.V2.Send;
|
||||
|
||||
namespace Speckle.Sdk.Serialization.Tests;
|
||||
@@ -20,10 +19,7 @@ public class ExternalIdTests
|
||||
public async Task ExternalIdTest_Detached()
|
||||
{
|
||||
var p = new Polyline() { units = "cm", value = [1, 2] };
|
||||
using var serializer = new ObjectSerializerFactory(new BasePropertyGatherer()).Create(
|
||||
new Dictionary<Id, NodeInfo>(),
|
||||
default
|
||||
);
|
||||
using var serializer = new ObjectSerializerFactory(new BasePropertyGatherer()).Create(default);
|
||||
var objects = serializer.Serialize(p).ToDictionary(x => x.Item1, x => x.Item2);
|
||||
|
||||
await VerifyJsonDictionary(objects);
|
||||
@@ -45,10 +41,7 @@ public class ExternalIdTests
|
||||
knots = [],
|
||||
weights = [],
|
||||
};
|
||||
using var serializer = new ObjectSerializerFactory(new BasePropertyGatherer()).Create(
|
||||
new Dictionary<Id, NodeInfo>(),
|
||||
default
|
||||
);
|
||||
using var serializer = new ObjectSerializerFactory(new BasePropertyGatherer()).Create(default);
|
||||
var objects = serializer.Serialize(curve).ToDictionary(x => x.Item1, x => x.Item2);
|
||||
|
||||
await VerifyJsonDictionary(objects);
|
||||
@@ -71,10 +64,7 @@ public class ExternalIdTests
|
||||
weights = [],
|
||||
};
|
||||
var polycurve = new Polycurve() { segments = [curve], units = "cm" };
|
||||
using var serializer = new ObjectSerializerFactory(new BasePropertyGatherer()).Create(
|
||||
new Dictionary<Id, NodeInfo>(),
|
||||
default
|
||||
);
|
||||
using var serializer = new ObjectSerializerFactory(new BasePropertyGatherer()).Create(default);
|
||||
var objects = serializer.Serialize(polycurve).ToDictionary(x => x.Item1, x => x.Item2);
|
||||
|
||||
await VerifyJsonDictionary(objects);
|
||||
@@ -99,10 +89,7 @@ public class ExternalIdTests
|
||||
var polycurve = new Polycurve() { segments = [curve], units = "cm" };
|
||||
var @base = new Base();
|
||||
@base.SetDetachedProp("profile", polycurve);
|
||||
using var serializer = new ObjectSerializerFactory(new BasePropertyGatherer()).Create(
|
||||
new Dictionary<Id, NodeInfo>(),
|
||||
default
|
||||
);
|
||||
using var serializer = new ObjectSerializerFactory(new BasePropertyGatherer()).Create(default);
|
||||
var objects = serializer.Serialize(@base).ToDictionary(x => x.Item1, x => x.Item2);
|
||||
await VerifyJsonDictionary(objects);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ public class ExceptionServerObjectManager : IServerObjectManager
|
||||
{
|
||||
public IAsyncEnumerable<(string, string)> DownloadObjects(
|
||||
IReadOnlyCollection<string> objectIds,
|
||||
string? attributeMask,
|
||||
IProgress<ProgressArgs>? progress,
|
||||
CancellationToken cancellationToken
|
||||
) => throw new NotImplementedException();
|
||||
|
||||
@@ -150,7 +150,7 @@ public class SerializationTests
|
||||
id.Should().Be(newId.Value);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Theory(Skip = "Takes too long")]
|
||||
[InlineData("RevitObject.json.gz", "3416d3fe01c9196115514c4a2f41617b", 7818)]
|
||||
public async Task Roundtrip_Test_Old(string fileName, string _, int count)
|
||||
{
|
||||
@@ -186,8 +186,6 @@ public class SerializationTests
|
||||
|
||||
[Theory]
|
||||
[InlineData(1)]
|
||||
[InlineData(2)]
|
||||
[InlineData(3)]
|
||||
[InlineData(4)]
|
||||
public async Task Roundtrip_Test_New(int concurrency)
|
||||
{
|
||||
@@ -205,7 +203,7 @@ public class SerializationTests
|
||||
new DummyReceiveServerObjectManager(closures),
|
||||
null,
|
||||
new NullLogger<ObjectLoader>(),
|
||||
default
|
||||
CancellationToken.None
|
||||
)
|
||||
)
|
||||
{
|
||||
|
||||
@@ -31,14 +31,17 @@ public class ServerObjectManagerTests : MoqTest
|
||||
var jObject = new JObject { { "id", id }, { "value", true } };
|
||||
var jObject2 = new JObject { { "id", id2 }, { "value", true } };
|
||||
var mockHttp = new MockHttpMessageHandler();
|
||||
Dictionary<string, string> postParameters = new()
|
||||
Dictionary<string, object> postParameters = new()
|
||||
{
|
||||
{ "objects", JsonConvert.SerializeObject(new List<string> { id, id2 }) },
|
||||
{
|
||||
"objectIds",
|
||||
new List<string> { id, id2 }
|
||||
},
|
||||
};
|
||||
|
||||
string serializedPayload = JsonConvert.SerializeObject(postParameters);
|
||||
mockHttp
|
||||
.When(HttpMethod.Post, $"http://localhost/api/getobjects/{streamId}")
|
||||
.When(HttpMethod.Post, $"http://localhost/api/v2/projects/{streamId}/object-stream/")
|
||||
.WithContent(serializedPayload)
|
||||
.Respond(
|
||||
"application/json",
|
||||
@@ -59,7 +62,7 @@ public class ServerObjectManagerTests : MoqTest
|
||||
token,
|
||||
new(timeout: TimeSpan.FromSeconds(timeout))
|
||||
);
|
||||
var results = serverObjectManager.DownloadObjects(new List<string> { id, id2 }, null, ct);
|
||||
var results = serverObjectManager.DownloadObjects(new List<string> { id, id2 }, null, null, ct);
|
||||
var objects = new JObject();
|
||||
await foreach (var (x, json) in results)
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ public class DummyReceiveServerObjectManager(IReadOnlyDictionary<string, string>
|
||||
{
|
||||
public async IAsyncEnumerable<(string, string)> DownloadObjects(
|
||||
IReadOnlyCollection<string> objectIds,
|
||||
string? attributeMask,
|
||||
IProgress<ProgressArgs>? progress,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken
|
||||
)
|
||||
|
||||
@@ -9,6 +9,7 @@ public class DummySendServerObjectManager(ConcurrentDictionary<string, string> s
|
||||
{
|
||||
public IAsyncEnumerable<(string, string)> DownloadObjects(
|
||||
IReadOnlyCollection<string> objectIds,
|
||||
string? attributeMask,
|
||||
IProgress<ProgressArgs>? progress,
|
||||
CancellationToken cancellationToken
|
||||
) => throw new NotImplementedException();
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"Data": {},
|
||||
"Message": "Response status code does not indicate success: 404 (Not Found).",
|
||||
"StatusCode": "NotFound",
|
||||
"Type": "HttpRequestException"
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"Data": {},
|
||||
"Message": "Response status code does not indicate success: 404 (Not Found).",
|
||||
"StatusCode": "NotFound",
|
||||
"Type": "HttpRequestException"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"ConvertedReferences": {},
|
||||
"RootId": "5313a8f61e1fa7abe9bf716ddfc767bd"
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"Data": {},
|
||||
"InnerException": {
|
||||
"$type": "SpeckleSerializeException",
|
||||
"Data": {},
|
||||
"InnerException": {
|
||||
"$type": "ArgumentException",
|
||||
"Data": {},
|
||||
"Message": "Unsupported value in serialization: System.Text.StringBuilder",
|
||||
"ParamName": "obj",
|
||||
"Type": "ArgumentException"
|
||||
},
|
||||
"Message": "Failed to extract (pre-serialize) properties from the Speckle.Sdk.Models.Base",
|
||||
"Type": "SpeckleSerializeException"
|
||||
},
|
||||
"Message": "Error while sending: Failed to extract (pre-serialize) properties from the Speckle.Sdk.Models.Base",
|
||||
"Type": "SpeckleException"
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Speckle.Sdk.Api;
|
||||
using Speckle.Sdk.Api.GraphQL.Enums;
|
||||
using Speckle.Sdk.Api.GraphQL.Models;
|
||||
using Speckle.Sdk.Host;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Sdk.Tests.Integration;
|
||||
|
||||
public sealed class SendReceiveTests : IAsyncLifetime
|
||||
{
|
||||
private Project _project;
|
||||
private IClient _client;
|
||||
private IOperations _operations;
|
||||
private const string NON_EXISTENT_OBJECT_ID = "0a480dfb7aa774f19a82bee9d6320abd";
|
||||
private const string NON_EXISTENT_PROJECT_ID = "8cdc651d13";
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
TypeLoader.Reset();
|
||||
TypeLoader.Initialize(typeof(Base).Assembly, Assembly.GetExecutingAssembly());
|
||||
var serviceProvider = TestServiceSetup.GetServiceProvider();
|
||||
_operations = serviceProvider.GetRequiredService<IOperations>();
|
||||
ClearCache();
|
||||
|
||||
_client = await Fixtures.SeedUserWithClient();
|
||||
_project = await _client.Project.Create(new("Blobber", "Flobber", ProjectVisibility.Private));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendAndReceive()
|
||||
{
|
||||
var myObject = Fixtures.GenerateNestedObject();
|
||||
string expectedId = myObject.GetId(true);
|
||||
|
||||
//SEND
|
||||
var fistSend = await _operations.Send2(
|
||||
_client.ServerUrl,
|
||||
_project.id,
|
||||
_client.Account.token,
|
||||
myObject,
|
||||
null,
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
Assert.Equal(expectedId, fistSend.RootId);
|
||||
await Verify(fistSend);
|
||||
|
||||
//RECEIVE
|
||||
var received = await _operations.Receive2(
|
||||
_client.ServerUrl,
|
||||
_project.id,
|
||||
fistSend.RootId,
|
||||
_client.Account.token,
|
||||
null,
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
Assert.Equal(expectedId, received.id);
|
||||
|
||||
//SEND AGAIN!
|
||||
var secondSend = await _operations.Send2(
|
||||
_client.ServerUrl,
|
||||
_project.id,
|
||||
_client.Account.token,
|
||||
received,
|
||||
null,
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
Assert.Equal(expectedId, secondSend.RootId);
|
||||
|
||||
//RECEIVE AGAIN, but using cache
|
||||
ClearCache();
|
||||
var secondReceive = await _operations.Receive2(
|
||||
_client.ServerUrl,
|
||||
_project.id,
|
||||
fistSend.RootId,
|
||||
_client.Account.token,
|
||||
null,
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
Assert.Equal(expectedId, secondReceive.id);
|
||||
}
|
||||
|
||||
private void ClearCache() { }
|
||||
|
||||
[Fact]
|
||||
public async Task ReceiveNonExistentObjectThrows()
|
||||
{
|
||||
var ex = await Assert.ThrowsAsync<HttpRequestException>(async () =>
|
||||
{
|
||||
_ = await _operations.Receive2(
|
||||
_client.ServerUrl,
|
||||
_project.id,
|
||||
NON_EXISTENT_OBJECT_ID,
|
||||
_client.Account.token,
|
||||
null,
|
||||
CancellationToken.None,
|
||||
new(true)
|
||||
);
|
||||
});
|
||||
await Verify(ex);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReceiveNonExistentProjectThrows()
|
||||
{
|
||||
var ex = await Assert.ThrowsAsync<HttpRequestException>(async () =>
|
||||
{
|
||||
_ = await _operations.Receive2(
|
||||
_client.ServerUrl,
|
||||
NON_EXISTENT_PROJECT_ID,
|
||||
NON_EXISTENT_OBJECT_ID,
|
||||
_client.Account.token,
|
||||
null,
|
||||
CancellationToken.None,
|
||||
new(true)
|
||||
);
|
||||
});
|
||||
await Verify(ex);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendInvalidData()
|
||||
{
|
||||
var myObject = Fixtures.GenerateNestedObject();
|
||||
myObject["invalidProp"] = new StringBuilder(); //Serializer does not support serializing this type
|
||||
|
||||
var ex = await Assert.ThrowsAsync<SpeckleException>(async () =>
|
||||
{
|
||||
_ = await _operations.Send2(
|
||||
_client.ServerUrl,
|
||||
_project.id,
|
||||
_client.Account.token,
|
||||
myObject,
|
||||
null,
|
||||
CancellationToken.None,
|
||||
new(SkipCacheRead: true, SkipCacheWrite: true)
|
||||
);
|
||||
});
|
||||
await Verify(ex);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReceiveNonAuthThrows()
|
||||
{
|
||||
using IClient unauthed = Fixtures.Unauthed;
|
||||
await Assert.ThrowsAsync<HttpRequestException>(async () =>
|
||||
{
|
||||
_ = await _operations.Receive2(
|
||||
unauthed.ServerUrl,
|
||||
_project.id,
|
||||
NON_EXISTENT_OBJECT_ID,
|
||||
unauthed.Account.token,
|
||||
null,
|
||||
CancellationToken.None,
|
||||
new(true)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReceiveCancellation()
|
||||
{
|
||||
using CancellationTokenSource ct = new();
|
||||
await ct.CancelAsync();
|
||||
await Assert.ThrowsAnyAsync<OperationCanceledException>(async () =>
|
||||
{
|
||||
_ = await _operations.Receive2(
|
||||
_client.ServerUrl,
|
||||
_project.id,
|
||||
NON_EXISTENT_OBJECT_ID,
|
||||
_client.Account.token,
|
||||
null,
|
||||
ct.Token,
|
||||
new(true)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
_client?.Dispose();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ public abstract class PrimitiveTestFixture
|
||||
}.Select(x => new object[] { x });
|
||||
|
||||
public static Half[] Float16TestCases { get; } =
|
||||
[default, Half.Epsilon, Half.MaxValue, Half.MinValue, Half.PositiveInfinity, Half.NegativeInfinity, Half.NaN];
|
||||
[default, Half.Epsilon, Half.MaxValue, Half.MinValue, Half.PositiveInfinity, Half.NegativeInfinity, Half.NaN];
|
||||
|
||||
public static float[] FloatIntegralTestCases { get; } = [0, 1, int.MaxValue, int.MinValue];
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ public class SerializeProcessRecordExceptionTests : MoqTest
|
||||
baseChildFinderMock.Object,
|
||||
baseSerializerMock.Object,
|
||||
loggerFactoryMock.Object,
|
||||
new(),
|
||||
cts.Token
|
||||
);
|
||||
var ex = new Exception("Test error");
|
||||
@@ -67,6 +68,7 @@ public class SerializeProcessRecordExceptionTests : MoqTest
|
||||
baseChildFinderMock.Object,
|
||||
baseSerializerMock.Object,
|
||||
loggerFactoryMock.Object,
|
||||
new(),
|
||||
cts.Token
|
||||
);
|
||||
var ex = new OperationCanceledException();
|
||||
@@ -98,6 +100,7 @@ public class SerializeProcessRecordExceptionTests : MoqTest
|
||||
baseChildFinderMock.Object,
|
||||
baseSerializerMock.Object,
|
||||
loggerFactoryMock.Object,
|
||||
new(),
|
||||
cts.Token
|
||||
);
|
||||
var ex = new AggregateException(new OperationCanceledException());
|
||||
|
||||
Reference in New Issue
Block a user