Compare commits

..

94 Commits

Author SHA1 Message Date
Jedd Morgan a4f0e0e4aa Added extra permission checks (#297)
.NET Build and Publish / build (push) Has been cancelled
2025-05-08 09:25:08 +00:00
Jedd Morgan 227729a0df Fixed Mistakes (#296)
.NET Build and Publish / build (push) Has been cancelled
2025-05-07 17:01:56 +00:00
Jedd Morgan 178085f3f8 dev -> main (#295)
.NET Build and Publish / build (push) Has been cancelled
* Sanitize test references

* add registration tests to SDK and ignore classes that can't be made

* add additional test

* chore(dev) Update to csharpier 1.0 (#284)

* Update to csharpier 1.0

* Fix check and nowarn

* format

* Update dependencies (#285)

* Added extra test for GetMembers (#290)

* Added extra test for GetMembers

* fixed tests

* verify

* Format

* Run the module init on unit tests and make it json

* update deps

* gitignore support was disabled in csharpier 1.0.1

---------

Co-authored-by: Adam Hathcock <adam@hathcock.uk>

* Add workspaces queries (#291)

* Add workspaces queries

* Format

* extra tweaks

* init speckle verify

* Add workspace creation state

* Add workspace creation test

* test exceptional cases

* GetActiveWorkspace tests

* fixed test

---------

Co-authored-by: Adam Hathcock <adamhathcock@users.noreply.github.com>
Co-authored-by: Adam Hathcock <adam@hathcock.uk>
2025-05-06 18:02:59 +03:00
Adam Hathcock 9794195e9c Merge pull request #293 from specklesystems/main-dev
.NET Build and Publish / build (push) Has been cancelled
Main to dev (NO SQUASH)
2025-05-06 14:00:54 +01:00
Adam Hathcock a61c442930 Merge branch 'dev' into main-dev 2025-05-06 13:50:16 +01:00
Jedd Morgan 68a407905d Add workspaces queries (#291)
* Add workspaces queries

* Format

* extra tweaks

* init speckle verify

* Add workspace creation state

* Add workspace creation test

* test exceptional cases

* GetActiveWorkspace tests

* fixed test
2025-05-01 21:23:30 +03:00
Adam Hathcock 0f2abaf532 Merge remote-tracking branch 'origin/dev' into main-dev 2025-04-30 16:24:06 +01:00
Adam Hathcock 07634b6f6a Merge remote-tracking branch 'origin/dev' into main-dev 2025-04-30 16:22:39 +01:00
Jedd Morgan e938725d35 Added extra test for GetMembers (#290)
* Added extra test for GetMembers

* fixed tests

* verify

* Format

* Run the module init on unit tests and make it json

* update deps

* gitignore support was disabled in csharpier 1.0.1

---------

Co-authored-by: Adam Hathcock <adam@hathcock.uk>
2025-04-30 16:13:41 +01:00
Adam Hathcock d3369e3ce5 Merge pull request #289 from specklesystems/main-dev
Main to dev (don't squash)
2025-04-30 16:00:46 +01:00
KatKatKateryna d75a61d775 Add text class (#271)
.NET Build and Publish / build (push) Has been cancelled
* draft class

* corrections

* edits

* max width

* remove import

* typo

* naming

* move directories

* delete from old location

* comment

* formatting

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-04-30 10:56:29 +02:00
Adam Hathcock 2ae4003afb Merge branch 'main' into main-dev 2025-04-28 10:52:58 +01:00
Adam Hathcock 24db4c4ae4 Merge pull request #288 from specklesystems/adam/no-drop-writes
.NET Build and Publish / build (push) Has been cancelled
fix (main) Don't drop items to write when sending fast
2025-04-28 10:18:48 +01:00
Adam Hathcock edf63d4a1b fix build issue 2025-04-28 09:39:46 +01:00
Adam Hathcock b5b0922e7f Revert to write async 2025-04-28 09:35:02 +01:00
Adam Hathcock ff390f772d just wait for space instead of another task and reduce size to 1000 2025-04-25 18:24:34 +01:00
Adam Hathcock d69f0bba2a fmt 2025-04-25 18:13:05 +01:00
Adam Hathcock 33c14fc14c Remove extras 2025-04-25 18:09:04 +01:00
Adam Hathcock 536e58aacc Don't drop items to write when sending fast 2025-04-25 17:45:01 +01:00
Adam Hathcock 88188aace6 Merge pull request #287 from specklesystems/main-dev
Main to dev (NO SQUASH)
2025-04-24 12:12:06 +01:00
Adam Hathcock ad44a7cdbc Merge branch 'dev' into main-dev 2025-04-24 12:01:32 +01:00
Adam Hathcock 38449dca9a Update dependencies (#285) 2025-04-24 11:59:26 +01:00
Adam Hathcock 764eb43838 Merge branch 'dev' into main-dev 2025-04-24 11:52:02 +01:00
Adam Hathcock a84e6d89ca chore(dev) Update to csharpier 1.0 (#284)
* Update to csharpier 1.0

* Fix check and nowarn

* format
2025-04-24 10:45:09 +00:00
Adam Hathcock 377829adae fix(main) exception test correction and token usage (#283)
.NET Build and Publish / build (push) Has been cancelled
* add parallelism on exception after count test

* use scoped token source correctly

* format

* Centralized token usage and made sqlite busy timeout be 5 seconds

* restore write parallelism to 4

* add to comment
2025-04-24 10:40:46 +00:00
Adam Hathcock a479440b66 Merge pull request #286 from specklesystems/adam/check-registration
feature (dev) check registration
2025-04-24 11:29:27 +01:00
Adam Hathcock cc9639b179 Merge pull request #282 from specklesystems/adam/error-fix
fix(main) Wrong error message being displayed in UI
2025-04-24 11:29:10 +01:00
Adam Hathcock d44b4fa52b add additional test 2025-04-24 08:33:42 +01:00
Adam Hathcock ea6ca8c555 add registration tests to SDK and ignore classes that can't be made 2025-04-23 15:57:03 +01:00
Adam Hathcock 113f0fd551 Sanitize test references 2025-04-23 15:42:57 +01:00
Adam Hathcock bcc4e25970 Merge pull request #280 from specklesystems/main-dev
Main->Dev
2025-04-23 13:04:37 +01:00
Adam Hathcock b733ce5f29 fix snapshot test message 2025-04-22 11:09:50 +01:00
Adam Hathcock 1c8b2b82d7 Wrong error message being displayed in UI 2025-04-22 10:13:59 +01:00
Jedd Morgan 11cd2dc1cb Update ProjectResourceExceptionalTests.cs (#279) 2025-04-11 12:27:04 +00:00
Adam Hathcock 3789898ea2 Merge pull request #278 from specklesystems/dev
.NET Build and Publish / build (push) Has been cancelled
SDK 3.2 release from dev to main
2025-04-09 12:35:06 +01:00
Adam Hathcock 0b3318f9e1 Merge pull request #277 from specklesystems/main-dev
Main to dev again
2025-04-09 11:56:35 +01:00
Adam Hathcock 7589ad5f05 Merge remote-tracking branch 'origin/dev' into main-dev 2025-04-09 11:45:06 +01:00
Adam Hathcock 6df32262bd fix: Update HostAppVersion.cs (#276)
Adding v2026 for next autodesk releases support.

Co-authored-by: Jonathon Broughton <760691+jsdbroughton@users.noreply.github.com>
2025-04-09 10:42:40 +00:00
Adam Hathcock 001ca1c287 Update some dependencies (#275) 2025-04-09 10:31:23 +00:00
Adam Hathcock 59e459559b Merge branch 'dev' into main-dev 2025-04-09 11:22:51 +01:00
Adam Hathcock d305fe59cb feat(sdk) clean up registration of sdk to not be connector specific (#274)
* First pass of ObjectSaver and better in-memory usage

* fix some tests

* add commit to match deserialize process

* correct more tests

* format

* make a deserialize factory

* fix tests? and format

* use distinct

* Fix mismerge

* Fix serialization issues with tests

* fix merges

* follow copilot suggestions

* remove disables

* change registration to take strings and TypeLoader isn't public

* remove unused transports

* more test fixes

* fmt

* add Application object back
2025-04-08 09:49:31 +00:00
Adam Hathcock f163b2822e (feat) add memory serialize and make relevant tests use it (#252)
* First pass of ObjectSaver and better in-memory usage

* fix some tests

* add commit to match deserialize process

* correct more tests

* format

* make a deserialize factory

* fix tests? and format

* use distinct

* Fix mismerge

* Fix serialization issues with tests

* fix merges

* follow copilot suggestions

* remove disables
2025-04-08 10:21:47 +01:00
Adam Hathcock 630bb38b8b fix: Update HostAppVersion.cs (#273)
Adding v2026 for next autodesk releases support.

Co-authored-by: Jonathon Broughton <760691+jsdbroughton@users.noreply.github.com>
2025-04-07 13:43:22 +01:00
Adam Hathcock bacff2da34 Merge branch 'dev' into main-dev 2025-04-07 12:46:07 +01:00
Adam Hathcock 129d5285ed Merge branch 'main' into main-dev 2025-04-07 12:45:09 +01:00
Adam Hathcock 5d07fe0ea0 refactor error handling to no propagate through channels. (#268)
* refactor error handling to no propagate through channels.  Use cancellation to shut down on exception

* don't try to shutdown scheduler when exception happens...handle tokens in a tree

* some cleanup

* ConfigureAwait(false) again

* Add test and clean up exception handling

* fmt

* fixed tests
2025-04-07 11:49:47 +01:00
Adam Hathcock 062e3c2838 Make packs require tests too (#254) 2025-04-07 10:10:41 +00:00
Adam Hathcock f4cc4bc77e Merge pull request #272 from specklesystems/adam/add-2026
.NET Build and Publish / build (push) Has been cancelled
fix: Update HostAppVersion.cs for 2026
2025-04-07 10:55:25 +01:00
Jonathon Broughton d395f03219 fix: Update HostAppVersion.cs
Adding v2026 for next autodesk releases support.
2025-04-07 10:10:18 +01:00
Adam Hathcock 56ed399d70 Removes log statement that is confusing people when viewing log files (#267) 2025-03-26 14:21:06 +00:00
Adam Hathcock 7e0766fc7f Merge pull request #262 from specklesystems/main-dev
Main to dev
2025-03-25 10:24:16 +00:00
Adam Hathcock 0df343ebe1 Merge remote-tracking branch 'origin/main' into main-dev 2025-03-25 09:02:02 +00:00
Adam Hathcock f6f1852664 Merge branch 'dev' into main-dev 2025-03-17 12:36:33 +00:00
Adam Hathcock 81a22bd4cc Merge pull request #256 from specklesystems/main-dev
Main to dev
2025-03-13 10:39:11 +00:00
Adam Hathcock f64da099ab Merge branch 'dev' into main-dev 2025-03-13 08:48:36 +00:00
Adam Hathcock f4ba200640 Merge branch 'main' into main-dev 2025-03-13 08:47:23 +00:00
KatKatKateryna 404600a839 Regions class (#241)
* region class

* comments

* adjusted constructor

* todos

* comment

* change displayValur to curves

* small refactor

* assign correct property

* generalize display value

* some minor xml changes

* transform meshes; bbox not required

* remove comment

* Update Region.cs

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
Co-authored-by: Adam Hathcock <adamhathcock@users.noreply.github.com>
2025-03-12 19:10:45 +08:00
Jedd Morgan 93d517eab7 test(objects): Verify tests for serialization (#227)
* Objects Verify Tests

* VerifyTests

* Verify as array with deterministic order

* lock

* Updated verified
2025-03-12 08:29:30 +00:00
Adam Hathcock 4110d90107 Merge pull request #245 from specklesystems/adam/more-code-cov2
Client tests and another SpeckleHttp one
2025-03-11 13:54:14 +00:00
Adam Hathcock 2d134bf7e1 make ExecuteWithResiliencePolicies internal 2025-03-11 13:24:39 +00:00
Adam Hathcock 686d0fd31c Merge remote-tracking branch 'origin/dev' into adam/more-code-cov2 2025-03-11 13:15:32 +00:00
Adam Hathcock b06878bbe1 Merge pull request #242 from specklesystems/remove-more-attributes
Remove more attributes
2025-03-11 12:09:56 +00:00
Adam Hathcock 64114167a8 Merge branch 'dev' into adam/more-code-cov2 2025-03-11 12:07:03 +00:00
Adam Hathcock 0c7cd75353 Merge branch 'dev' into remove-more-attributes 2025-03-11 11:58:47 +00:00
Adam Hathcock 3c5679ad2a Merge pull request #251 from specklesystems/main-dev
Main to dev
2025-03-11 11:58:12 +00:00
Adam Hathcock b043623021 Merge remote-tracking branch 'origin/dev' into main-dev 2025-03-10 09:36:28 +00:00
Adam Hathcock bd0997913a Add back the integration test 2025-03-10 09:31:56 +00:00
Jedd Morgan a79f0fd035 Deprecated public visibility (#249) 2025-03-08 17:50:52 +00:00
Adam Hathcock 7a29d27e46 Merge remote-tracking branch 'origin/dev' into adam/more-code-cov2 2025-03-07 11:14:32 +00:00
Adam Hathcock 82e3d37dd1 graphql test with client 2025-03-07 11:12:31 +00:00
Adam Hathcock 9695ec8c51 SpeckleHttp coverage (#244)
* Show code coverage for dev

* SpeckleHttp coverage and moved base stuff around

* add SpeckleHttpTests
2025-03-07 10:48:01 +00:00
Adam Hathcock 15fa319433 Show code coverage for dev (#243) 2025-03-07 10:26:13 +00:00
Adam Hathcock 793bbb9cd3 Complete speckle http 2025-03-07 09:29:05 +00:00
Adam Hathcock 3291010d43 add SpeckleHttpTests 2025-03-07 09:09:25 +00:00
Adam Hathcock 14732ce174 SpeckleHttp coverage and moved base stuff around 2025-03-07 09:03:07 +00:00
Adam Hathcock ba655988b0 Show code coverage for dev 2025-03-07 08:39:51 +00:00
Adam Hathcock 96822c4e66 Fix tests 2025-03-06 16:55:01 +00:00
Adam Hathcock 08356de1ad Merge remote-tracking branch 'origin/dev' into remove-more-attributes 2025-03-06 13:46:28 +00:00
Adam Hathcock 375f5071ae Improve test coverage by adding tests and removing unused code (#240)
* maybe all objects need to be false

* fmt

* remove extra code form hosts

* fix build

* add speckle serializer exception test

* add DownloadObjects and DownloadSingleObject test

* fmt

* add HasObjects test

* BaseItem tests

* adjust code and add test for UploadObjects

* Remove try/catch

* can't test compressed?  use lib to parse multipart

* remove unused verify snapshots
2025-03-06 13:20:11 +00:00
Adam Hathcock 5900a3c178 compiles 2025-03-06 12:00:57 +00:00
Adam Hathcock d7bf324029 Remove more unused attributes 2025-03-06 11:59:13 +00:00
Adam Hathcock c784fbf462 remove unused verify snapshots 2025-03-05 14:14:42 +00:00
Adam Hathcock 26f1802787 can't test compressed? use lib to parse multipart 2025-03-05 14:13:05 +00:00
Adam Hathcock a96e0f8c8e Remove try/catch 2025-03-05 12:38:34 +00:00
Adam Hathcock 9f36c9cfe5 adjust code and add test for UploadObjects 2025-03-05 12:18:19 +00:00
Adam Hathcock 4959f277e8 BaseItem tests 2025-03-05 11:10:21 +00:00
Adam Hathcock ea08b83f7a add HasObjects test 2025-03-05 09:54:31 +00:00
Adam Hathcock b24dc685fa fmt 2025-03-05 09:44:32 +00:00
Adam Hathcock 7cad14fe25 add DownloadObjects and DownloadSingleObject test 2025-03-05 09:34:44 +00:00
Adam Hathcock 4d552b6834 add speckle serializer exception test 2025-03-04 16:44:15 +00:00
Adam Hathcock 378a91995e fix build 2025-03-04 16:41:59 +00:00
Adam Hathcock 53b66dd26b remove extra code form hosts 2025-03-04 13:29:36 +00:00
Adam Hathcock 9dd04c0881 fmt 2025-03-03 16:39:17 +00:00
Adam Hathcock 474c18c29f maybe all objects need to be false 2025-03-03 16:32:02 +00:00
193 changed files with 3133 additions and 1806 deletions
+2 -2
View File
@@ -3,9 +3,9 @@
"isRoot": true,
"tools": {
"csharpier": {
"version": "0.30.6",
"version": "1.0.1",
"commands": [
"dotnet-csharpier"
"csharpier"
],
"rollForward": false
},
+26
View File
@@ -0,0 +1,26 @@
Directory.Build.targets
Directory.Build.props
**/bin/*
**/obj/*
_ReSharper.SharpCompress/
bin/
*.suo
*.user
TestArchives/Scratch/
TestArchives/Scratch2/
TestResults/
*.nupkg
packages/*/
project.lock.json
tests/TestArchives/Scratch
.vs
tools
.vscode
.idea/
.DS_Store
*.snupkg
coverage.xml
*.received.*
+1 -1
View File
@@ -1,6 +1,6 @@
printWidth: 120
useTabs: false
tabWidth: 2
indentSize: 2
preprocessorSymbolSets:
- ""
- "DEBUG"
+8 -21
View File
@@ -1,5 +1,4 @@
<Project>
<PropertyGroup Label="Compiler Properties">
<LangVersion>12</LangVersion>
<Nullable>enable</Nullable>
@@ -7,7 +6,6 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
<PropertyGroup Label="Nugetspec Package Properties">
<!-- Defines common Nugetspec properties -->
<!-- Inheriting packable projects should define the rest of the nugetspec properties (PackageId, Description) -->
@@ -22,18 +20,16 @@
<PackageTags>speckle</PackageTags>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
</PropertyGroup>
<PropertyGroup Label="Nuget Package Properties">
<IsPackable>false</IsPackable> <!--Can be set to true in inheriting .props/.csproj files for projects that should be packed-->
<IsPackable>false</IsPackable>
<!--Can be set to true in inheriting .props/.csproj files for projects that should be packed-->
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
</PropertyGroup>
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<PropertyGroup Label="Analyers">
<EnableNetAnalyzers>true</EnableNetAnalyzers>
<AnalysisLevel>latest-AllEnabledByDefault</AnalysisLevel>
@@ -41,7 +37,6 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<!-- Ingored warnings, some aspirational but too noisy for now, some by design. -->
<NoWarn>
<!--Disabled by design-->
@@ -59,28 +54,20 @@
<!-- Aspirational -->
CA1502;CA1716;NETSDK1206;
$(NoWarn)
</NoWarn>
</NoWarn
>
</PropertyGroup>
<PropertyGroup>
<!-- Expose the repository root to all projects -->
<RepositoryRoot>$(MSBuildThisFileDirectory)</RepositoryRoot>
</PropertyGroup>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\README.md" Pack="true" PackagePath="\"/>
<None
Condition="'$(IsPackable)' == 'true'"
Include="..\..\logo.png"
Pack="true"
PackagePath="\"
Visible="false"/>
<None Include="..\..\README.md" Pack="true" PackagePath="\" />
<None Condition="'$(IsPackable)' == 'true'" Include="..\..\logo.png" Pack="true" PackagePath="\" Visible="false" />
</ItemGroup>
<ItemGroup>
<!-- This file contains the configuration for some analyzer warnings, such as cyclomatic
complexity threshold -->
<AdditionalFiles Include="$(RepositoryRoot)CodeMetricsConfig.txt"/>
<AdditionalFiles Include="$(RepositoryRoot)CodeMetricsConfig.txt" />
</ItemGroup>
</Project>
+13 -14
View File
@@ -1,18 +1,17 @@
<Project>
<PropertyGroup Condition="'$(IsTestProject)' == 'true'">
<NoWarn>
$(NoWarn);
<!-- Things we need to test -->
CS0618;CA1034;CA2201;CA1051;CA1040;CA1724;
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;
</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(IsTestProject)' == 'true'">
<NoWarn>
<!-- Things we need to test -->
CS0618;CA1034;CA2201;CA1051;CA1040;CA1724;
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;
$(NoWarn);
</NoWarn>
</PropertyGroup>
<Target Name="DeepClean">
<Message Text="Deep clean of $(MSBuildProjectName).csproj" Importance="high"/>
<RemoveDir Directories="$(BaseIntermediateOutputPath)"/>
<RemoveDir Directories="$(BaseOutputPath)"/>
<Message Text="Deep clean of $(MSBuildProjectName).csproj" Importance="high" />
<RemoveDir Directories="$(BaseIntermediateOutputPath)" />
<RemoveDir Directories="$(BaseOutputPath)" />
</Target>
</Project>
+9 -7
View File
@@ -1,32 +1,34 @@
<Project>
<ItemGroup>
<PackageVersion Include="altcover" Version="9.0.1" />
<PackageVersion Include="AwesomeAssertions" Version="8.0.0" />
<PackageVersion Include="AwesomeAssertions" Version="8.1.0" />
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
<PackageVersion Include="Bullseye" Version="5.0.0" />
<PackageVersion Include="Bullseye" Version="6.0.0" />
<PackageVersion Include="GraphQL.Client" Version="6.0.0" />
<PackageVersion Include="Glob" Version="1.1.9" />
<PackageVersion Include="HttpMultipartParser" Version="9.0.0" />
<PackageVersion Include="ILRepack.FullAuto" Version="1.6.0" />
<PackageVersion Include="Microsoft.CSharp" Version="4.7.0" />
<!-- Keep at exactly 7.0.5 for side by side with V2 -->
<PackageVersion Include="Microsoft.Data.Sqlite" Version="[7.0.5,)" />
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="9.0.4" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="[2.2.0,)" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="[2.2.0,)" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="[2.2.0,)" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="[5.0.0,)" />
<PackageVersion Include="Moq" Version="4.20.70" />
<PackageVersion Include="Open.ChannelExtensions" Version="9.0.0" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="Open.ChannelExtensions" Version="9.1.0" />
<PackageVersion Include="Polly" Version="7.2.3" />
<PackageVersion Include="Polly.Contrib.WaitAndRetry" Version="1.1.1" />
<PackageVersion Include="Polly.Extensions.Http" Version="3.0.0" />
<PackageVersion Include="RichardSzalay.MockHttp" Version="7.0.0" />
<PackageVersion Include="Speckle.Newtonsoft.Json" Version="13.0.2" />
<PackageVersion Include="Speckle.DoubleNumerics" Version="4.1.0" />
<PackageVersion Include="SimpleExec" Version="12.0.0" />
<PackageVersion Include="System.Threading.Channels" Version="9.0.2" />
<PackageVersion Include="System.Threading.Channels" Version="9.0.4" />
<PackageVersion Include="Verify.Quibble" Version="2.1.1" />
<PackageVersion Include="Verify.Xunit" Version="28.10.1" />
<PackageVersion Include="Verify.Xunit" Version="29.4.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.assert" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.2" />
+1 -1
View File
@@ -8,7 +8,7 @@ Speckle | Sharp | SDK
### .NET SDK, Tests, and Objects
[![Codecov](https://codecov.io/gh/specklesystems/speckle-sharp-sdk/graph/badge.svg?token=TTM5OGr38m)](https://codecov.io/gh/specklesystems/speckle-sharp-sdk)
[![codecov](https://codecov.io/gh/specklesystems/speckle-sharp-sdk/branch/dev/graph/badge.svg?token=TTM5OGr38m)](https://codecov.io/gh/specklesystems/speckle-sharp-sdk)
> [!WARNING]
> This is an early beta release, not meant for use in production! We're working to stabilise the 3.0 API, and until then there will be breaking changes. You have been warned!
+8 -8
View File
@@ -42,7 +42,7 @@ Target(
Target(
CLEAN,
ForEach("**/output"),
forEach: ["**/output"],
dir =>
{
IEnumerable<string> GetDirectories(string d)
@@ -68,13 +68,13 @@ Target(
Target(RESTORE_TOOLS, () => RunAsync("dotnet", "tool restore"));
Target(FORMAT, DependsOn(RESTORE_TOOLS), () => RunAsync("dotnet", "csharpier --check ."));
Target(FORMAT, dependsOn: [RESTORE_TOOLS], () => RunAsync("dotnet", "csharpier check ."));
Target(RESTORE, DependsOn(FORMAT), () => RunAsync("dotnet", "restore Speckle.Sdk.sln --locked-mode"));
Target(RESTORE, dependsOn: [FORMAT], () => RunAsync("dotnet", "restore Speckle.Sdk.sln --locked-mode"));
Target(
BUILD,
DependsOn(RESTORE),
dependsOn: [RESTORE],
async () =>
{
var (version, fileVersion) = await GetVersions().ConfigureAwait(false);
@@ -89,7 +89,7 @@ Target(
Target(
TEST,
DependsOn(BUILD),
dependsOn: [BUILD],
Glob.Files(".", "**/*.Tests.Unit.csproj").Concat(Glob.Files(".", "**/*.Tests.csproj")),
async file =>
{
@@ -103,7 +103,7 @@ Target(
Target(
INTEGRATION,
DependsOn(BUILD),
dependsOn: [BUILD],
async () =>
{
await RunAsync("docker", "compose -f docker-compose.yml up --wait").ConfigureAwait(false);
@@ -170,7 +170,7 @@ Target(
Target(
PACK,
DependsOn(BUILD),
dependsOn: [TEST],
async () =>
{
{
@@ -182,6 +182,6 @@ Target(
}
);
Target("default", DependsOn(FORMAT, TEST, INTEGRATION), () => Console.WriteLine("Done!"));
Target("default", dependsOn: [FORMAT, TEST, INTEGRATION], () => Console.WriteLine("Done!"));
await RunTargetsAndExitAsync(args).ConfigureAwait(true);
+1 -2
View File
@@ -1,10 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ItemGroup>
<PackageReference Include="Bullseye" />
<PackageReference Include="Glob" />
<PackageReference Include="SimpleExec" />
+3 -3
View File
@@ -4,9 +4,9 @@
"net8.0": {
"Bullseye": {
"type": "Direct",
"requested": "[5.0.0, )",
"resolved": "5.0.0",
"contentHash": "bqyt+m17ym+5aN45C5oZRAjuLDt8jKiCm/ys1XfymIXSkrTFwvI/QsbY3ucPSHDz7SF7uON7B57kXFv5H2k1ew=="
"requested": "[6.0.0, )",
"resolved": "6.0.0",
"contentHash": "vgwwXfzs7jJrskWH7saHRMgPzziq/e86QZNWY1MnMxd7e+De7E7EX4K3C7yrvaK9y02SJoLxNxcLG/q5qUAghw=="
},
"Glob": {
"type": "Direct",
+69
View File
@@ -0,0 +1,69 @@
using Speckle.Objects.Geometry;
using Speckle.Sdk.Models;
using Point = Speckle.Objects.Geometry.Point;
namespace Speckle.Objects.Annotation;
/// <summary>
/// Text class for representation in the viewer
/// </summary>
[SpeckleType("Objects.Annotation.Text")]
public class Text : Base
{
/// <summary>
/// Plain text, without formatting
/// </summary>
public required string value { get; set; }
/// <summary>
/// Origin point, relation to the text is defined by AlignmentHorizontal and AlignmentVertical
/// </summary>
public required Point origin { get; set; }
/// <summary>
/// Height in linear units or pixels (if Units.None)
/// </summary>
public required double height { get; set; }
/// <summary>
/// Units will be 'Units.None' if the text size is defined in pixels (stays the same size
/// independently of zooming the model). Default height in pixels is 17px (used for Viewer measurements)
/// </summary>
public required string units { get; set; }
/// <summary>
/// Horizontal alignment: Left, Center or Right
/// </summary>
public AlignmentHorizontal alignmentH { get; set; }
/// <summary>
/// Vertical alignment: Top, Center or Bottom
/// </summary>
public AlignmentVertical alignmentV { get; set; }
/// <summary>
/// Plane will be null if the text object orientation follows camera view
/// </summary>
public Plane? plane { get; set; }
/// <summary>
/// Maximum width of the text field (in 'units').
/// Text will be split into lines (wrapped) to fit into the width.
/// null, if text should not be wrapped.
/// </summary>
public double? maxWidth { get; set; }
}
public enum AlignmentHorizontal
{
Left,
Center,
Right,
}
public enum AlignmentVertical
{
Top,
Center,
Bottom,
}
+8 -9
View File
@@ -3,7 +3,6 @@ using Speckle.Newtonsoft.Json;
using Speckle.Objects.Other;
using Speckle.Objects.Primitive;
using Speckle.Sdk.Common;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
namespace Speckle.Objects.Geometry;
@@ -31,7 +30,7 @@ public class Brep : Base, IHasArea, IHasVolume, IHasBoundingBox, ITransformable<
/// <summary>
/// Gets or sets the flat list of numbers representing the <see cref="Brep"/>'s surfaces.
/// </summary>
[DetachProperty, SchemaIgnore, Chunkable(31250)]
[DetachProperty, Chunkable(31250)]
public List<double> SurfacesValue
{
get
@@ -77,7 +76,7 @@ public class Brep : Base, IHasArea, IHasVolume, IHasBoundingBox, ITransformable<
/// <remarks>
/// This is only used for the <see cref="Brep"/> class serialisation/deserialisation. You should use <see cref="Brep.Curve3D"/> instead.
/// </remarks>
[DetachProperty, SchemaIgnore, Chunkable(31250)]
[DetachProperty, Chunkable(31250)]
public List<double> Curve3DValues
{
get => CurveArrayEncodingExtensions.ToArray(Curve3D);
@@ -102,7 +101,7 @@ public class Brep : Base, IHasArea, IHasVolume, IHasBoundingBox, ITransformable<
/// <remarks>
/// This is only used for the <see cref="Brep"/> class serialisation/deserialisation. You should use <see cref="Brep.Curve2D"/> instead.
/// </remarks>
[DetachProperty, SchemaIgnore, Chunkable(31250)]
[DetachProperty, Chunkable(31250)]
public List<double> Curve2DValues
{
get => CurveArrayEncodingExtensions.ToArray(Curve2D);
@@ -127,7 +126,7 @@ public class Brep : Base, IHasArea, IHasVolume, IHasBoundingBox, ITransformable<
/// <remarks>
/// This is only used for the <see cref="Brep"/> class serialisation/deserialisation. You should use <see cref="Brep.Vertices"/> instead.
/// </remarks>
[DetachProperty, SchemaIgnore, Chunkable(31250)]
[DetachProperty, Chunkable(31250)]
public List<double> VerticesValue
{
get
@@ -167,7 +166,7 @@ public class Brep : Base, IHasArea, IHasVolume, IHasBoundingBox, ITransformable<
/// <remarks>
/// This is only used for the <see cref="Brep"/> class serialisation/deserialisation. You should use <see cref="Brep.Edges"/> instead.
/// </remarks>
[DetachProperty, SchemaIgnore, Chunkable(62500)]
[DetachProperty, Chunkable(62500)]
public List<double?> EdgesValue
{
get =>
@@ -241,7 +240,7 @@ public class Brep : Base, IHasArea, IHasVolume, IHasBoundingBox, ITransformable<
/// <remarks>
/// This is only used for the <see cref="Brep"/> class serialisation/deserialisation. You should use <see cref="Brep.Loops"/> instead.
/// </remarks>
[DetachProperty, SchemaIgnore, Chunkable(62500)]
[DetachProperty, Chunkable(62500)]
public List<int> LoopsValue
{
get =>
@@ -297,7 +296,7 @@ public class Brep : Base, IHasArea, IHasVolume, IHasBoundingBox, ITransformable<
/// <remarks>
/// This is only used for the <see cref="Brep"/> class serialisation/deserialisation. You should use <see cref="Brep.Trims"/> instead.
/// </remarks>
[DetachProperty, SchemaIgnore, Chunkable(62500)]
[DetachProperty, Chunkable(62500)]
public List<int> TrimsValue
{
get
@@ -363,7 +362,7 @@ public class Brep : Base, IHasArea, IHasVolume, IHasBoundingBox, ITransformable<
/// <remarks>
/// This is only used for the <see cref="Brep"/> class serialisation/deserialisation. You should use <see cref="Brep.Faces"/> instead.
/// </remarks>
[DetachProperty, SchemaIgnore, Chunkable(62500)]
[DetachProperty, Chunkable(62500)]
public List<int> FacesValue
{
get =>
-1
View File
@@ -68,7 +68,6 @@ public class Plane : Base, ITransformable<Plane>
/// Returns the values of this <see cref="Plane"/> as a list of numbers
/// </summary>
/// <returns>A list of values representing the Plane.</returns>
public List<double> ToList()
{
var list = new List<double>();
-1
View File
@@ -147,7 +147,6 @@ public class Surface : Base, IHasBoundingBox, IHasArea, ITransformable<Surface>
/// </summary>
/// <returns>A 2-dimensional array representing this <see cref="Surface"/>s control points.</returns>
/// <remarks>The ControlPoints will be ordered following directions "[u][v]"</remarks>
public List<List<ControlPoint>> GetControlPoints()
{
var matrix = new List<List<ControlPoint>>();
@@ -1,37 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Compiler Properties">
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<PolySharpExcludeGeneratedTypes>System.Runtime.CompilerServices.RequiresLocationAttribute</PolySharpExcludeGeneratedTypes>
<Configurations>Debug;Release;Local</Configurations>
</PropertyGroup>
<PropertyGroup Label="Nugetspec Package Properties">
<PackageId>Speckle.Objects</PackageId>
<Description>Objects is the default object model for Speckle</Description>
<PackageTags>$(PackageTags) objects</PackageTags>
</PropertyGroup>
<PropertyGroup Label="Nuget Package Properties">
<IsPackable>true</IsPackable>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<PropertyGroup Label="Analyers">
<NoWarn>
$(NoWarn);
CA1819;CA1008;CA2225;
</NoWarn>
</PropertyGroup>
<ItemGroup Label="Expose internals to test projects">
<InternalsVisibleTo Include="Speckle.Objects.Tests.Unit" />
</ItemGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\Speckle.Sdk\Speckle.Sdk.csproj" />
</ItemGroup>
</Project>
@@ -22,4 +22,23 @@ public static class Collections
public static class EnumerableExtensions
{
public static IEnumerable<int> RangeFrom(int from, int to) => Enumerable.Range(from, to - from + 1);
#if NETSTANDARD2_0
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector
)
{
var keys = new HashSet<TKey>();
foreach (var element in source)
{
if (keys.Contains(keySelector(element)))
{
continue;
}
keys.Add(keySelector(element));
yield return element;
}
}
#endif
}
@@ -19,13 +19,13 @@ public static class BatchExtensions
public static void AddBatchItem<T>(this IMemoryOwner<T> batch, T item)
where T : IHasByteSize => ((Batch<T>)batch).Add(item);
public static int GetBatchSize<T>(this IMemoryOwner<T> batch, Action<string> logAsWarning, int maxBatchSize)
public static int GetBatchSize<T>(this IMemoryOwner<T> batch, int maxBatchSize)
where T : IHasByteSize
{
var currentSize = ((Batch<T>)batch).BatchByteSize;
if (currentSize > maxBatchSize)
{
logAsWarning($"Batch size exceeded. Current size: {currentSize} bytes. Max size: {maxBatchSize} bytes.");
//doing this to say it's full since the channel reader only does full being equivalent
return maxBatchSize;
}
@@ -8,7 +8,6 @@ public static class ChannelExtensions
{
public static BatchingChannelReader<T, IMemoryOwner<T>> BatchByByteSize<T>(
this ChannelReader<T> source,
Action<string> logAsWarning,
int batchSize,
bool singleReader = false,
bool allowSynchronousContinuations = false
@@ -16,7 +15,6 @@ public static class ChannelExtensions
where T : IHasByteSize =>
new SizeBatchingChannelReader<T>(
source ?? throw new ArgumentNullException(nameof(source)),
logAsWarning,
batchSize,
singleReader,
allowSynchronousContinuations
@@ -5,10 +5,10 @@ using Speckle.Sdk.Serialisation.V2.Send;
namespace Speckle.Sdk.Dependencies.Serialization;
public abstract class ChannelSaver<T>(Action<string> logAsWarning, CancellationToken cancellationToken)
public abstract class ChannelSaver<T>
where T : IHasByteSize
{
private const int SEND_CAPACITY = 500;
private const int SEND_CAPACITY = 1000;
private const int HTTP_SEND_CHUNK_SIZE = 25_000_000; //bytes
private static readonly TimeSpan HTTP_BATCH_TIMEOUT = TimeSpan.FromSeconds(2);
private const int MAX_PARALLELISM_HTTP = 4;
@@ -16,8 +16,6 @@ public abstract class ChannelSaver<T>(Action<string> logAsWarning, CancellationT
private const int MAX_CACHE_WRITE_PARALLELISM = 4;
private const int MAX_CACHE_BATCH = 500;
private readonly CancellationTokenSource _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
private readonly Channel<T> _checkCacheChannel = Channel.CreateBounded<T>(
new BoundedChannelOptions(SEND_CAPACITY)
{
@@ -30,26 +28,31 @@ public abstract class ChannelSaver<T>(Action<string> logAsWarning, CancellationT
_ => throw new NotImplementedException("Dropping items not supported.")
);
public Task Start() =>
public Task Start(
int? maxParallelism,
int? httpBatchSize,
int? cacheBatchSize,
CancellationToken cancellationToken
) =>
_checkCacheChannel
.Reader.BatchByByteSize(logAsWarning, HTTP_SEND_CHUNK_SIZE)
.Reader.BatchByByteSize(httpBatchSize ?? HTTP_SEND_CHUNK_SIZE)
.WithTimeout(HTTP_BATCH_TIMEOUT)
.PipeAsync(
MAX_PARALLELISM_HTTP,
maxParallelism ?? MAX_PARALLELISM_HTTP,
async x => await SendToServer(x).ConfigureAwait(false),
HTTP_CAPACITY,
false,
_cts.Token
cancellationToken
)
.Join()
.Batch(MAX_CACHE_BATCH)
.Batch(cacheBatchSize ?? MAX_CACHE_BATCH)
.WithTimeout(HTTP_BATCH_TIMEOUT)
.ReadAllConcurrently(MAX_CACHE_WRITE_PARALLELISM, SaveToCache, _cts.Token)
.ReadAllConcurrently(maxParallelism ?? MAX_CACHE_WRITE_PARALLELISM, SaveToCache, cancellationToken)
.ContinueWith(
t =>
{
Exception? ex = t.Exception;
if (ex is null && t.Status is TaskStatus.Canceled && !_cts.Token.IsCancellationRequested)
if (ex is null && t.Status is TaskStatus.Canceled && !cancellationToken.IsCancellationRequested)
{
ex = new OperationCanceledException();
}
@@ -60,25 +63,27 @@ public abstract class ChannelSaver<T>(Action<string> logAsWarning, CancellationT
}
_checkCacheChannel.Writer.TryComplete(ex);
},
_cts.Token,
cancellationToken,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Current
);
public async ValueTask Save(T item)
public async Task SaveAsync(T item, CancellationToken cancellationToken)
{
if (Exception is not null || _cts.IsCancellationRequested)
if (Exception is not null)
{
return; //don't save if we're already done through an error
}
await _checkCacheChannel.Writer.WriteAsync(item).ConfigureAwait(false);
//can switch to check then try pattern when back pressure is needed or exceptions are too much
//the trees don't need to respond to back pressure
await _checkCacheChannel.Writer.WriteAsync(item, cancellationToken).ConfigureAwait(false);
}
private async Task<IMemoryOwner<T>> SendToServer(IMemoryOwner<T> batch)
{
try
{
await SendToServer((Batch<T>)batch).ConfigureAwait(false);
await SendToServerInternal((Batch<T>)batch).ConfigureAwait(false);
return batch;
}
#pragma warning disable CA1031
@@ -90,20 +95,6 @@ public abstract class ChannelSaver<T>(Action<string> logAsWarning, CancellationT
}
}
public async Task SendToServer(Batch<T> batch)
{
try
{
await SendToServerInternal(batch).ConfigureAwait(false);
}
#pragma warning disable CA1031
catch (Exception ex)
#pragma warning restore CA1031
{
RecordException(ex);
}
}
protected abstract Task SendToServerInternal(Batch<T> batch);
public abstract void SaveToCache(List<T> item);
@@ -118,13 +109,11 @@ public abstract class ChannelSaver<T>(Action<string> logAsWarning, CancellationT
}
}
protected Exception? Exception { get; set; }
public Exception? Exception { get; set; }
private void RecordException(Exception ex)
{
Exception = ex;
_checkCacheChannel.Writer.TryComplete(ex);
//cancel everything!
_cts.Cancel();
}
}
@@ -11,7 +11,6 @@ public interface IHasByteSize
public sealed class SizeBatchingChannelReader<T>(
ChannelReader<T> source,
Action<string> logAsWarning,
int batchSize,
bool singleReader,
bool syncCont = false
@@ -34,5 +33,5 @@ public sealed class SizeBatchingChannelReader<T>(
protected override void AddBatchItem(IMemoryOwner<T> batch, T item) => batch.AddBatchItem(item);
protected override int GetBatchSize(IMemoryOwner<T> batch) => batch.GetBatchSize(logAsWarning, _batchSize);
protected override int GetBatchSize(IMemoryOwner<T> batch) => batch.GetBatchSize(_batchSize);
}
@@ -1,5 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Compiler Properties">
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<Configurations>Debug;Release;Local</Configurations>
@@ -7,30 +6,26 @@
<ILRepackRenameInternalized>true</ILRepackRenameInternalized>
<ILRepackMergeDebugSymbols>true</ILRepackMergeDebugSymbols>
</PropertyGroup>
<PropertyGroup Label="Nugetspec Package Properties">
<PackageId>Speckle.Sdk.Dependencies</PackageId>
<Description>The .NET SDK for Speckle</Description>
<PackageTags>$(PackageTags) core sdk</PackageTags>
</PropertyGroup>
<PropertyGroup Label="Nuget Package Properties">
<IsPackable>true</IsPackable>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ILRepack.FullAuto">
<PackageReference Include="ILRepack.FullAuto">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.ObjectPool" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" PrivateAssets="all" />
<PackageReference Include="Polly" PrivateAssets="all" />
<PackageReference Include="Polly.Contrib.WaitAndRetry" PrivateAssets="all" />
<PackageReference Include="Polly.Extensions.Http" PrivateAssets="all" />
<PackageReference Include="Open.ChannelExtensions" PrivateAssets="all" />
<PackageReference Include="System.Threading.Channels" PrivateAssets="all" />
</ItemGroup>
</Project>
@@ -3,7 +3,7 @@ using Speckle.Sdk.Logging;
namespace Speckle.Sdk.Helpers;
public sealed class SpeckleHttpClientHandler : DelegatingHandler
internal sealed class SpeckleHttpClientHandler : DelegatingHandler
{
private readonly IAsyncPolicy<HttpResponseMessage> _resiliencePolicy;
private readonly ISdkActivityFactory _activityFactory;
@@ -40,8 +40,13 @@ public sealed class SpeckleHttpClientHandlerFactory(ISdkActivityFactory activity
return Policy.WrapAsync(retryPolicy, timeoutPolicy);
}
public SpeckleHttpClientHandler Create(
public DelegatingHandler Create(
HttpMessageHandler? innerHandler = null,
int timeoutSeconds = DEFAULT_TIMEOUT_SECONDS
) => new(innerHandler ?? new HttpClientHandler(), activityFactory, HttpAsyncPolicy(timeoutSeconds: timeoutSeconds));
) =>
new SpeckleHttpClientHandler(
innerHandler ?? new HttpClientHandler(),
activityFactory,
HttpAsyncPolicy(timeoutSeconds: timeoutSeconds)
);
}
+26 -26
View File
@@ -13,9 +13,9 @@
},
"Microsoft.Extensions.ObjectPool": {
"type": "Direct",
"requested": "[9.0.1, )",
"resolved": "9.0.1",
"contentHash": "r64veU9uYILp6pYqfo3qzRab8zLMALvXZgT4VRY79tXMLu8X79uTlJ6nqPLtPIVhfCPXycRh8ILyFz/gGBDQdQ=="
"requested": "[9.0.4, )",
"resolved": "9.0.4",
"contentHash": "G7p1k2xVZ+2aVANz0JdSiafr+AHDHeS1kF8+Y0ABbIsByd0erOL59IDXBs9vcdJf3pPV/murO0mbtr4k40QxWw=="
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
@@ -38,13 +38,13 @@
},
"Open.ChannelExtensions": {
"type": "Direct",
"requested": "[9.0.0, )",
"resolved": "9.0.0",
"contentHash": "DP+l5S6G46wcuY4I4kNXE+RDOmJr0DKuMienOdt0mMBN9z7vmLSC8YQbqCyb9i9LNjXj1tgCx5LyitJiRr/v7g==",
"requested": "[9.1.0, )",
"resolved": "9.1.0",
"contentHash": "D6c24vMGy1oZ06vmkD2/FNzWHK7ZIihuv2spDgYEeaUp+eobrILQnrNQKRoASFXD4JGfZ7nfvTM0e+AX79dt8Q==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "9.0.0",
"System.Collections.Immutable": "9.0.0",
"System.Threading.Channels": "9.0.0"
"Microsoft.Bcl.AsyncInterfaces": "9.0.4",
"System.Collections.Immutable": "9.0.4",
"System.Threading.Channels": "9.0.4"
}
},
"Polly": {
@@ -82,11 +82,11 @@
},
"System.Threading.Channels": {
"type": "Direct",
"requested": "[9.0.2, )",
"resolved": "9.0.2",
"contentHash": "pUmqkuBS9OxWHOlfNad09Oxc8gRbxgN9UQtsqHPst4jfcgZRxQetNcsT2oe+VnUpEFAtBy1FZcJZiOscrBmA7g==",
"requested": "[9.0.4, )",
"resolved": "9.0.4",
"contentHash": "4qBn2H6/aXBpE/Pm3wY5yusY/pEvQz99NlWHrTUji0qCmOdbhhjaALcpmbfW2ksxlPM6i6S+QFLkpOQdyfeKYQ==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "9.0.2",
"Microsoft.Bcl.AsyncInterfaces": "9.0.4",
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
@@ -117,8 +117,8 @@
},
"System.Collections.Immutable": {
"type": "Transitive",
"resolved": "9.0.0",
"contentHash": "QhkXUl2gNrQtvPmtBTQHb0YsUrDiDQ2QS09YbtTTiSjGcf7NBqtYbrG/BE06zcBPCKEwQGzIv13IVdXNOSub2w==",
"resolved": "9.0.4",
"contentHash": "wfm2NgK22MmBe5qJjp52qzpkeDZKb4l9LbdubhZSehY1z4LS+lld6R+B+UQNb2AZRHu/QJlHxEUcRst5hIEejg==",
"dependencies": {
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
@@ -155,8 +155,8 @@
"Microsoft.Bcl.AsyncInterfaces": {
"type": "CentralTransitive",
"requested": "[5.0.0, )",
"resolved": "9.0.2",
"contentHash": "1CED0BGD7dCKsbe7tDhzpPB2Qdi9x35QChu6zkBEI4s0T5bDkkttGReqQnOeOfRNSxtP2WvpX6Ik/0O93XDuMw==",
"resolved": "9.0.4",
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
@@ -174,9 +174,9 @@
},
"Microsoft.Extensions.ObjectPool": {
"type": "Direct",
"requested": "[9.0.1, )",
"resolved": "9.0.1",
"contentHash": "r64veU9uYILp6pYqfo3qzRab8zLMALvXZgT4VRY79tXMLu8X79uTlJ6nqPLtPIVhfCPXycRh8ILyFz/gGBDQdQ=="
"requested": "[9.0.4, )",
"resolved": "9.0.4",
"contentHash": "G7p1k2xVZ+2aVANz0JdSiafr+AHDHeS1kF8+Y0ABbIsByd0erOL59IDXBs9vcdJf3pPV/murO0mbtr4k40QxWw=="
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
@@ -190,9 +190,9 @@
},
"Open.ChannelExtensions": {
"type": "Direct",
"requested": "[9.0.0, )",
"resolved": "9.0.0",
"contentHash": "DP+l5S6G46wcuY4I4kNXE+RDOmJr0DKuMienOdt0mMBN9z7vmLSC8YQbqCyb9i9LNjXj1tgCx5LyitJiRr/v7g=="
"requested": "[9.1.0, )",
"resolved": "9.1.0",
"contentHash": "D6c24vMGy1oZ06vmkD2/FNzWHK7ZIihuv2spDgYEeaUp+eobrILQnrNQKRoASFXD4JGfZ7nfvTM0e+AX79dt8Q=="
},
"Polly": {
"type": "Direct",
@@ -229,9 +229,9 @@
},
"System.Threading.Channels": {
"type": "Direct",
"requested": "[9.0.2, )",
"resolved": "9.0.2",
"contentHash": "pUmqkuBS9OxWHOlfNad09Oxc8gRbxgN9UQtsqHPst4jfcgZRxQetNcsT2oe+VnUpEFAtBy1FZcJZiOscrBmA7g=="
"requested": "[9.0.4, )",
"resolved": "9.0.4",
"contentHash": "4qBn2H6/aXBpE/Pm3wY5yusY/pEvQz99NlWHrTUji0qCmOdbhhjaALcpmbfW2ksxlPM6i6S+QFLkpOQdyfeKYQ=="
},
"ILRepack": {
"type": "Transitive",
+13
View File
@@ -1,4 +1,5 @@
using Speckle.Sdk.Api.GraphQL;
using Speckle.Sdk.Api.GraphQL.Models;
namespace Speckle.Sdk.Api;
@@ -96,3 +97,15 @@ public sealed class SpeckleGraphQLInvalidQueryException : SpeckleGraphQLExceptio
public SpeckleGraphQLInvalidQueryException(string? message, Exception? innerException)
: base(message, innerException) { }
}
/// <seealso cref="PermissionCheckResult"/>
public sealed class WorkspacePermissionException : SpeckleGraphQLException
{
public WorkspacePermissionException() { }
public WorkspacePermissionException(string? message)
: base(message) { }
public WorkspacePermissionException(string? message, Exception? innerException)
: base(message, innerException) { }
}
+13 -1
View File
@@ -4,6 +4,7 @@ using System.Reflection;
using GraphQL;
using GraphQL.Client.Http;
using Microsoft.Extensions.Logging;
using Speckle.InterfaceGenerator;
using Speckle.Newtonsoft.Json;
using Speckle.Newtonsoft.Json.Serialization;
using Speckle.Sdk.Api.GraphQL;
@@ -16,8 +17,14 @@ using Speckle.Sdk.Logging;
namespace Speckle.Sdk.Api;
public partial interface IClient : IDisposable
{
GraphQLHttpClient GQLClient { get; }
}
[SuppressMessage("Maintainability", "CA1506:Avoid excessive class coupling", Justification = "Class needs refactor")]
public sealed class Client : ISpeckleGraphQLClient, IDisposable
[GenerateAutoInterface]
public sealed class Client : ISpeckleGraphQLClient, IClient
{
private readonly ILogger<Client> _logger;
private readonly ISdkActivityFactory _activityFactory;
@@ -29,6 +36,7 @@ public sealed class Client : ISpeckleGraphQLClient, IDisposable
public ProjectInviteResource ProjectInvite { get; }
public CommentResource Comment { get; }
public SubscriptionResource Subscription { get; }
public WorkspaceResource Workspace { get; }
public Uri ServerUrl => new(Account.serverInfo.url);
@@ -37,6 +45,7 @@ public sealed class Client : ISpeckleGraphQLClient, IDisposable
private HttpClient HttpClient { get; }
[AutoInterfaceIgnore]
public GraphQLHttpClient GQLClient { get; }
/// <param name="account"></param>
@@ -61,12 +70,14 @@ public sealed class Client : ISpeckleGraphQLClient, IDisposable
ProjectInvite = new(this);
Comment = new(this);
Subscription = new(this);
Workspace = new(this);
HttpClient = CreateHttpClient(application, speckleHttp, account);
GQLClient = CreateGraphQLClient(account, HttpClient);
}
[AutoInterfaceIgnore]
public void Dispose()
{
try
@@ -132,6 +143,7 @@ public sealed class Client : ISpeckleGraphQLClient, IDisposable
}
}
[AutoInterfaceIgnore]
IDisposable ISpeckleGraphQLClient.SubscribeTo<T>(GraphQLRequest request, Action<object, T> callback) =>
SubscribeTo(request, callback);
+2 -2
View File
@@ -14,6 +14,6 @@ public class ClientFactory(
ISpeckleHttp speckleHttp
) : IClientFactory
{
public Client Create(Account account) =>
new(loggerFactory.CreateLogger<Client>(), activityFactory, application, speckleHttp, account);
public IClient Create(Account account) =>
new Client(loggerFactory.CreateLogger<Client>(), activityFactory, application, speckleHttp, account);
}
@@ -2,7 +2,9 @@
public enum ProjectVisibility
{
Private,
Public,
Unlisted,
Private = 0,
[Obsolete("Use Unlisted instead", true)]
Public = 1,
Unlisted = 2,
}
@@ -6,6 +6,13 @@ public sealed record ProjectCommentsFilter(bool? includeArchived, bool? loadedVe
public sealed record ProjectCreateInput(string? name, string? description, ProjectVisibility? visibility);
public sealed record WorkspaceProjectCreateInput(
string? name,
string? description,
ProjectVisibility? visibility,
string workspaceId
);
public sealed record ProjectInviteCreateInput(string? email, string? role, string? serverRole, string? userId);
public sealed record ProjectInviteUseInput(bool accept, string projectId, string token);
@@ -29,4 +36,4 @@ public sealed record ProjectUpdateInput(
public sealed record ProjectUpdateRoleInput(string userId, string projectId, string? role);
public sealed record UserProjectsFilter(string search, IReadOnlyList<string>? onlyWithRoles = null);
public sealed record WorkspaceProjectsFilter(string? search, bool? withProjectRoleOnly);
@@ -6,3 +6,13 @@ public sealed record UserUpdateInput(
string? company = null,
string? name = null
);
public sealed record UserProjectsFilter(
string? search = null,
IReadOnlyList<string>? onlyWithRoles = null,
string? workspaceId = null,
bool? personalOnly = null,
bool? includeImplicitAccess = null
);
public sealed record UserWorkspacesFilter(string? search);
@@ -0,0 +1,17 @@
namespace Speckle.Sdk.Api.GraphQL.Models;
public sealed class PermissionCheckResult
{
public bool authorized { get; init; }
public string code { get; init; }
public string message { get; init; }
/// <exception cref="SpeckleException">Throws when <see cref="PermissionCheckResult.authorized"/> is <see langword="false"/></exception>
public void EnsureAuthorised()
{
if (!authorized)
{
throw new WorkspacePermissionException(message);
}
}
}
@@ -0,0 +1,9 @@
namespace Speckle.Sdk.Api.GraphQL.Models;
public sealed class ProjectPermissionChecks
{
public PermissionCheckResult canCreateModel { get; init; }
public PermissionCheckResult canDelete { get; init; }
public PermissionCheckResult canLoad { get; init; }
public PermissionCheckResult canPublish { get; init; }
}
@@ -7,6 +7,8 @@ public sealed class Version
public string id { get; init; }
public string? message { get; init; }
public Uri previewUrl { get; init; }
public string referencedObject { get; init; }
/// <remarks>May be <see langword="null"/> if workspaces version history limit has been exceeded</remarks>
public string? referencedObject { get; init; }
public string? sourceApplication { get; init; }
}
@@ -0,0 +1,26 @@
namespace Speckle.Sdk.Api.GraphQL.Models;
public sealed class Workspace
{
public string id { get; init; }
public string name { get; init; }
public string role { get; init; }
public string slug { get; init; }
public string? description { get; init; }
public string? logo { get; init; }
public DateTime? createdAt { get; init; }
public DateTime? updatedAt { get; init; }
public bool? readOnly { get; init; }
public WorkspacePermissionChecks permissions { get; init; }
public WorkspaceCreationState? creationState { get; init; }
}
public sealed class WorkspaceCreationState
{
public bool completed { get; init; }
}
public sealed class WorkspacePermissionChecks
{
public PermissionCheckResult canCreateProject { get; init; }
}
@@ -85,6 +85,7 @@ public sealed class ActiveUserResource
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}"/>
/// <exception cref="SpeckleException">The ActiveUser could not be found (e.g. the client is not authenticated)</exception>
public async Task<ResourceCollection<Project>> GetProjects(
int limit = ServerLimits.DEFAULT_PAGINATION_REQUEST,
string? cursor = null,
@@ -135,7 +136,7 @@ public sealed class ActiveUserResource
if (response.data is null)
{
throw new SpeckleGraphQLException("GraphQL response indicated that the ActiveUser could not be found");
throw new SpeckleException("GraphQL response indicated that the ActiveUser could not be found");
}
return response.data.data;
@@ -199,8 +200,165 @@ public sealed class ActiveUserResource
return response.data.data;
}
/// <inheritdoc cref="GetProjectInvites"/>
[Obsolete($"Renamed to {nameof(GetProjectInvites)}")]
public async Task<List<PendingStreamCollaborator>> ProjectInvites(CancellationToken cancellationToken = default) =>
await GetProjectInvites(cancellationToken).ConfigureAwait(false);
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}"/>
/// <exception cref="SpeckleException">The ActiveUser could not be found (e.g. the client is not authenticated)</exception>
public async Task<PermissionCheckResult> CanCreatePersonalProjects(CancellationToken cancellationToken = default)
{
//language=graphql
const string QUERY = """
query CanCreatePersonalProject {
data:activeUser {
data:permissions {
data:canCreatePersonalProject {
authorized
code
message
}
}
}
}
""";
var request = new GraphQLRequest { Query = QUERY };
var response = await _client
.ExecuteGraphQLRequest<NullableResponse<RequiredResponse<RequiredResponse<PermissionCheckResult>>?>>(
request,
cancellationToken
)
.ConfigureAwait(false);
if (response.data is null)
{
throw new SpeckleException("GraphQL response indicated that the ActiveUser could not be found");
}
return response.data.data.data;
}
/// <remarks>This feature is only available on Workspace enabled servers (e.g. app.speckle.systems)</remarks>
/// <param name="limit"></param>
/// <param name="cursor"></param>
/// <param name="filter"></param>
/// <returns></returns>
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}"/>
/// <exception cref="SpeckleException">The ActiveUser could not be found (e.g. the client is not authenticated)</exception>
public async Task<ResourceCollection<Workspace>> GetWorkspaces(
int limit = ServerLimits.DEFAULT_PAGINATION_REQUEST,
string? cursor = null,
UserWorkspacesFilter? filter = null,
CancellationToken cancellationToken = default
)
{
//language=graphql
const string QUERY = """
query ActiveUser($limit: Int!, $cursor: String, $filter: UserWorkspacesFilter) {
data:activeUser {
data:workspaces(limit: $limit, cursor: $cursor, filter: $filter) {
cursor
totalCount
items {
id
name
role
slug
logo
createdAt
updatedAt
readOnly
description
creationState
{
completed
}
permissions {
canCreateProject {
authorized
code
message
}
}
}
}
}
}
""";
var request = new GraphQLRequest
{
Query = QUERY,
Variables = new
{
limit,
cursor,
filter,
},
};
var response = await _client
.ExecuteGraphQLRequest<NullableResponse<RequiredResponse<ResourceCollection<Workspace>>?>>(
request,
cancellationToken
)
.ConfigureAwait(false);
if (response.data is null)
{
throw new SpeckleException("GraphQL response indicated that the ActiveUser could not be found");
}
return response.data.data;
}
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}"/>
/// <exception cref="SpeckleException">The ActiveUser could not be found (e.g. the client is not authenticated)</exception>
public async Task<Workspace?> GetActiveWorkspace(CancellationToken cancellationToken = default)
{
//language=graphql
const string QUERY = """
query ActiveUser {
data:activeUser {
data:activeWorkspace {
id
name
role
slug
logo
createdAt
updatedAt
readOnly
description
creationState
{
completed
}
permissions {
canCreateProject {
authorized
code
message
}
}
}
}
}
""";
var request = new GraphQLRequest { Query = QUERY };
var response = await _client
.ExecuteGraphQLRequest<NullableResponse<NullableResponse<Workspace?>?>>(request, cancellationToken)
.ConfigureAwait(false);
if (response.data is null)
{
throw new SpeckleException("GraphQL response indicated that the ActiveUser could not be found");
}
return response.data.data;
}
}
@@ -47,6 +47,52 @@ public sealed class ProjectResource
return response.data;
}
/// <param name="projectId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}"/>
public async Task<ProjectPermissionChecks> GetPermissions(
string projectId,
CancellationToken cancellationToken = default
)
{
//language=graphql
const string QUERY = """
query Project($projectId: String!) {
data:project(id: $projectId) {
data:permissions {
canCreateModel {
authorized
code
message
}
canDelete {
authorized
code
message
}
canLoad {
authorized
code
message
}
canPublish {
authorized
code
message
}
}
}
}
""";
GraphQLRequest request = new() { Query = QUERY, Variables = new { projectId } };
var response = await _client
.ExecuteGraphQLRequest<RequiredResponse<RequiredResponse<ProjectPermissionChecks>>>(request, cancellationToken)
.ConfigureAwait(false);
return response.data.data;
}
/// <param name="projectId"></param>
/// <param name="modelsLimit">Max number of models to fetch</param>
/// <param name="modelsCursor">Optional cursor for pagination</param>
@@ -186,6 +232,10 @@ public sealed class ProjectResource
return response.data;
}
/// <summary>
/// Creates a non-workspace project (aka Personal Project)<br/>
/// See <see cref="ActiveUserResource.CanCreatePersonalProjects"/> to see if the user has permission
/// </summary>
/// <param name="input"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
@@ -219,6 +269,49 @@ public sealed class ProjectResource
return response.data.data;
}
/// <summary>
/// Creates a workspace project.<br/>
/// This feature is only supported on Workspace Enabled Servers (e.g. app.speckle.systems)
/// See <see cref="ActiveUserResource.CanCreatePersonalProjects"/> to see if the user has permission
/// </summary>
/// <param name="input"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}"/>
public async Task<Project> CreateInWorkspace(
WorkspaceProjectCreateInput input,
CancellationToken cancellationToken = default
)
{
//language=graphql
const string QUERY = """
mutation WorkspaceProjectCreate($input: WorkspaceProjectCreateInput!) {
data:workspaceMutations {
data:projects {
data:create(input: $input) {
id
name
description
visibility
allowPublicComments
role
createdAt
updatedAt
sourceApps
workspaceId
}
}
}
}
""";
GraphQLRequest request = new() { Query = QUERY, Variables = new { input } };
var response = await _client
.ExecuteGraphQLRequest<RequiredResponse<RequiredResponse<RequiredResponse<Project>>>>(request, cancellationToken)
.ConfigureAwait(false);
return response.data.data.data;
}
/// <param name="input"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
@@ -0,0 +1,120 @@
using GraphQL;
using Speckle.Sdk.Api.GraphQL.Inputs;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Api.GraphQL.Models.Responses;
namespace Speckle.Sdk.Api.GraphQL.Resources;
public sealed class WorkspaceResource
{
private readonly ISpeckleGraphQLClient _client;
internal WorkspaceResource(ISpeckleGraphQLClient client)
{
_client = client;
}
/// <param name="workspaceId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}"/>
public async Task<Workspace> Get(string workspaceId, CancellationToken cancellationToken = default)
{
//language=graphql
const string QUERY = """
query WorkspaceGet($workspaceId: String!) {
data:workspace(id: $workspaceId) {
id
name
role
slug
logo
createdAt
updatedAt
readOnly
description
creationState
{
completed
}
permissions {
canCreateProject {
authorized
code
message
}
}
}
}
""";
var request = new GraphQLRequest { Query = QUERY, Variables = new { workspaceId } };
var response = await _client
.ExecuteGraphQLRequest<RequiredResponse<Workspace>>(request, cancellationToken)
.ConfigureAwait(false);
return response.data;
}
/// <param name="workspaceId"></param>
/// <param name="limit">Max number of projects to fetch</param>
/// <param name="cursor">Optional cursor for pagination</param>
/// <param name="filter"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}"/>
/// <see cref="Get"/>
public async Task<ResourceCollection<Project>> GetProjects(
string workspaceId,
int limit = ServerLimits.DEFAULT_PAGINATION_REQUEST,
string? cursor = null,
WorkspaceProjectsFilter? filter = null,
CancellationToken cancellationToken = default
)
{
//language=graphql
const string QUERY = """
query Workspace($workspaceId: String!, $limit: Int!, $cursor: String, $filter: WorkspaceProjectsFilter) {
data:workspace(id: $workspaceId) {
data:projects(limit: $limit, cursor: $cursor, filter: $filter) {
cursor
items {
allowPublicComments
createdAt
description
id
name
role
sourceApps
updatedAt
visibility
workspaceId
}
totalCount
}
}
}
""";
var request = new GraphQLRequest
{
Query = QUERY,
Variables = new
{
workspaceId,
limit,
cursor,
filter,
},
};
var response = await _client
.ExecuteGraphQLRequest<RequiredResponse<RequiredResponse<ResourceCollection<Project>>>>(
request,
cancellationToken
)
.ConfigureAwait(false);
return response.data.data;
}
}
@@ -29,7 +29,7 @@ public partial class Operations
metricsFactory.CreateCounter<long>("Receive").Add(1);
receiveActivity?.SetTag("objectId", objectId);
var process = serializeProcessFactory.CreateDeserializeProcess(
var process = deserializeProcessFactory.CreateDeserializeProcess(
url,
streamId,
authorizationToken,
+2 -1
View File
@@ -15,5 +15,6 @@ public partial class Operations(
ILogger<Operations> logger,
ISdkActivityFactory activityFactory,
ISdkMetricsFactory metricsFactory,
ISerializeProcessFactory serializeProcessFactory
ISerializeProcessFactory serializeProcessFactory,
IDeserializeProcessFactory deserializeProcessFactory
) : IOperations;
+4
View File
@@ -0,0 +1,4 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Speckle.Objects.Tests.Unit")]
[assembly: InternalsVisibleTo("Speckle.Sdk.Tests.Performance")]
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Text;
using Speckle.Newtonsoft.Json;
using Speckle.Sdk.Dependencies;
@@ -5,6 +6,8 @@ using Speckle.Sdk.Serialisation;
namespace Speckle.Sdk.Helpers;
//just a wrapper around a lot of newtonsoft overloads
[ExcludeFromCodeCoverage]
public sealed class SerializerIdWriter : JsonWriter
{
private readonly JsonWriter _jsonWriter;
@@ -15,10 +15,15 @@ public class SpeckleHttp(ILogger<SpeckleHttp> logger, ISpeckleHttpClientHandlerF
/// <param name="uri">The URI that should be pinged</param>
/// <exception cref="System.Net.Http.HttpRequestException">Request to <paramref name="uri"/> failed</exception>
public async Task<HttpResponseMessage> HttpPing(Uri uri)
{
using var httpClient = CreateHttpClient();
return await HttpPing(uri, httpClient).ConfigureAwait(false);
}
public async Task<HttpResponseMessage> HttpPing(Uri uri, HttpClient httpClient)
{
try
{
using var httpClient = CreateHttpClient();
HttpResponseMessage response = await httpClient.GetAsync(uri).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
logger.LogInformation("Successfully pinged {uri}", uri);
-63
View File
@@ -1,63 +0,0 @@
namespace Speckle.Sdk.Host;
[AttributeUsage(AttributeTargets.Constructor)]
public sealed class SchemaInfoAttribute : Attribute
{
public SchemaInfoAttribute(string name, string description)
: this(name, description, null, null) { }
public SchemaInfoAttribute(string name, string description, string? category, string? subcategory)
{
Name = name;
Description = description;
Category = category;
Subcategory = subcategory;
}
public string? Subcategory { get; }
public string? Category { get; }
public string Description { get; }
public string Name { get; }
}
[AttributeUsage(AttributeTargets.Constructor)]
public sealed class SchemaDeprecatedAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class SchemaParamInfoAttribute : Attribute
{
public SchemaParamInfoAttribute(string description)
{
Description = description;
}
public string Description { get; }
}
/// <summary>
/// Used to indicate which is the main input parameter of the schema builder component. Schema info will be attached to this object.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class SchemaMainParamAttribute : Attribute { }
// TODO: this could be nuked, as it's only used to hide props on Base,
// which we might want to expose anyways...
/// <summary>
/// Used to ignore properties from expand objects etc
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public sealed class SchemaIgnoreAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Method)]
public sealed class SchemaComputedAttribute : Attribute
{
public SchemaComputedAttribute(string name)
{
Name = name;
}
public string Name { get; }
}
-23
View File
@@ -1,23 +0,0 @@
namespace Speckle.Sdk.Host;
public enum HostAppVersion
{
v3,
v6,
v7,
v8,
v2019,
v2020,
v2021,
v2022,
v2023,
v2024,
v2025,
v21,
v22,
v25,
v26,
v715,
v716,
v717,
}
-13
View File
@@ -1,13 +0,0 @@
namespace Speckle.Sdk.Host;
public readonly struct HostApplication
{
public string Name { get; }
public string Slug { get; }
public HostApplication(string name, string slug)
{
Name = name;
Slug = slug;
}
}
-220
View File
@@ -1,220 +0,0 @@
using System.Diagnostics.Contracts;
namespace Speckle.Sdk.Host;
/// <summary>
/// List of Host Applications - their slugs should match our ghost tags and ci/cd slugs
/// </summary>
public static class HostApplications
{
public static string GetVersion(HostAppVersion version) => version.ToString().TrimStart('v');
public static readonly HostApplication Rhino = new("Rhino", "rhino"),
Grasshopper = new("Grasshopper", "grasshopper"),
Revit = new("Revit", "revit"),
Dynamo = new("Dynamo", "dynamo"),
Unity = new("Unity", "unity"),
GSA = new("GSA", "gsa"),
Civil = new("Civil 3D", "civil3d"),
Civil3D = new("Civil 3D", "civil3d"),
AutoCAD = new("AutoCAD", "autocad"),
MicroStation = new("MicroStation", "microstation"),
OpenRoads = new("OpenRoads", "openroads"),
OpenRail = new("OpenRail", "openrail"),
OpenBuildings = new("OpenBuildings", "openbuildings"),
ETABS = new("ETABS", "etabs"),
SAP2000 = new("SAP2000", "sap2000"),
CSiBridge = new("CSiBridge", "csibridge"),
SAFE = new("SAFE", "safe"),
TeklaStructures = new("Tekla Structures", "teklastructures"),
Dxf = new("DXF Converter", "dxf"),
Excel = new("Excel", "excel"),
Unreal = new("Unreal", "unreal"),
PowerBI = new("Power BI", "powerbi"),
Blender = new("Blender", "blender"),
QGIS = new("QGIS", "qgis"),
ArcGIS = new("ArcGIS", "arcgis"),
SketchUp = new("SketchUp", "sketchup"),
Archicad = new("Archicad", "archicad"),
TopSolid = new("TopSolid", "topsolid"),
Python = new("Python", "python"),
NET = new(".NET", "net"),
Navisworks = new("Navisworks", "navisworks"),
AdvanceSteel = new("Advance Steel", "advancesteel"),
Other = new("Other", "other");
/// <summary>
/// Gets a HostApplication form a string. It could be the versioned name or a string coming from a process running.
/// </summary>
/// <param name="appname">String with the name of the app</param>
/// <returns></returns>
[Pure]
public static HostApplication GetHostAppFromString(string? appname)
{
if (appname == null)
{
return Other;
}
appname = appname.ToLowerInvariant().Replace(" ", "");
if (appname.Contains("dynamo"))
{
return Dynamo;
}
if (appname.Contains("revit"))
{
return Revit;
}
if (appname.Contains("autocad"))
{
return AutoCAD;
}
if (appname.Contains("civil3d"))
{
return Civil3D;
}
if (appname.Contains("civil"))
{
return Civil;
}
if (appname.Contains("rhino"))
{
return Rhino;
}
if (appname.Contains("grasshopper"))
{
return Grasshopper;
}
if (appname.Contains("unity"))
{
return Unity;
}
if (appname.Contains("gsa"))
{
return GSA;
}
if (appname.Contains("microstation"))
{
return MicroStation;
}
if (appname.Contains("openroads"))
{
return OpenRoads;
}
if (appname.Contains("openrail"))
{
return OpenRail;
}
if (appname.Contains("openbuildings"))
{
return OpenBuildings;
}
if (appname.Contains("etabs"))
{
return ETABS;
}
if (appname.Contains("sap"))
{
return SAP2000;
}
if (appname.Contains("csibridge"))
{
return CSiBridge;
}
if (appname.Contains("safe"))
{
return SAFE;
}
if (appname.Contains("teklastructures"))
{
return TeklaStructures;
}
if (appname.Contains("dxf"))
{
return Dxf;
}
if (appname.Contains("excel"))
{
return Excel;
}
if (appname.Contains("unreal"))
{
return Unreal;
}
if (appname.Contains("powerbi"))
{
return PowerBI;
}
if (appname.Contains("blender"))
{
return Blender;
}
if (appname.Contains("qgis"))
{
return QGIS;
}
if (appname.Contains("arcgis"))
{
return ArcGIS;
}
if (appname.Contains("sketchup"))
{
return SketchUp;
}
if (appname.Contains("archicad"))
{
return Archicad;
}
if (appname.Contains("topsolid"))
{
return TopSolid;
}
if (appname.Contains("python"))
{
return Python;
}
if (appname.Contains("net"))
{
return NET;
}
if (appname.Contains("navisworks"))
{
return Navisworks;
}
if (appname.Contains("advancesteel"))
{
return AdvanceSteel;
}
return new HostApplication(appname, appname);
}
}
+2 -2
View File
@@ -5,9 +5,9 @@ using Speckle.Sdk.Models;
namespace Speckle.Sdk.Host;
public record LoadedType(string Name, Type Type, List<string> DeprecatedNames);
internal record LoadedType(string Name, Type Type, List<string> DeprecatedNames);
public static class TypeLoader
internal static class TypeLoader
{
private static bool s_initialized;
private static List<LoadedType> s_availableTypes = new();
+1 -8
View File
@@ -28,20 +28,17 @@ public class Base : DynamicBase, ISpeckleObject
/// <summary>
/// A speckle object's id is an unique hash based on its properties. <b>NOTE: this field will be null unless the object was deserialised from a source. Use the <see cref="GetId"/> function to get it.</b>
/// </summary>
[SchemaIgnore]
public virtual string? id { get; set; }
/// <summary>
/// Secondary, ideally host application driven, object identifier.
/// </summary>
[SchemaIgnore]
public string? applicationId { get; set; }
/// <summary>
/// Holds the type information of this speckle object, derived automatically
/// from its assembly name and inheritance.
/// </summary>
[SchemaIgnore]
public virtual string speckle_type
{
get
@@ -210,11 +207,7 @@ public class Base : DynamicBase, ISpeckleObject
myDuplicate.id = id;
myDuplicate.applicationId = applicationId;
foreach (
var kvp in GetMembers(
DynamicBaseMemberType.Instance | DynamicBaseMemberType.Dynamic | DynamicBaseMemberType.SchemaIgnored
)
)
foreach (var kvp in GetMembers())
{
var propertyInfo = type.GetProperty(kvp.Key);
if (propertyInfo is not null && !propertyInfo.CanWrite)
+1 -20
View File
@@ -1,7 +1,6 @@
using System.Dynamic;
using System.Reflection;
using Speckle.Newtonsoft.Json;
using Speckle.Sdk.Common;
using Speckle.Sdk.Host;
namespace Speckle.Sdk.Models;
@@ -233,16 +232,12 @@ public class DynamicBase : DynamicObject, IDynamicMetaObjectProvider
.GetBaseProperties(GetType())
.Where(x =>
{
var hasIgnored = x.IsDefined(typeof(SchemaIgnoreAttribute), true);
var hasObsolete = x.IsDefined(typeof(ObsoleteAttribute), true);
// If obsolete is false and prop has obsolete attr
// OR
// If schemaIgnored is true and prop has schemaIgnore attr
return !(
!includeMembers.HasFlag(DynamicBaseMemberType.SchemaIgnored) && hasIgnored
|| !includeMembers.HasFlag(DynamicBaseMemberType.Obsolete) && hasObsolete
);
return !(!includeMembers.HasFlag(DynamicBaseMemberType.Obsolete) && hasObsolete);
});
foreach (var pi in pinfos)
{
@@ -252,20 +247,6 @@ public class DynamicBase : DynamicObject, IDynamicMetaObjectProvider
}
}
}
if (includeMembers.HasFlag(DynamicBaseMemberType.SchemaComputed))
{
GetType()
.GetMethods()
.Where(e => e.IsDefined(typeof(SchemaComputedAttribute)) && !e.IsDefined(typeof(ObsoleteAttribute)))
.ToList()
.ForEach(e =>
{
var attr = e.GetCustomAttribute<SchemaComputedAttribute>().NotNull();
dic[attr.Name] = e.Invoke(this, null);
});
}
return dic;
}
@@ -1,5 +1,3 @@
using Speckle.Sdk.Host;
namespace Speckle.Sdk.Models;
/// <summary>
@@ -23,23 +21,18 @@ public enum DynamicBaseMemberType
/// </summary>
Obsolete = 4,
/// <summary>
/// The typed members flagged with <see cref="SchemaIgnoreAttribute"/> attribute.
/// </summary>
SchemaIgnored = 8,
/// <summary>
/// The typed methods flagged with TODO:
/// </summary>
SchemaComputed = 16,
/// <summary>
/// All the typed members, including ones with <see cref="ObsoleteAttribute"/> or <see cref="SchemaIgnoreAttribute"/> attributes.
/// All the typed members, including ones with <see cref="ObsoleteAttribute"/> attributes.
/// </summary>
InstanceAll = Instance + Obsolete + SchemaIgnored,
InstanceAll = Instance + Obsolete,
/// <summary>
/// All the members, including dynamic and instance members flagged with <see cref="ObsoleteAttribute"/> or <see cref="SchemaIgnoreAttribute"/> attributes
/// All the members, including dynamic and instance members flagged with <see cref="ObsoleteAttribute"/> attributes
/// </summary>
All = InstanceAll + Dynamic,
}
@@ -72,9 +72,14 @@ public sealed class SqLiteJsonCacheManager : ISqLiteJsonCacheManager
cmd4.ExecuteNonQuery();
}
using (SqliteCommand cmd0 = new("PRAGMA journal_mode='wal';", c))
using (SqliteCommand cmd5 = new("PRAGMA journal_mode='wal';", c))
{
cmd0.ExecuteNonQuery();
cmd5.ExecuteNonQuery();
}
//do this to wait 5 seconds to avoid db lock exceptions, this is 0 by default
using (SqliteCommand cmd6 = new("PRAGMA busy_timeout=5000;", c))
{
cmd6.ExecuteNonQuery();
}
});
}
@@ -0,0 +1,56 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using Speckle.InterfaceGenerator;
using Speckle.Sdk.Serialisation.V2.Receive;
using Speckle.Sdk.SQLite;
using Speckle.Sdk.Transports;
namespace Speckle.Sdk.Serialisation.V2;
[GenerateAutoInterface]
public class DeserializeProcessFactory(
IBaseDeserializer baseDeserializer,
ISqLiteJsonCacheManagerFactory sqLiteJsonCacheManagerFactory,
IServerObjectManagerFactory serverObjectManagerFactory,
ILoggerFactory loggerFactory
) : IDeserializeProcessFactory
{
public IDeserializeProcess CreateDeserializeProcess(
Uri url,
string streamId,
string? authorizationToken,
IProgress<ProgressArgs>? progress,
CancellationToken cancellationToken,
DeserializeProcessOptions? options = null
)
{
var sqLiteJsonCacheManager = sqLiteJsonCacheManagerFactory.CreateFromStream(streamId);
var serverObjectManager = serverObjectManagerFactory.Create(url, streamId, authorizationToken);
return new DeserializeProcess(
sqLiteJsonCacheManager,
serverObjectManager,
progress,
baseDeserializer,
loggerFactory,
cancellationToken,
options
);
}
public IDeserializeProcess CreateDeserializeProcess(
ConcurrentDictionary<Id, Json> jsonCache,
ConcurrentDictionary<string, string> objects,
IProgress<ProgressArgs>? progress,
CancellationToken cancellationToken,
DeserializeProcessOptions? options = null
) =>
new DeserializeProcess(
new MemoryJsonCacheManager(jsonCache),
new MemoryServerObjectManager(objects),
progress,
baseDeserializer,
loggerFactory,
cancellationToken,
options
);
}
@@ -1,68 +0,0 @@
using System.Text;
using Speckle.Sdk.Serialisation.V2.Send;
using Speckle.Sdk.SQLite;
using Speckle.Sdk.Transports;
namespace Speckle.Sdk.Serialisation.V2;
#pragma warning disable CA1063
public class DummySqLiteJsonCacheManager : ISqLiteJsonCacheManager
#pragma warning restore CA1063
{
#pragma warning disable CA1816
#pragma warning disable CA1063
public void Dispose() { }
#pragma warning restore CA1063
#pragma warning restore CA1816
public IReadOnlyCollection<(string, string)> GetAllObjects() => throw new NotImplementedException();
public void DeleteObject(string id) => throw new NotImplementedException();
public string? GetObject(string id) => null;
public void SaveObject(string id, string json) => throw new NotImplementedException();
public void UpdateObject(string id, string json) => throw new NotImplementedException();
public virtual void SaveObjects(IEnumerable<(string id, string json)> items) => throw new NotImplementedException();
public bool HasObject(string objectId) => false;
}
public class DummySendServerObjectManager : IServerObjectManager
{
public IAsyncEnumerable<(string, string)> DownloadObjects(
IReadOnlyCollection<string> objectIds,
IProgress<ProgressArgs>? progress,
CancellationToken cancellationToken
) => throw new NotImplementedException();
public Task<string?> DownloadSingleObject(
string objectId,
IProgress<ProgressArgs>? progress,
CancellationToken cancellationToken
) => throw new NotImplementedException();
public Task<Dictionary<string, bool>> HasObjects(
IReadOnlyCollection<string> objectIds,
CancellationToken cancellationToken
) => throw new NotImplementedException();
public Task UploadObjects(
IReadOnlyList<BaseItem> objects,
bool compressPayloads,
IProgress<ProgressArgs>? progress,
CancellationToken cancellationToken
)
{
long totalBytes = 0;
foreach (var item in objects)
{
totalBytes += Encoding.Default.GetByteCount(item.Json.Value);
}
progress?.Report(new(ProgressEvent.UploadBytes, totalBytes, totalBytes));
return Task.CompletedTask;
}
}
@@ -0,0 +1,37 @@
using System.Collections.Concurrent;
using Speckle.Sdk.SQLite;
namespace Speckle.Sdk.Serialisation.V2;
#pragma warning disable CA1063
public class MemoryJsonCacheManager(ConcurrentDictionary<Id, Json> jsonCache) : ISqLiteJsonCacheManager
#pragma warning restore CA1063
{
public IReadOnlyCollection<(string Id, string Json)> GetAllObjects() =>
jsonCache.Select(x => (x.Key.Value, x.Value.Value)).ToList();
public void DeleteObject(string id) => jsonCache.TryRemove(new Id(id), out _);
public string? GetObject(string id) => jsonCache.TryGetValue(new Id(id), out var json) ? json.Value : null;
public void SaveObject(string id, string json) => jsonCache.TryAdd(new Id(id), new Json(json));
public void UpdateObject(string id, string json) => jsonCache[new Id(id)] = new Json(json);
public virtual void SaveObjects(IEnumerable<(string id, string json)> items)
{
foreach (var (id, json) in items)
{
SaveObject(id, json);
}
}
public bool HasObject(string objectId) => jsonCache.ContainsKey(new Id(objectId));
#pragma warning disable CA1063
#pragma warning disable CA1816
public void Dispose()
#pragma warning restore CA1816
#pragma warning restore CA1063
{ }
}
@@ -0,0 +1,48 @@
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using Speckle.Sdk.Serialisation.V2.Send;
using Speckle.Sdk.Transports;
namespace Speckle.Sdk.Serialisation.V2;
public class MemoryServerObjectManager(ConcurrentDictionary<string, string> objects) : IServerObjectManager
{
public virtual async IAsyncEnumerable<(string, string)> DownloadObjects(
IReadOnlyCollection<string> objectIds,
IProgress<ProgressArgs>? progress,
[EnumeratorCancellation] CancellationToken cancellationToken
)
{
foreach (var item in objects.Where(x => objectIds.Contains(x.Key)))
{
cancellationToken.ThrowIfCancellationRequested();
yield return (item.Key, item.Value);
}
await Task.CompletedTask.ConfigureAwait(false);
}
public virtual Task<string?> DownloadSingleObject(
string objectId,
IProgress<ProgressArgs>? progress,
CancellationToken cancellationToken
) => Task.FromResult(objects.TryGetValue(objectId, out var json) ? json : null);
public virtual Task<Dictionary<string, bool>> HasObjects(
IReadOnlyCollection<string> objectIds,
CancellationToken cancellationToken
) => Task.FromResult(objectIds.ToDictionary(x => x, objects.ContainsKey));
public virtual Task UploadObjects(
IReadOnlyList<BaseItem> objectToUpload,
bool compressPayloads,
IProgress<ProgressArgs>? progress,
CancellationToken cancellationToken
)
{
foreach (BaseItem baseItem in objectToUpload)
{
objects.TryAdd(baseItem.Id.Value, baseItem.Json.Value);
}
return Task.CompletedTask;
}
}
@@ -6,8 +6,7 @@ namespace Speckle.Sdk.Serialisation.V2;
public sealed class PriorityScheduler(
ILogger<PriorityScheduler> logger,
ThreadPriority priority,
int maximumConcurrencyLevel,
CancellationToken cancellationToken
int maximumConcurrencyLevel
) : TaskScheduler, IAsyncDisposable
{
private readonly BlockingCollection<Task> _tasks = new();
@@ -56,7 +55,7 @@ public sealed class PriorityScheduler(
while (true)
{
//we're done so leave
if (_tasks.IsCompleted || cancellationToken.IsCancellationRequested)
if (_tasks.IsCompleted)
{
break;
}
@@ -66,11 +65,6 @@ public sealed class PriorityScheduler(
{
break;
}
//cancelled just leave
if (cancellationToken.IsCancellationRequested)
{
break;
}
//didn't get a task but just timed out so continue
if (!success)
{
@@ -54,8 +54,7 @@ public sealed class DeserializeProcess(
private readonly PriorityScheduler _belowNormal = new(
loggerFactory.CreateLogger<PriorityScheduler>(),
ThreadPriority.BelowNormal,
Environment.ProcessorCount * 2,
cancellationToken
Environment.ProcessorCount * 2
);
private readonly DeserializeProcessOptions _options = options ?? new();
@@ -146,7 +146,7 @@ public sealed class ObjectLoader(
cancellationToken.ThrowIfCancellationRequested();
if (Exception is not null)
{
throw new SpeckleException("Error while sending", Exception);
throw new SpeckleException("Error while loading", Exception);
}
}
}
@@ -2,8 +2,7 @@ using System.Text;
namespace Speckle.Sdk.Serialisation.V2.Send;
public readonly record struct BaseItem(Id Id, Json Json, bool NeedsStorage, Dictionary<Id, int>? Closures)
: IHasByteSize
public sealed record BaseItem(Id Id, Json Json, bool NeedsStorage, Dictionary<Id, int>? Closures) : IHasByteSize
{
public int ByteSize { get; } = Encoding.UTF8.GetByteCount(Json.Value);
@@ -13,7 +12,7 @@ public readonly record struct BaseItem(Id Id, Json Json, bool NeedsStorage, Dict
{
return false;
}
return string.Equals(Id.Value, other.Value.Id.Value, StringComparison.OrdinalIgnoreCase);
return string.Equals(Id.Value, other.Id.Value, StringComparison.OrdinalIgnoreCase);
}
public override int GetHashCode() => Id.GetHashCode();
@@ -1,47 +0,0 @@
using System.Collections;
using System.Diagnostics.CodeAnalysis;
namespace Speckle.Sdk.Serialisation.V2.Send;
public class EmptyDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => throw new NotImplementedException();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void Add(KeyValuePair<TKey, TValue> item) { }
public void Clear() => throw new NotImplementedException();
public bool Contains(KeyValuePair<TKey, TValue> item) => false;
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) => throw new NotImplementedException();
public bool Remove(KeyValuePair<TKey, TValue> item) => false;
public int Count => 0;
public bool IsReadOnly => false;
public void Add(TKey key, TValue value) { }
public bool ContainsKey(TKey key) => false;
public bool Remove(TKey key) => false;
public bool TryGetValue(TKey key, [UnscopedRef] out TValue value)
{
value = default!;
return false;
}
public TValue this[TKey key]
{
#pragma warning disable CA1065
get => throw new NotImplementedException();
#pragma warning restore CA1065
set { }
}
public ICollection<TKey> Keys { get; }
public ICollection<TValue> Values { get; }
}
@@ -0,0 +1,126 @@
using Microsoft.Extensions.Logging;
using Speckle.Sdk.Dependencies;
using Speckle.Sdk.Dependencies.Serialization;
using Speckle.Sdk.SQLite;
using Speckle.Sdk.Transports;
namespace Speckle.Sdk.Serialisation.V2.Send;
public interface IObjectSaver : IDisposable
{
Exception? Exception { get; set; }
Task Start(int? maxParallelism, int? httpBatchSize, int? cacheBatchSize, CancellationToken cancellationToken);
void DoneTraversing();
Task DoneSaving();
Task SaveAsync(BaseItem item);
}
public sealed class ObjectSaver(
IProgress<ProgressArgs>? progress,
ISqLiteJsonCacheManager sqLiteJsonCacheManager,
IServerObjectManager serverObjectManager,
ILogger<ObjectSaver> logger,
CancellationToken cancellationToken,
#pragma warning disable CS9107
#pragma warning disable CA2254
SerializeProcessOptions? options = null
) : ChannelSaver<BaseItem>, IObjectSaver
#pragma warning restore CA2254
#pragma warning restore CS9107
{
private readonly CancellationTokenSource _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(
cancellationToken
);
private readonly SerializeProcessOptions _options = options ?? new();
private long _uploaded;
private long _cached;
private long _objectsSerialized;
protected override async Task SendToServerInternal(Batch<BaseItem> batch)
{
if (_cancellationTokenSource.IsCancellationRequested)
{
return;
}
try
{
if (!_options.SkipServer && batch.Items.Count != 0)
{
var objectBatch = batch.Items.Distinct().ToList();
var hasObjects = await serverObjectManager
.HasObjects(objectBatch.Select(x => x.Id.Value).Freeze(), _cancellationTokenSource.Token)
.ConfigureAwait(false);
objectBatch = batch.Items.Where(x => !hasObjects[x.Id.Value]).ToList();
if (objectBatch.Count != 0)
{
await serverObjectManager
.UploadObjects(objectBatch, true, progress, _cancellationTokenSource.Token)
.ConfigureAwait(false);
Interlocked.Add(ref _uploaded, batch.Items.Count);
}
progress?.Report(new(ProgressEvent.UploadedObjects, _uploaded, null));
}
}
catch (OperationCanceledException)
{
_cancellationTokenSource.Cancel();
}
#pragma warning disable CA1031
catch (Exception e)
#pragma warning restore CA1031
{
RecordException(e);
}
}
public async Task SaveAsync(BaseItem item)
{
Interlocked.Increment(ref _objectsSerialized);
await SaveAsync(item, _cancellationTokenSource.Token).ConfigureAwait(false);
}
public override void SaveToCache(List<BaseItem> batch)
{
if (_cancellationTokenSource.IsCancellationRequested)
{
return;
}
try
{
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));
}
}
catch (OperationCanceledException)
{
_cancellationTokenSource.Cancel();
}
#pragma warning disable CA1031
catch (Exception e)
#pragma warning restore CA1031
{
RecordException(e);
}
}
private void RecordException(Exception e)
{
//order here matters
logger.LogError(e, "Error in SDK");
Exception = e;
_cancellationTokenSource.Cancel();
}
public void Dispose()
{
_cancellationTokenSource.Dispose();
sqLiteJsonCacheManager.Dispose();
}
}
@@ -4,9 +4,7 @@ using Microsoft.Extensions.Logging;
using Speckle.InterfaceGenerator;
using Speckle.Sdk.Common;
using Speckle.Sdk.Dependencies;
using Speckle.Sdk.Dependencies.Serialization;
using Speckle.Sdk.Models;
using Speckle.Sdk.SQLite;
using Speckle.Sdk.Transports;
namespace Speckle.Sdk.Serialisation.V2.Send;
@@ -16,7 +14,12 @@ public record SerializeProcessOptions(
bool SkipCacheWrite = false,
bool SkipServer = false,
bool SkipFindTotalObjects = false
);
)
{
public int? MaxHttpSendSize { get; set; }
public int? MaxCacheSize { get; set; }
public int? MaxParallelism { get; set; }
}
public readonly record struct SerializeProcessResults(
string RootId,
@@ -28,28 +31,27 @@ public partial interface ISerializeProcess : IAsyncDisposable;
[GenerateAutoInterface]
public sealed class SerializeProcess(
IProgress<ProgressArgs>? progress,
ISqLiteJsonCacheManager sqLiteJsonCacheManager,
IServerObjectManager serverObjectManager,
IObjectSaver objectSaver,
IBaseChildFinder baseChildFinder,
IBaseSerializer baseSerializer,
ILoggerFactory loggerFactory,
CancellationToken cancellationToken,
SerializeProcessOptions? options = null
#pragma warning disable CS9107
#pragma warning disable CA2254
)
: ChannelSaver<BaseItem>(x => loggerFactory.CreateLogger<SerializeProcess>().LogWarning(x), cancellationToken),
ISerializeProcess
#pragma warning restore CA2254
#pragma warning restore CS9107
) : ISerializeProcess
{
private static readonly Dictionary<Id, NodeInfo> EMPTY_CLOSURES = new();
private readonly CancellationTokenSource _processSource = CancellationTokenSource.CreateLinkedTokenSource(
cancellationToken
);
private readonly ILogger<SerializeProcess> _logger = loggerFactory.CreateLogger<SerializeProcess>();
//async dispose
[SuppressMessage("Usage", "CA2213:Disposable fields should be disposed")]
private readonly PriorityScheduler _highest = new(
loggerFactory.CreateLogger<PriorityScheduler>(),
ThreadPriority.Highest,
2,
cancellationToken
2
);
//async dispose
@@ -57,12 +59,9 @@ public sealed class SerializeProcess(
private readonly PriorityScheduler _belowNormal = new(
loggerFactory.CreateLogger<PriorityScheduler>(),
ThreadPriority.BelowNormal,
Environment.ProcessorCount * 2,
cancellationToken
Environment.ProcessorCount * 2
);
private readonly SerializeProcessOptions _options = options ?? new();
private readonly ILogger<SerializeProcess> _logger = loggerFactory.CreateLogger<SerializeProcess>();
private readonly Pool<Dictionary<Id, NodeInfo>> _currentClosurePool = Pools.CreateDictionaryPool<Id, NodeInfo>();
private readonly Pool<ConcurrentDictionary<Id, NodeInfo>> _childClosurePool = Pools.CreateConcurrentDictionaryPool<
@@ -75,26 +74,24 @@ public sealed class SerializeProcess(
private long _objectsSerialized;
private long _uploaded;
private long _cached;
[AutoInterfaceIgnore]
public async ValueTask DisposeAsync()
{
await WaitForSchedulerCompletion().ConfigureAwait(false);
await _highest.DisposeAsync().ConfigureAwait(false);
await _belowNormal.DisposeAsync().ConfigureAwait(false);
sqLiteJsonCacheManager.Dispose();
objectSaver.Dispose();
_processSource.Dispose();
}
public void ThrowIfFailed()
private void ThrowIfFailed()
{
//always check for cancellation first
cancellationToken.ThrowIfCancellationRequested();
if (Exception is not null)
//order here matters...null with cancellation means a user did it, otherwise it's a real Exception
if (objectSaver.Exception is not null)
{
throw new SpeckleException("Error while sending", Exception);
throw new SpeckleException("Error while sending", objectSaver.Exception);
}
_processSource.Token.ThrowIfCancellationRequested();
}
private async Task WaitForSchedulerCompletion()
@@ -107,30 +104,36 @@ public sealed class SerializeProcess(
{
try
{
var channelTask = Start();
var channelTask = objectSaver.Start(
options?.MaxParallelism,
options?.MaxHttpSendSize,
options?.MaxCacheSize,
_processSource.Token
);
var findTotalObjectsTask = Task.CompletedTask;
if (!_options.SkipFindTotalObjects)
{
ThrowIfFailed();
findTotalObjectsTask = Task.Factory.StartNew(
() => TraverseTotal(root),
cancellationToken,
_processSource.Token,
TaskCreationOptions.AttachedToParent | TaskCreationOptions.PreferFairness,
_highest
);
}
await Traverse(root).ConfigureAwait(false);
DoneTraversing();
ThrowIfFailed();
objectSaver.DoneTraversing();
await Task.WhenAll(findTotalObjectsTask, channelTask).ConfigureAwait(false);
ThrowIfFailed();
await DoneSaving().ConfigureAwait(false);
await objectSaver.DoneSaving().ConfigureAwait(false);
ThrowIfFailed();
await WaitForSchedulerCompletion().ConfigureAwait(false);
ThrowIfFailed();
return new(root.id.NotNull(), baseSerializer.ObjectReferences.Freeze());
}
catch (TaskCanceledException)
catch (OperationCanceledException)
{
ThrowIfFailed();
throw;
@@ -139,9 +142,13 @@ public sealed class SerializeProcess(
private void TraverseTotal(Base obj)
{
if (_processSource.Token.IsCancellationRequested)
{
return;
}
foreach (var child in baseChildFinder.GetChildren(obj))
{
_objectsFound++;
Interlocked.Increment(ref _objectsFound);
progress?.Report(new(ProgressEvent.FindingChildren, _objectsFound, null));
TraverseTotal(child);
}
@@ -149,114 +156,142 @@ public sealed class SerializeProcess(
private async Task<Dictionary<Id, NodeInfo>> Traverse(Base obj)
{
var tasks = new List<Task<Dictionary<Id, NodeInfo>>>();
foreach (var child in baseChildFinder.GetChildren(obj))
if (_processSource.Token.IsCancellationRequested)
{
// tmp is necessary because of the way closures close over loop variables
var tmp = child;
cancellationToken.ThrowIfCancellationRequested();
var t = Task
.Factory.StartNew(
async () => await Traverse(tmp).ConfigureAwait(false),
cancellationToken,
TaskCreationOptions.AttachedToParent | TaskCreationOptions.PreferFairness,
_belowNormal
)
.Unwrap();
tasks.Add(t);
return EMPTY_CLOSURES;
}
Dictionary<Id, NodeInfo>[] taskClosures = [];
if (tasks.Count > 0)
{
taskClosures = await Task.WhenAll(tasks).ConfigureAwait(false);
}
var childClosures = _childClosurePool.Get();
foreach (var childClosure in taskClosures)
{
foreach (var kvp in childClosure)
{
childClosures[kvp.Key] = kvp.Value;
}
_currentClosurePool.Return(childClosure);
}
var items = baseSerializer.Serialise(obj, childClosures, _options.SkipCacheRead, cancellationToken);
var currentClosures = _currentClosurePool.Get();
Interlocked.Increment(ref _objectCount);
progress?.Report(new(ProgressEvent.FromCacheOrSerialized, _objectCount, Math.Max(_objectCount, _objectsFound)));
foreach (var item in items)
{
if (item.NeedsStorage)
{
Interlocked.Increment(ref _objectsSerialized);
await Save(item).ConfigureAwait(false);
}
if (!currentClosures.ContainsKey(item.Id))
{
currentClosures.Add(item.Id, new NodeInfo(item.Json, item.Closures));
}
}
_childClosurePool.Return(childClosures);
return currentClosures;
}
protected override async Task SendToServerInternal(Batch<BaseItem> batch)
{
try
{
if (!_options.SkipServer && batch.Items.Count != 0)
var tasks = new List<Task<Dictionary<Id, NodeInfo>>>();
foreach (var child in baseChildFinder.GetChildren(obj))
{
var objectBatch = batch.Items.Distinct().ToList();
var hasObjects = await serverObjectManager
.HasObjects(objectBatch.Select(x => x.Id.Value).Freeze(), cancellationToken)
.ConfigureAwait(false);
objectBatch = batch.Items.Where(x => !hasObjects[x.Id.Value]).ToList();
if (objectBatch.Count != 0)
// tmp is necessary because of the way closures close over loop variables
var tmp = child;
if (_processSource.Token.IsCancellationRequested)
{
await serverObjectManager.UploadObjects(objectBatch, true, progress, cancellationToken).ConfigureAwait(false);
Interlocked.Exchange(ref _uploaded, _uploaded + batch.Items.Count);
return EMPTY_CLOSURES;
}
var t = Task
.Factory.StartNew(
// ReSharper disable once AccessToDisposedClosure
// don't need to capture here
async () => await Traverse(tmp).ConfigureAwait(false),
_processSource.Token,
TaskCreationOptions.AttachedToParent | TaskCreationOptions.PreferFairness,
_belowNormal
)
.Unwrap();
tasks.Add(t);
}
if (_processSource.Token.IsCancellationRequested)
{
return EMPTY_CLOSURES;
}
List<Dictionary<Id, NodeInfo>> taskClosures = new();
if (tasks.Count > 0)
{
var currentTasks = tasks.ToList();
do
{
//grab when any Task is done and see if we're cancelling
var t = await Task.WhenAny(currentTasks).ConfigureAwait(false);
if (t.IsCanceled)
{
return EMPTY_CLOSURES;
}
if (t.IsFaulted)
{
if (t.Exception is not null)
{
RecordException(t.Exception);
}
return EMPTY_CLOSURES;
}
taskClosures.Add(t.Result);
currentTasks.Remove(t);
} while (currentTasks.Count > 0);
}
if (_processSource.Token.IsCancellationRequested)
{
return EMPTY_CLOSURES;
}
var childClosures = _childClosurePool.Get();
foreach (var childClosure in taskClosures)
{
foreach (var kvp in childClosure)
{
childClosures[kvp.Key] = kvp.Value;
}
progress?.Report(new(ProgressEvent.UploadedObjects, _uploaded, null));
_currentClosurePool.Return(childClosure);
}
if (_processSource.Token.IsCancellationRequested)
{
return EMPTY_CLOSURES;
}
var items = baseSerializer.Serialise(obj, childClosures, _options.SkipCacheRead, _processSource.Token);
if (_processSource.Token.IsCancellationRequested)
{
return EMPTY_CLOSURES;
}
var currentClosures = _currentClosurePool.Get();
try
{
Interlocked.Increment(ref _objectCount);
progress?.Report(new(ProgressEvent.FromCacheOrSerialized, _objectCount, Math.Max(_objectCount, _objectsFound)));
foreach (var item in items)
{
if (item.NeedsStorage)
{
if (_processSource.Token.IsCancellationRequested)
{
return EMPTY_CLOSURES;
}
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;
}
catch (OperationCanceledException)
{
throw;
return EMPTY_CLOSURES;
}
#pragma warning disable CA1031
catch (Exception e)
#pragma warning restore CA1031
{
_logger.LogError(e, "Error sending objects to server");
throw;
RecordException(e);
return EMPTY_CLOSURES;
}
}
public override void SaveToCache(List<BaseItem> batch)
private void RecordException(Exception e)
{
try
{
if (!_options.SkipCacheWrite && batch.Count != 0)
{
sqLiteJsonCacheManager.SaveObjects(batch.Select(x => (x.Id.Value, x.Json.Value)));
Interlocked.Exchange(ref _cached, _cached + batch.Count);
progress?.Report(new(ProgressEvent.CachedToLocal, _cached, _objectsSerialized));
}
}
catch (OperationCanceledException)
{
throw;
}
#pragma warning disable CA1031
catch (Exception e)
#pragma warning restore CA1031
{
_logger.LogError(e, "Error sending objects to server");
throw;
}
//order here matters
_logger.LogError(e, "Error in SDK");
objectSaver.Exception = e;
_processSource.Cancel();
}
}
@@ -1,35 +1,16 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using Speckle.Sdk.Serialisation.V2.Receive;
using Speckle.InterfaceGenerator;
using Speckle.Sdk.Serialisation.V2.Send;
using Speckle.Sdk.SQLite;
using Speckle.Sdk.Transports;
namespace Speckle.Sdk.Serialisation.V2;
public interface ISerializeProcessFactory
{
ISerializeProcess CreateSerializeProcess(
Uri url,
string streamId,
string? authorizationToken,
IProgress<ProgressArgs>? progress,
CancellationToken cancellationToken,
SerializeProcessOptions? options = null
);
IDeserializeProcess CreateDeserializeProcess(
Uri url,
string streamId,
string? authorizationToken,
IProgress<ProgressArgs>? progress,
CancellationToken cancellationToken,
DeserializeProcessOptions? options = null
);
}
[GenerateAutoInterface]
public class SerializeProcessFactory(
IBaseChildFinder baseChildFinder,
IObjectSerializerFactory objectSerializerFactory,
IBaseDeserializer baseDeserializer,
ISqLiteJsonCacheManagerFactory sqLiteJsonCacheManagerFactory,
IServerObjectManagerFactory serverObjectManagerFactory,
ILoggerFactory loggerFactory
@@ -46,34 +27,54 @@ public class SerializeProcessFactory(
{
var sqLiteJsonCacheManager = sqLiteJsonCacheManagerFactory.CreateFromStream(streamId);
var serverObjectManager = serverObjectManagerFactory.Create(url, streamId, authorizationToken);
return new SerializeProcess(
return CreateSerializeProcess(sqLiteJsonCacheManager, serverObjectManager, progress, cancellationToken, options);
}
public ISerializeProcess CreateSerializeProcess(
ISqLiteJsonCacheManager sqLiteJsonCacheManager,
IServerObjectManager serverObjectManager,
IProgress<ProgressArgs>? progress,
CancellationToken cancellationToken,
SerializeProcessOptions? options = null
) =>
new SerializeProcess(
progress,
sqLiteJsonCacheManager,
serverObjectManager,
new ObjectSaver(
progress,
sqLiteJsonCacheManager,
serverObjectManager,
loggerFactory.CreateLogger<ObjectSaver>(),
cancellationToken
),
baseChildFinder,
new BaseSerializer(sqLiteJsonCacheManager, objectSerializerFactory),
loggerFactory,
cancellationToken,
options
);
}
public IDeserializeProcess CreateDeserializeProcess(
Uri url,
string streamId,
string? authorizationToken,
public ISerializeProcess CreateSerializeProcess(
ConcurrentDictionary<Id, Json> jsonCache,
ConcurrentDictionary<string, string> objects,
IProgress<ProgressArgs>? progress,
CancellationToken cancellationToken,
DeserializeProcessOptions? options = null
SerializeProcessOptions? options = null
)
{
var sqLiteJsonCacheManager = sqLiteJsonCacheManagerFactory.CreateFromStream(streamId);
var serverObjectManager = serverObjectManagerFactory.Create(url, streamId, authorizationToken);
return new DeserializeProcess(
sqLiteJsonCacheManager,
serverObjectManager,
#pragma warning disable CA2000
var memoryJsonCacheManager = new MemoryJsonCacheManager(jsonCache);
#pragma warning restore CA2000
return new SerializeProcess(
progress,
baseDeserializer,
new ObjectSaver(
progress,
memoryJsonCacheManager,
new MemoryServerObjectManager(objects),
loggerFactory.CreateLogger<ObjectSaver>(),
cancellationToken
),
baseChildFinder,
new BaseSerializer(memoryJsonCacheManager, objectSerializerFactory),
loggerFactory,
cancellationToken,
options
@@ -13,6 +13,12 @@ using Speckle.Sdk.Transports.ServerUtils;
namespace Speckle.Sdk.Serialisation.V2;
public class ServerObjectManagerOptions(TimeSpan? timeout = null, string? boundary = null)
{
public TimeSpan Timeout => timeout ?? TimeSpan.FromSeconds(120);
public string Boundary => boundary ??= Guid.NewGuid().ToString();
}
[GenerateAutoInterface]
public class ServerObjectManager : IServerObjectManager
{
@@ -21,6 +27,7 @@ public class ServerObjectManager : IServerObjectManager
private readonly ISdkActivityFactory _activityFactory;
private readonly HttpClient _client;
private readonly string _streamId;
private readonly ServerObjectManagerOptions _serverObjectManagerOptions;
public ServerObjectManager(
ISpeckleHttp speckleHttp,
@@ -28,13 +35,14 @@ public class ServerObjectManager : IServerObjectManager
Uri baseUri,
string streamId,
string? authorizationToken,
int timeoutSeconds = 120
ServerObjectManagerOptions? options = null
)
{
_serverObjectManagerOptions = options ?? new();
_activityFactory = activityFactory;
_client = speckleHttp.CreateHttpClient(
new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip },
timeoutSeconds: timeoutSeconds,
timeoutSeconds: (int)_serverObjectManagerOptions.Timeout.TotalSeconds,
authorizationToken: authorizationToken
);
_client.BaseAddress = baseUri;
@@ -139,10 +147,8 @@ public class ServerObjectManager : IServerObjectManager
CancellationToken cancellationToken
)
{
using var _ = _activityFactory.Start();
cancellationToken.ThrowIfCancellationRequested();
// Stopwatch sw = new Stopwatch(); sw.Start();
string objectsPostParameter = JsonConvert.SerializeObject(objectIds);
var payload = new Dictionary<string, string> { { "objects", objectsPostParameter } };
string serializedPayload = JsonConvert.SerializeObject(payload);
@@ -169,6 +175,7 @@ public class ServerObjectManager : IServerObjectManager
CancellationToken cancellationToken
)
{
using var _ = _activityFactory.Start();
cancellationToken.ThrowIfCancellationRequested();
using HttpRequestMessage message = new()
@@ -177,7 +184,7 @@ public class ServerObjectManager : IServerObjectManager
Method = HttpMethod.Post,
};
MultipartFormDataContent multipart = new();
MultipartFormDataContent multipart = new(_serverObjectManagerOptions.Boundary);
int mpId = 0;
var ctBuilder = new StringBuilder("[");
@@ -8,6 +8,13 @@ namespace Speckle.Sdk.Serialisation.V2;
public class ServerObjectManagerFactory(ISpeckleHttp speckleHttp, ISdkActivityFactory activityFactory)
: IServerObjectManagerFactory
{
public IServerObjectManager Create(Uri url, string streamId, string? authorizationToken, int timeoutSeconds = 120) =>
new ServerObjectManager(speckleHttp, activityFactory, url, streamId, authorizationToken, timeoutSeconds);
public IServerObjectManager Create(Uri url, string streamId, string? authorizationToken, TimeSpan? timeout = null) =>
new ServerObjectManager(
speckleHttp,
activityFactory,
url,
streamId,
authorizationToken,
new ServerObjectManagerOptions(timeout: timeout)
);
}
+80 -10
View File
@@ -1,47 +1,117 @@
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Speckle.Sdk.Api;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Dependencies;
using Speckle.Sdk.Host;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models.GraphTraversal;
using Speckle.Sdk.Serialisation.V2;
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;
public record Application(string Name, string Slug);
public record SpeckleSdkOptions(
Application Application,
string ApplicationVersion,
string? SpeckleVersion,
IEnumerable<Assembly>? Assemblies
);
public static class ServiceRegistration
{
private static string GetAssemblyVersion() =>
Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "Unknown";
public static IServiceCollection AddSpeckleSdk(
this IServiceCollection serviceCollection,
HostApplication application,
HostAppVersion version,
string speckleVersion
Application application,
string applicationVersion,
string? speckleVersion = null,
IEnumerable<Assembly>? assemblies = null
) => serviceCollection.AddSpeckleSdk(new(application, applicationVersion, speckleVersion, assemblies));
public static IServiceCollection AddSpeckleSdk(
this IServiceCollection serviceCollection,
Application application,
string applicationVersion,
string? speckleVersion,
params Assembly[] assemblies
) => serviceCollection.AddSpeckleSdk(new(application, applicationVersion, speckleVersion, assemblies));
public static IServiceCollection AddSpeckleSdk(
this IServiceCollection serviceCollection,
Application application,
string applicationVersion,
params Assembly[] assemblies
) => serviceCollection.AddSpeckleSdk(new(application, applicationVersion, null, assemblies));
public static IServiceCollection AddSpeckleSdk(
this IServiceCollection serviceCollection,
SpeckleSdkOptions speckleSdkOptions
)
{
var currentAssembly = Assembly.GetExecutingAssembly();
var allAssembles = speckleSdkOptions.Assemblies?.ToList() ?? [];
if (!allAssembles.Contains(currentAssembly))
{
allAssembles.Add(currentAssembly);
}
TypeLoader.Reset();
TypeLoader.Initialize(allAssembles.ToArray());
serviceCollection.AddLogging();
string name = application.Name;
serviceCollection.AddSingleton<ISpeckleApplication>(
new SpeckleApplication
{
HostApplication = name,
SpeckleVersion = speckleVersion,
HostApplicationVersion = HostApplications.GetVersion(version),
Slug = application.Slug,
HostApplication = speckleSdkOptions.Application.Name,
HostApplicationVersion = speckleSdkOptions.ApplicationVersion,
Slug = speckleSdkOptions.Application.Slug,
SpeckleVersion = speckleSdkOptions.SpeckleVersion ?? GetAssemblyVersion(),
}
);
serviceCollection.TryAddSingleton<ISdkActivityFactory, NullActivityFactory>();
serviceCollection.TryAddSingleton<ISdkMetricsFactory, NullSdkMetricsFactory>();
serviceCollection.AddMatchingInterfacesAsTransient(Assembly.GetExecutingAssembly());
serviceCollection.AddMatchingInterfacesAsTransient(
Assembly.GetExecutingAssembly(),
typeof(ServerTransport),
typeof(Account),
typeof(ServerApi),
typeof(SqLiteJsonCacheManager),
typeof(ServerObjectManager),
typeof(BaseSerializer),
typeof(SerializeProcess),
typeof(ObjectSaver),
typeof(ObjectSerializer),
typeof(ObjectDeserializer),
typeof(DeserializeProcess),
typeof(ObjectLoader),
typeof(TraversalRule),
typeof(Client)
);
serviceCollection.AddMatchingInterfacesAsTransient(typeof(GraphQLRetry).Assembly);
return serviceCollection;
}
public static IServiceCollection AddMatchingInterfacesAsTransient(
this IServiceCollection serviceCollection,
Assembly assembly
Assembly assembly,
params Type[] classesToIgnore
)
{
foreach (var type in assembly.ExportedTypes.Where(t => t.IsNonAbstractClass()))
{
if (classesToIgnore.Contains(type))
{
continue;
}
foreach (var matchingInterface in type.FindMatchingInterface())
{
serviceCollection.TryAddTransient(matchingInterface, type);
+5 -12
View File
@@ -1,50 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Compiler Properties">
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<Configurations>Debug;Release;Local</Configurations>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Label="Nugetspec Package Properties">
<PackageId>Speckle.Sdk</PackageId>
<Description>The .NET SDK for Speckle</Description>
<PackageTags>$(PackageTags) core sdk</PackageTags>
<NoWarn>$(NoWarn);CS8618</NoWarn>
</PropertyGroup>
<PropertyGroup Label="Nuget Package Properties">
<IsPackable>true</IsPackable>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup Label="Expose internals to test projects">
<InternalsVisibleTo Include="Speckle.Sdk.Tests.Unit" />
<InternalsVisibleTo Include="Speckle.Sdk.Tests.Integration" />
<InternalsVisibleTo Include="Speckle.Sdk.Serialization.Tests" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="GraphQL.Client"/>
<ItemGroup Label="Package References">
<PackageReference Include="GraphQL.Client" />
<PackageReference Include="Microsoft.CSharp" />
<PackageReference Include="Microsoft.Data.Sqlite"/>
<PackageReference Include="Microsoft.Data.Sqlite" />
<PackageReference Include="Speckle.DoubleNumerics" />
<PackageReference Include="Speckle.Newtonsoft.Json" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" OverrideVersion="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" OverrideVersion="8.0.0"/>
<PackageReference Include="Microsoft.Extensions.Logging" OverrideVersion="8.0.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Logging" />
<PackageReference Include="Microsoft.Extensions.Logging" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Speckle.Sdk.Dependencies\Speckle.Sdk.Dependencies.csproj" />
</ItemGroup>
</Project>
@@ -1,15 +0,0 @@
namespace Speckle.Sdk;
/// <summary>
/// These are exceptions who's message is not user friendly
/// </summary>
public class SpeckleNonUserFacingException : SpeckleException
{
public SpeckleNonUserFacingException() { }
public SpeckleNonUserFacingException(string? message)
: base(message) { }
public SpeckleNonUserFacingException(string? message, Exception? innerException)
: base(message, innerException) { }
}
-128
View File
@@ -1,128 +0,0 @@
using System.Diagnostics;
using System.Text;
using Speckle.Sdk.Logging;
namespace Speckle.Sdk.Transports;
/// <summary>
/// Writes speckle objects to disk.
/// </summary>
public class DiskTransport : ICloneable, ITransport
{
public DiskTransport(string? basePath = null)
{
basePath ??= Path.Combine(SpecklePathProvider.UserSpeckleFolderPath, "DiskTransportFiles");
RootPath = Path.Combine(basePath);
Directory.CreateDirectory(RootPath);
}
public string RootPath { get; set; }
public object Clone()
{
return new DiskTransport
{
RootPath = RootPath,
CancellationToken = CancellationToken,
OnErrorAction = OnErrorAction,
OnProgressAction = OnProgressAction,
TransportName = TransportName,
};
}
public string TransportName { get; set; } = "Disk";
public Dictionary<string, object> TransportContext =>
new()
{
{ "name", TransportName },
{ "type", GetType().Name },
{ "basePath", RootPath },
};
public CancellationToken CancellationToken { get; set; }
public IProgress<ProgressArgs>? OnProgressAction { get; set; }
public Action<string, Exception>? OnErrorAction { get; set; }
public int SavedObjectCount { get; private set; }
public TimeSpan Elapsed { get; set; } = TimeSpan.Zero;
public void BeginWrite()
{
SavedObjectCount = 0;
}
public void EndWrite() { }
public Task<string?> GetObject(string id)
{
CancellationToken.ThrowIfCancellationRequested();
var filePath = Path.Combine(RootPath, id);
if (File.Exists(filePath))
{
return Task.FromResult<string?>(File.ReadAllText(filePath, Encoding.UTF8));
}
return Task.FromResult<string?>(null);
}
public void SaveObject(string id, string serializedObject)
{
var stopwatch = Stopwatch.StartNew();
CancellationToken.ThrowIfCancellationRequested();
var filePath = Path.Combine(RootPath, id);
if (File.Exists(filePath))
{
return;
}
try
{
File.WriteAllText(filePath, serializedObject, Encoding.UTF8);
}
catch (Exception ex)
{
throw new TransportException(this, $"Failed to write object {id} to disk", ex);
}
SavedObjectCount++;
stopwatch.Stop();
Elapsed += stopwatch.Elapsed;
}
public Task WriteComplete()
{
return Task.CompletedTask;
}
public async Task<string> CopyObjectAndChildren(string id, ITransport targetTransport)
{
string res = await TransportHelpers
.CopyObjectAndChildrenAsync(id, this, targetTransport, CancellationToken)
.ConfigureAwait(false);
return res;
}
public Task<Dictionary<string, bool>> HasObjects(IReadOnlyList<string> objectIds)
{
Dictionary<string, bool> ret = new();
foreach (string objectId in objectIds)
{
var filePath = Path.Combine(RootPath, objectId);
ret[objectId] = File.Exists(filePath);
}
return Task.FromResult(ret);
}
public override string ToString()
{
return $"Disk Transport @{RootPath}";
}
}
+2 -1
View File
@@ -1,6 +1,7 @@
using System.Runtime.CompilerServices;
using Speckle.Objects.Geometry;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
using Speckle.Sdk.Testing;
namespace Speckle.Objects.Tests.Unit;
@@ -12,6 +13,6 @@ public static class Module
{
SpeckleVerify.Initialize();
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Polyline).Assembly);
TypeLoader.Initialize(typeof(Base).Assembly, typeof(Polyline).Assembly);
}
}
@@ -0,0 +1,76 @@
[
{
"applicationId": null,
"data": [
3,
0,
1,
2
],
"id": "13da8e855141b835e68bba721411046e",
"speckle_type": "Speckle.Core.Models.DataChunk"
},
{
"__closure": {
"13da8e855141b835e68bba721411046e": 100,
"cc9d42395b317ed25947b1285bbd5103": 100,
"ede4848fc3abda9275a19fee3447ffbd": 100
},
"applicationId": "asdfasdf",
"area": 42.0,
"bbox": null,
"colors": [
{
"__closure": null,
"referencedId": "cc9d42395b317ed25947b1285bbd5103",
"speckle_type": "reference"
}
],
"faces": [
{
"__closure": null,
"referencedId": "13da8e855141b835e68bba721411046e",
"speckle_type": "reference"
}
],
"id": "85fdad62ca935832449ffb359ebed7b8",
"speckle_type": "Objects.Geometry.Mesh",
"textureCoordinates": [],
"units": "m",
"vertexNormals": [],
"vertices": [
{
"__closure": null,
"referencedId": "ede4848fc3abda9275a19fee3447ffbd",
"speckle_type": "reference"
}
],
"volume": 420.0
},
{
"applicationId": null,
"data": [
-10185235,
-10185235,
-10185235
],
"id": "cc9d42395b317ed25947b1285bbd5103",
"speckle_type": "Speckle.Core.Models.DataChunk"
},
{
"applicationId": null,
"data": [
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
1.0,
1.0,
0.0
],
"id": "ede4848fc3abda9275a19fee3447ffbd",
"speckle_type": "Speckle.Core.Models.DataChunk"
}
]
@@ -0,0 +1,11 @@
[
{
"applicationId": "iosdf;juasdfioj",
"id": "035cad79be39e59c765b7946c4ea5f0c",
"speckle_type": "Objects.Geometry.Point",
"units": "ft",
"x": 123.123,
"y": 111.222,
"z": -0.001
}
]
@@ -0,0 +1,12 @@
[
{
"applicationId": "iosdf;juasdfioj",
"bbox": null,
"id": "282d78f7ca1a2ec37bf3fcf3f24e7a27",
"speckle_type": "Objects.Geometry.Vector",
"units": "in",
"x": 321.321,
"y": 222.111,
"z": -1.001
}
]
@@ -0,0 +1,33 @@
using Speckle.Sdk.Models;
using Speckle.Sdk.Serialisation;
using Speckle.Sdk.Serialisation.V2.Send;
using Speckle.Sdk.Testing;
namespace Speckle.Objects.SerializationTests;
public sealed class ObjectsSerializationTest
{
[Theory]
[MemberData(nameof(ObjectsTestData.TheoryData), MemberType = typeof(ObjectsTestData))]
public async Task SerializeAndVerify(Base testCase)
{
var serialized = Serialize(testCase);
await VerifySerialized(serialized).UseParameters(testCase);
}
private static IReadOnlyList<(Id, Json, Dictionary<Id, int>)> Serialize(Base data)
{
using var serializer = new ObjectSerializerFactory(new BasePropertyGatherer()).Create(
new Dictionary<Id, NodeInfo>(),
default
);
return serializer.Serialize(data).ToList();
}
private static SettingsTask VerifySerialized(IReadOnlyList<(Id, Json, Dictionary<Id, int>)> serializedResult)
{
var jsons = serializedResult.OrderBy(x => x.Item1.Value).Select(x => x.Item2).ToArray();
return SpeckleVerify.VerifyJsons(jsons);
}
}
@@ -0,0 +1,46 @@
using Speckle.Objects.Geometry;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models;
using Point = Speckle.Objects.Geometry.Point;
namespace Speckle.Objects.SerializationTests;
public static class ObjectsTestData
{
/// <summary>
/// Data set of simple valid Speckle Objects
/// Right now, we should have exactly one for each non-abstract object model.
/// </summary>
/// <returns></returns>
private static IEnumerable<Base> Data()
{
yield return new Mesh()
{
vertices = [0d, 0d, 0d, 1d, 0d, 0d, 1d, 1d, 0d],
faces = [3, 0, 1, 2],
applicationId = "asdfasdf",
units = Units.Meters,
area = 42,
volume = 420,
colors = [-10185235, -10185235, -10185235],
};
yield return new Point()
{
x = 123.123,
y = 111.222,
z = -0.001,
applicationId = "iosdf;juasdfioj",
units = Units.Feet,
};
yield return new Vector()
{
x = 321.321,
y = 222.111,
z = -1.001,
applicationId = "iosdf;juasdfioj",
units = Units.Inches,
};
}
public static TheoryData<Base> TheoryData => new(Data());
}
@@ -1,21 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<IsTestProject>true</IsTestProject>
<PolySharpExcludeGeneratedTypes>System.Runtime.CompilerServices.IsExternalInit</PolySharpExcludeGeneratedTypes>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="altcover" />
<PackageReference Include="AwesomeAssertions" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit.assert" />
<PackageReference Include="xunit.runner.visualstudio"/>
<PackageReference Include="xunit.runner.visualstudio" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Speckle.Objects\Speckle.Objects.csproj" />
<ProjectReference Include="..\Speckle.Sdk.Testing\Speckle.Sdk.Testing.csproj" />
</ItemGroup>
</Project>
@@ -10,9 +10,9 @@
},
"AwesomeAssertions": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "6fWiV7mGZUzZXzeiW3hWF0nJokuuNm4hnzuqbM3IXHqGYkWnHl65+wNpuQ73xfJXClX0fmfKcTdQ2Ula719IDg=="
"requested": "[8.1.0, )",
"resolved": "8.1.0",
"contentHash": "IfNC4cpXPi9tclWvuNO9lfkuIxJsUTLTS1NXto55jDrAUQJYl0zLI9ByISrfkbBE2Xtg+IWaAXQ6jnUx3anDuw=="
},
"Microsoft.NET.Test.Sdk": {
"type": "Direct",
@@ -60,22 +60,30 @@
},
"Argon": {
"type": "Transitive",
"resolved": "0.26.0",
"contentHash": "n7btGXdtRyprGnpLMpBs6rLScxlvPtVWwmTR8h7CtJvpZXBGhGvibEdZxRjeTZNrwf403jJ0ZPpt35Pz/NaNsw=="
"resolved": "0.28.0",
"contentHash": "78BmoFm8SK733nq4F/SjqNKkXJHdrg/MslvYfNjJX/nM/mEkltHUzPJRjBE9VI/zghsjFPQxMRPEUaqIgg98zg=="
},
"Castle.Core": {
"type": "Transitive",
"resolved": "5.1.1",
"contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==",
"dependencies": {
"System.Diagnostics.EventLog": "6.0.0"
}
},
"DiffEngine": {
"type": "Transitive",
"resolved": "15.9.0",
"contentHash": "shE6+tO4w5BmQTX0z+WnUV4UfmPNn6oTqBINbkts6OP0Icyx5WROSDzjjb95EwVYC4IAS+PxxS4Vbapxz4hkdw==",
"resolved": "16.2.1",
"contentHash": "UfMgXClqOGkPNfth210upiTY18LPCgjsfNrh0Olo5qI+QTkkCO6wHSuOwknxJdKtsWoaJ+E132Y2nzD0PiLWRw==",
"dependencies": {
"EmptyFiles": "8.7.1",
"System.Management": "9.0.1"
"EmptyFiles": "8.9.1",
"System.Management": "8.0.0"
}
},
"EmptyFiles": {
"type": "Transitive",
"resolved": "8.7.1",
"contentHash": "C8pvg0TvG2Mkn5LGNFGkFgFu8SUgYFwiu8U3y34qGQnnwKmGnlQTfTIUrtzfSjPxA4q7L/kRu09U5p32otZ2Aw=="
"resolved": "8.9.1",
"contentHash": "GbGf+oH/xiI3C5vJ5TnoA4sx7x7LhtOvN00fxihRZJsj40XuXk2TMz/4m26PfNSJj8JMAqo3BUBirjvam+3xkA=="
},
"FSharp.Core": {
"type": "Transitive",
@@ -242,25 +250,30 @@
},
"System.CodeDom": {
"type": "Transitive",
"resolved": "9.0.1",
"contentHash": "2J5uq+2smnj+u1jlyVJ6BGGqaK9fHcK/EwN7mbsuPqTI6dZr86br8Cg6o/5B+icQ9ANTvTDpJjnhDNtYYZijHQ=="
"resolved": "8.0.0",
"contentHash": "WTlRjL6KWIMr/pAaq3rYqh0TJlzpouaQ/W1eelssHgtlwHAH25jXTkUphTYx9HaIIf7XA6qs/0+YhtLEQRkJ+Q=="
},
"System.ComponentModel.Annotations": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg=="
},
"System.Diagnostics.EventLog": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "lcyUiXTsETK2ALsZrX+nWuHSIQeazhqPphLfaRxzdGaG93+0kELqpgEHtwWOlQe7+jSFnKwaCAgL4kjeZCQJnw=="
},
"System.IO.Hashing": {
"type": "Transitive",
"resolved": "9.0.1",
"contentHash": "jY+E/PElNWQiazN0YHqZGvcSedcZ4Wt0Os1nnJ2SzR3gWZlhNRDkSXOhuHJcLuImD8SrJQQ8TfU0W4mVcit2hg=="
"resolved": "9.0.4",
"contentHash": "WogPvgAFqQORFD8Iyha6RZ+/1QB3dsWRWxbwi8/HHVgiGQ8z0oMWpwe8Kk3Ti+Roe+P6a3sBg+WwBfEsyziZKg=="
},
"System.Management": {
"type": "Transitive",
"resolved": "9.0.1",
"contentHash": "CLEo9O6FuO4GQ3ZQkGssg9CJ2w2TN7GMFf3wHTc7YVWJV4xoyJRPw+XIDQnCcSUJCrHhrAWOO60cAX29EV5LFQ==",
"resolved": "8.0.0",
"contentHash": "jrK22i5LRzxZCfGb+tGmke2VH7oE0DvcDlJ1HAKYU8cPmD8XnpUT0bYn2Gy98GEhGjtfbR/sxKTVb+dE770pfA==",
"dependencies": {
"System.CodeDom": "9.0.1"
"System.CodeDom": "8.0.0"
}
},
"System.Memory": {
@@ -298,13 +311,13 @@
},
"Verify": {
"type": "Transitive",
"resolved": "28.10.1",
"contentHash": "2B/VtFN5jtF5g28kaM4GdJZTwb3pisd4+wL2NEPi9ZYe2lghWsCzS30V6LF1ILApLBfAorAstkU/Vw3sDWRqrg==",
"resolved": "29.4.0",
"contentHash": "wlqJ6ygXORa3lrAaErTA4EWkDcK9pBG2k5iC/I5F2UpWfyV7aXw/+SwGiWYe4XSk9d7VObe4xi4+0AV9rLRsBw==",
"dependencies": {
"Argon": "0.26.0",
"DiffEngine": "15.9.0",
"Argon": "0.28.0",
"DiffEngine": "16.2.1",
"SimpleInfoName": "3.1.0",
"System.IO.Hashing": "9.0.1"
"System.IO.Hashing": "9.0.4"
}
},
"xunit.abstractions": {
@@ -353,11 +366,10 @@
"speckle.sdk.testing": {
"type": "Project",
"dependencies": {
"Microsoft.NET.Test.Sdk": "[17.13.0, )",
"Moq": "[4.20.72, )",
"Speckle.Sdk": "[1.0.0, )",
"Verify.Quibble": "[2.1.1, )",
"Verify.Xunit": "[28.10.1, )",
"xunit.runner.visualstudio": "[3.0.2, )"
"Verify.Xunit": "[29.4.0, )"
}
},
"GraphQL.Client": {
@@ -405,6 +417,15 @@
"Microsoft.Extensions.Options": "2.2.0"
}
},
"Moq": {
"type": "CentralTransitive",
"requested": "[4.20.72, )",
"resolved": "4.20.72",
"contentHash": "EA55cjyNn8eTNWrgrdZJH5QLFp2L43oxl1tlkoYUKIE9pRwL784OWiTXeCV5ApS+AMYEAlt7Fo03A2XfouvHmQ==",
"dependencies": {
"Castle.Core": "5.1.1"
}
},
"Speckle.DoubleNumerics": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
@@ -430,15 +451,15 @@
},
"Verify.Xunit": {
"type": "CentralTransitive",
"requested": "[28.10.1, )",
"resolved": "28.10.1",
"contentHash": "mkG7agMlx8oAEGcHoRY72hyDyNTdLIrzbfmniXFQgQ3yKulAHSYvYc9quzhpg0Sy+jb3svbdLqnRSg0VRhet3A==",
"requested": "[29.4.0, )",
"resolved": "29.4.0",
"contentHash": "P8HYW7aromKGm90Cgx0XKL3qKKmYZHDwHTQfBfDCCPnhNlVWevJzrpuUQ0+3vTVdRAtsts2a1OE/tD+3yjJbHA==",
"dependencies": {
"Argon": "0.26.0",
"DiffEngine": "15.9.0",
"Argon": "0.28.0",
"DiffEngine": "16.2.1",
"SimpleInfoName": "3.1.0",
"System.IO.Hashing": "9.0.1",
"Verify": "28.10.1",
"System.IO.Hashing": "9.0.4",
"Verify": "29.4.0",
"xunit.abstractions": "2.0.3",
"xunit.extensibility.execution": "2.9.3"
}
@@ -1,22 +1,14 @@
#pragma warning disable CA1506
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Speckle.Sdk;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
using Speckle.Sdk.Serialisation.V2;
using Speckle.Sdk.Serialisation.V2.Receive;
using Speckle.Sdk.Serialisation.V2.Send;
using Speckle.Sdk.Serialization.Testing;
using Speckle.Sdk.SQLite;
const bool skipCacheReceive = false;
const bool skipCacheSendCheck = true;
const bool skipCacheSendSave = false;
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Base).Assembly, Assembly.GetExecutingAssembly());
var url = "https://latest.speckle.systems/projects/a3ac1b2706/models/59d3b0f3c6"; //small?
var streamId = "a3ac1b2706";
@@ -33,7 +25,7 @@ var streamId = "2099ac4b5f";
var rootId = "30fb4cbe6eb2202b9e7b4a4fcc3dd2b6";*/
var serviceCollection = new ServiceCollection();
serviceCollection.AddSpeckleSdk(HostApplications.Navisworks, HostAppVersion.v2023, "Test");
serviceCollection.AddSpeckleSdk(new("Tests", "test"), "v3");
var serviceProvider = serviceCollection.BuildServiceProvider();
Console.WriteLine("Attach");
@@ -41,21 +33,15 @@ Console.WriteLine("Attach");
var token = serviceProvider.GetRequiredService<IAccountManager>().GetDefaultAccount()?.token;
var progress = new Progress(true);
var factory = new SerializeProcessFactory(
new BaseChildFinder(new BasePropertyGatherer()),
new ObjectSerializerFactory(new BasePropertyGatherer()),
new BaseDeserializer(new ObjectDeserializerFactory()),
serviceProvider.GetRequiredService<ISqLiteJsonCacheManagerFactory>(),
serviceProvider.GetRequiredService<IServerObjectManagerFactory>(),
new NullLoggerFactory()
);
var factory = serviceProvider.GetRequiredService<IDeserializeProcessFactory>();
var process = factory.CreateDeserializeProcess(new Uri(url), streamId, token, progress, default, new(skipCacheReceive));
var @base = await process.Deserialize(rootId).ConfigureAwait(false);
Console.WriteLine("Deserialized");
Console.ReadLine();
Console.WriteLine("Executing");
var process2 = factory.CreateSerializeProcess(
var serializeProcessFactory = serviceProvider.GetRequiredService<ISerializeProcessFactory>();
var serializeProcess = serializeProcessFactory.CreateSerializeProcess(
new Uri(url),
streamId,
token,
@@ -63,8 +49,8 @@ var process2 = factory.CreateSerializeProcess(
default,
new SerializeProcessOptions(skipCacheSendCheck, skipCacheSendSave, true, true)
);
await process2.Serialize(@base).ConfigureAwait(false);
await serializeProcess.Serialize(@base).ConfigureAwait(false);
Console.WriteLine("Detach");
Console.ReadLine();
await process2.DisposeAsync().ConfigureAwait(false);
await serializeProcess.DisposeAsync().ConfigureAwait(false);
#pragma warning restore CA1506
@@ -1,11 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Speckle.Sdk\Speckle.Sdk.csproj" />
<ProjectReference Include="..\Speckle.Sdk.Tests.Performance\Speckle.Sdk.Tests.Performance.csproj" />
@@ -13,5 +12,4 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
</ItemGroup>
</Project>
@@ -1,4 +1,6 @@
using Speckle.Sdk.Serialisation.V2;
using System.Collections.Concurrent;
using Speckle.Sdk.Serialisation;
using Speckle.Sdk.Serialisation.V2;
using Speckle.Sdk.Serialisation.V2.Send;
using Speckle.Sdk.Testing.Framework;
using Speckle.Sdk.Transports;
@@ -6,7 +8,7 @@ using Speckle.Sdk.Transports;
namespace Speckle.Sdk.Serialization.Tests;
public sealed class CancellationSqLiteJsonCacheManager(CancellationTokenSource cancellationTokenSource)
: DummySqLiteJsonCacheManager
: MemoryJsonCacheManager(new ConcurrentDictionary<Id, Json>())
{
public override void SaveObjects(IEnumerable<(string id, string json)> items)
{
@@ -24,7 +26,8 @@ public class CancellationSqLiteSendManager(CancellationTokenSource cancellationT
}
}
public class CancellationServerObjectManager(CancellationTokenSource cancellationTokenSource) : DummyServerObjectManager
public class CancellationServerObjectManager(CancellationTokenSource cancellationTokenSource)
: MemoryServerObjectManager(new ConcurrentDictionary<string, string>())
{
public override Task UploadObjects(
IReadOnlyList<BaseItem> objects,
@@ -1,5 +1,15 @@
{
"Type": "System.OperationCanceledException",
"CancellationToken": {
"IsCancellationRequested": true,
"CanBeCanceled": true,
"WaitHandle": {
"SafeWaitHandle": {
"IsClosed": false,
"IsInvalid": false
}
}
},
"Data": {},
"Message": "The operation was canceled.",
"Source": "System.Private.CoreLib"
"Type": "OperationCanceledException"
}
@@ -1,5 +1,15 @@
{
"Type": "System.OperationCanceledException",
"CancellationToken": {
"IsCancellationRequested": true,
"CanBeCanceled": true,
"WaitHandle": {
"SafeWaitHandle": {
"IsClosed": false,
"IsInvalid": false
}
}
},
"Data": {},
"Message": "The operation was canceled.",
"Source": "System.Private.CoreLib"
"Type": "OperationCanceledException"
}
@@ -1,5 +1,15 @@
{
"Type": "System.OperationCanceledException",
"CancellationToken": {
"IsCancellationRequested": true,
"CanBeCanceled": true,
"WaitHandle": {
"SafeWaitHandle": {
"IsClosed": false,
"IsInvalid": false
}
}
},
"Data": {},
"Message": "The operation was canceled.",
"Source": "System.Private.CoreLib"
"Type": "OperationCanceledException"
}
@@ -1,5 +1,15 @@
{
"Type": "System.OperationCanceledException",
"CancellationToken": {
"IsCancellationRequested": true,
"CanBeCanceled": true,
"WaitHandle": {
"SafeWaitHandle": {
"IsClosed": false,
"IsInvalid": false
}
}
},
"Data": {},
"Message": "The operation was canceled.",
"Source": "System.Private.CoreLib"
"Type": "OperationCanceledException"
}
@@ -1,5 +1,15 @@
{
"Type": "System.OperationCanceledException",
"CancellationToken": {
"IsCancellationRequested": true,
"CanBeCanceled": true,
"WaitHandle": {
"SafeWaitHandle": {
"IsClosed": false,
"IsInvalid": false
}
}
},
"Data": {},
"Message": "The operation was canceled.",
"Source": "System.Private.CoreLib"
"Type": "OperationCanceledException"
}
@@ -1,5 +1,15 @@
{
"Type": "System.OperationCanceledException",
"CancellationToken": {
"IsCancellationRequested": true,
"CanBeCanceled": true,
"WaitHandle": {
"SafeWaitHandle": {
"IsClosed": false,
"IsInvalid": false
}
}
},
"Data": {},
"Message": "The operation was canceled.",
"Source": "System.Private.CoreLib"
"Type": "OperationCanceledException"
}
@@ -1,10 +1,11 @@
using System.Collections.Concurrent;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Speckle.Objects.Geometry;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
using Speckle.Sdk.Serialisation;
using Speckle.Sdk.Serialisation.V2;
using Speckle.Sdk.Serialisation.V2.Receive;
using Speckle.Sdk.Serialisation.V2.Send;
using Speckle.Sdk.Serialization.Tests.Framework;
@@ -14,10 +15,15 @@ namespace Speckle.Sdk.Serialization.Tests;
public class CancellationTests
{
private readonly ISerializeProcessFactory _factory;
public CancellationTests()
{
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Base).Assembly, typeof(DetachedTests).Assembly, typeof(Polyline).Assembly);
var serviceCollection = new ServiceCollection();
serviceCollection.AddSpeckleSdk(new("Tests", "test"), "v3", typeof(TestClass).Assembly, typeof(Polyline).Assembly);
var serviceProvider = serviceCollection.BuildServiceProvider();
_factory = serviceProvider.GetRequiredService<ISerializeProcessFactory>();
}
[Fact]
@@ -26,19 +32,17 @@ public class CancellationTests
var testClass = new TestClass() { RegularProperty = "Hello" };
using var cancellationSource = new CancellationTokenSource();
await using var serializeProcess = new SerializeProcess(
await using var serializeProcess = _factory.CreateSerializeProcess(
new ConcurrentDictionary<Id, Json>(),
new ConcurrentDictionary<string, string>(),
null,
new DummySqLiteSendManager(),
new DummyServerObjectManager(),
new BaseChildFinder(new BasePropertyGatherer()),
new BaseSerializer(new DummySqLiteSendManager(), new ObjectSerializerFactory(new BasePropertyGatherer())),
new NullLoggerFactory(),
cancellationSource.Token,
new SerializeProcessOptions(true, true, false, true)
);
await cancellationSource.CancelAsync();
var ex = await Assert.ThrowsAsync<OperationCanceledException>(
async () => await serializeProcess.Serialize(testClass)
var ex = await Assert.ThrowsAsync<OperationCanceledException>(async () =>
await serializeProcess.Serialize(testClass)
);
await Verify(ex);
cancellationSource.IsCancellationRequested.Should().BeTrue();
@@ -50,18 +54,16 @@ public class CancellationTests
var testClass = new TestClass() { RegularProperty = "Hello" };
using var cancellationSource = new CancellationTokenSource();
await using var serializeProcess = new SerializeProcess(
null,
await using var serializeProcess = _factory.CreateSerializeProcess(
new DummySqLiteSendManager(),
new CancellationServerObjectManager(cancellationSource),
new BaseChildFinder(new BasePropertyGatherer()),
new BaseSerializer(new DummySqLiteSendManager(), new ObjectSerializerFactory(new BasePropertyGatherer())),
new NullLoggerFactory(),
null,
cancellationSource.Token,
new SerializeProcessOptions(true, false, false, true)
new SerializeProcessOptions(true, true, false, true)
);
var ex = await Assert.ThrowsAsync<OperationCanceledException>(
async () => await serializeProcess.Serialize(testClass)
var ex = await Assert.ThrowsAsync<OperationCanceledException>(async () =>
await serializeProcess.Serialize(testClass)
);
await Verify(ex);
cancellationSource.IsCancellationRequested.Should().BeTrue();
@@ -73,18 +75,16 @@ public class CancellationTests
var testClass = new TestClass() { RegularProperty = "Hello" };
using var cancellationSource = new CancellationTokenSource();
await using var serializeProcess = new SerializeProcess(
await using var serializeProcess = _factory.CreateSerializeProcess(
new DummySqLiteSendManager(),
new CancellationServerObjectManager(cancellationSource),
null,
new CancellationSqLiteSendManager(cancellationSource),
new DummyServerObjectManager(),
new BaseChildFinder(new BasePropertyGatherer()),
new BaseSerializer(new DummySqLiteSendManager(), new ObjectSerializerFactory(new BasePropertyGatherer())),
new NullLoggerFactory(),
cancellationSource.Token,
new SerializeProcessOptions(true, false, false, true)
new SerializeProcessOptions(true, true, false, true)
);
var ex = await Assert.ThrowsAsync<OperationCanceledException>(
async () => await serializeProcess.Serialize(testClass)
var ex = await Assert.ThrowsAsync<OperationCanceledException>(async () =>
await serializeProcess.Serialize(testClass)
);
await Verify(ex);
cancellationSource.IsCancellationRequested.Should().BeTrue();
@@ -94,7 +94,7 @@ public class CancellationTests
[InlineData("RevitObject.json.gz", "3416d3fe01c9196115514c4a2f41617b", 7818)]
public async Task Cancellation_Receive_Cache(string fileName, string rootId, int oldCount)
{
var closures = await TestFileManager.GetFileAsClosures(fileName);
var closures = TestFileManager.GetFileAsClosures(fileName);
closures.Count.Should().Be(oldCount);
using var cancellationSource = new CancellationTokenSource();
@@ -121,7 +121,7 @@ public class CancellationTests
[InlineData("RevitObject.json.gz", "3416d3fe01c9196115514c4a2f41617b", 7818)]
public async Task Cancellation_Receive_Server(string fileName, string rootId, int oldCount)
{
var closures = await TestFileManager.GetFileAsClosures(fileName);
var closures = TestFileManager.GetFileAsClosures(fileName);
closures.Count.Should().Be(oldCount);
using var cancellationSource = new CancellationTokenSource();
@@ -148,7 +148,7 @@ public class CancellationTests
[InlineData("RevitObject.json.gz", "3416d3fe01c9196115514c4a2f41617b", 7818)]
public async Task Cancellation_Receive_Deserialize(string fileName, string rootId, int oldCount)
{
var closures = await TestFileManager.GetFileAsClosures(fileName);
var closures = TestFileManager.GetFileAsClosures(fileName);
closures.Count.Should().Be(oldCount);
using var cancellationSource = new CancellationTokenSource();
@@ -1,10 +1,9 @@
using System.Collections.Concurrent;
using System.Text;
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Newtonsoft.Json.Linq;
using Speckle.Objects.Geometry;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
using Speckle.Sdk.Serialisation;
using Speckle.Sdk.Serialisation.V2;
@@ -16,10 +15,15 @@ namespace Speckle.Sdk.Serialization.Tests;
public class DetachedTests
{
private readonly ISerializeProcessFactory _factory;
public DetachedTests()
{
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Base).Assembly, typeof(DetachedTests).Assembly, typeof(Polyline).Assembly);
var serviceCollection = new ServiceCollection();
serviceCollection.AddSpeckleSdk(new("Tests", "test"), "v3", typeof(TestClass).Assembly, typeof(Polyline).Assembly);
var serviceProvider = serviceCollection.BuildServiceProvider();
_factory = serviceProvider.GetRequiredService<ISerializeProcessFactory>();
}
[Fact]
@@ -30,19 +34,16 @@ public class DetachedTests
@base.detachedProp = new SamplePropBase() { name = "detachedProp" };
@base.attachedProp = new SamplePropBase() { name = "attachedProp" };
var objects = new Dictionary<string, string>();
var objects = new ConcurrentDictionary<string, string>();
await using var process2 = new SerializeProcess(
await using var serializeProcess = _factory.CreateSerializeProcess(
new ConcurrentDictionary<Id, Json>(),
objects,
null,
new DummySendCacheManager(objects),
new DummyServerObjectManager(),
new BaseChildFinder(new BasePropertyGatherer()),
new BaseSerializer(new DummySendCacheManager(objects), new ObjectSerializerFactory(new BasePropertyGatherer())),
new NullLoggerFactory(),
default,
new SerializeProcessOptions(false, false, true, true)
);
await process2.Serialize(@base);
await serializeProcess.Serialize(@base);
await VerifyJsonDictionary(objects);
}
@@ -115,19 +116,16 @@ public class DetachedTests
line = new Polyline() { units = "test", value = [3.0, 4.0] },
};
var objects = new Dictionary<string, string>();
var objects = new ConcurrentDictionary<string, string>();
await using var process2 = new SerializeProcess(
await using var serializeProcess = _factory.CreateSerializeProcess(
new ConcurrentDictionary<Id, Json>(),
objects,
null,
new DummySendCacheManager(objects),
new DummyServerObjectManager(),
new BaseChildFinder(new BasePropertyGatherer()),
new BaseSerializer(new DummySendCacheManager(objects), new ObjectSerializerFactory(new BasePropertyGatherer())),
new NullLoggerFactory(),
default,
new SerializeProcessOptions(false, false, true, true)
);
var results = await process2.Serialize(@base);
var results = await serializeProcess.Serialize(@base);
await VerifyJsonDictionary(objects);
}
@@ -185,19 +183,17 @@ public class DetachedTests
@base.list = new List<double>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
@base.list2 = new List<double>() { 1, 10 };
var objects = new Dictionary<string, string>();
var objects = new ConcurrentDictionary<string, string>();
await using var process2 = new SerializeProcess(
await using var serializeProcess = _factory.CreateSerializeProcess(
new ConcurrentDictionary<Id, Json>(),
objects,
null,
new DummySendCacheManager(objects),
new DummyServerObjectManager(),
new BaseChildFinder(new BasePropertyGatherer()),
new BaseSerializer(new DummySendCacheManager(objects), new ObjectSerializerFactory(new BasePropertyGatherer())),
new NullLoggerFactory(),
default,
new SerializeProcessOptions(false, false, true, true)
);
var results = await process2.Serialize(@base);
var results = await serializeProcess.Serialize(@base);
objects.Count.Should().Be(3);
var x = JObject.Parse(objects["efeadaca70a85ae6d3acfc93a8b380db"]);
@@ -220,19 +216,16 @@ public class DetachedTests
@base.list2 = new List<double>() { 1, 10 };
@base.arr = [1, 10];
var objects = new Dictionary<string, string>();
var objects = new ConcurrentDictionary<string, string>();
await using var process2 = new SerializeProcess(
await using var serializeProcess = _factory.CreateSerializeProcess(
new ConcurrentDictionary<Id, Json>(),
objects,
null,
new DummySendCacheManager(objects),
new DummyServerObjectManager(),
new BaseChildFinder(new BasePropertyGatherer()),
new BaseSerializer(new DummySendCacheManager(objects), new ObjectSerializerFactory(new BasePropertyGatherer())),
new NullLoggerFactory(),
default,
new SerializeProcessOptions(false, false, true, true)
);
var results = await process2.Serialize(@base);
var results = await serializeProcess.Serialize(@base);
await VerifyJsonDictionary(objects);
}
}
@@ -1,5 +1,11 @@
{
"Type": "Speckle.Sdk.SpeckleException",
"Data": {},
"InnerException": {
"$type": "NotImplementedException",
"Data": {},
"Message": "The method or operation is not implemented.",
"Type": "NotImplementedException"
},
"Message": "Error while sending",
"Source": "Speckle.Sdk"
"Type": "SpeckleException"
}
@@ -0,0 +1,11 @@
{
"Data": {},
"InnerException": {
"$type": "Exception",
"Data": {},
"Message": "Count exceeded",
"Type": "Exception"
},
"Message": "Error while sending",
"Type": "SpeckleException"
}
@@ -1,5 +1,11 @@
{
"Type": "Speckle.Sdk.SpeckleException",
"Message": "Error while sending",
"Source": "Speckle.Sdk"
"Data": {},
"InnerException": {
"$type": "NotImplementedException",
"Data": {},
"Message": "The method or operation is not implemented.",
"Type": "NotImplementedException"
},
"Message": "Error while loading",
"Type": "SpeckleException"
}
@@ -1,5 +1,5 @@
{
"Type": "System.NotImplementedException",
"Data": {},
"Message": "The method or operation is not implemented.",
"Source": "Speckle.Sdk.Serialization.Tests"
"Type": "NotImplementedException"
}
@@ -1,5 +1,5 @@
{
"Type": "System.NotImplementedException",
"Data": {},
"Message": "The method or operation is not implemented.",
"Source": "Speckle.Sdk.Serialization.Tests"
"Type": "NotImplementedException"
}
@@ -1,5 +1,5 @@
{
"Type": "Speckle.Sdk.SpeckleException",
"Data": {},
"Message": "Cannot skip server and cache. Please choose one.",
"Source": "Speckle.Sdk"
"Type": "SpeckleException"
}
@@ -1,5 +1,11 @@
{
"Type": "Speckle.Sdk.SpeckleException",
"Data": {},
"InnerException": {
"$type": "NotImplementedException",
"Data": {},
"Message": "The method or operation is not implemented.",
"Type": "NotImplementedException"
},
"Message": "Error while sending",
"Source": "Speckle.Sdk"
"Type": "SpeckleException"
}
@@ -1,8 +1,11 @@
using System.Collections.Concurrent;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Speckle.Objects.Geometry;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
using Speckle.Sdk.Serialisation;
using Speckle.Sdk.Serialisation.V2;
using Speckle.Sdk.Serialisation.V2.Receive;
using Speckle.Sdk.Serialisation.V2.Send;
using Speckle.Sdk.Serialization.Tests.Framework;
@@ -12,10 +15,15 @@ namespace Speckle.Sdk.Serialization.Tests;
public class ExceptionTests
{
private readonly ISerializeProcessFactory _factory;
public ExceptionTests()
{
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Base).Assembly, typeof(DetachedTests).Assembly, typeof(Polyline).Assembly);
var serviceCollection = new ServiceCollection();
serviceCollection.AddSpeckleSdk(new("Tests", "test"), "v3", typeof(TestClass).Assembly, typeof(Polyline).Assembly);
var serviceProvider = serviceCollection.BuildServiceProvider();
_factory = serviceProvider.GetRequiredService<ISerializeProcessFactory>();
}
[Fact]
@@ -23,20 +31,18 @@ public class ExceptionTests
{
var testClass = new TestClass() { RegularProperty = "Hello" };
var objects = new Dictionary<string, string>();
await using var process2 = new SerializeProcess(
null,
new DummySendCacheManager(objects),
var objects = new ConcurrentDictionary<Id, Json>();
await using var serializeProcess = _factory.CreateSerializeProcess(
new MemoryJsonCacheManager(objects),
new ExceptionServerObjectManager(),
new BaseChildFinder(new BasePropertyGatherer()),
new BaseSerializer(new DummySendCacheManager(objects), new ObjectSerializerFactory(new BasePropertyGatherer())),
new NullLoggerFactory(),
null,
default,
new SerializeProcessOptions(false, false, false, true)
);
//4 exceptions are fine because we use 4 threads for saving cache
var ex = await Assert.ThrowsAsync<SpeckleException>(async () => await process2.Serialize(testClass));
var ex = await Assert.ThrowsAsync<SpeckleException>(async () => await serializeProcess.Serialize(testClass));
await Verify(ex);
}
@@ -45,18 +51,57 @@ public class ExceptionTests
{
var testClass = new TestClass() { RegularProperty = "Hello" };
await using var process2 = new SerializeProcess(
null,
await using var serializeProcess = _factory.CreateSerializeProcess(
new ExceptionSendCacheManager(),
new DummyServerObjectManager(),
new BaseChildFinder(new BasePropertyGatherer()),
new BaseSerializer(new ExceptionSendCacheManager(), new ObjectSerializerFactory(new BasePropertyGatherer())),
new NullLoggerFactory(),
new MemoryServerObjectManager(new()),
null,
default,
new SerializeProcessOptions(false, false, false, true)
);
var ex = await Assert.ThrowsAsync<SpeckleException>(async () => await process2.Serialize(testClass));
var ex = await Assert.ThrowsAsync<SpeckleException>(async () => await serializeProcess.Serialize(testClass));
await Verify(ex);
}
[Fact]
public async Task Test_Exceptions_Cache_ExceptionsAfter_10()
{
var @base = new SampleObjectBase2();
@base["dynamicProp"] = 123;
@base.applicationId = "1";
@base.detachedProp = new SamplePropBase2()
{
name = "detachedProp",
applicationId = "2",
line = new Polyline() { units = "test", value = [1.0, 2.0] },
};
@base.detachedProp2 = new SamplePropBase2()
{
name = "detachedProp2",
applicationId = "3",
line = new Polyline() { units = "test", value = [3.0, 2.0] },
};
@base.attachedProp = new SamplePropBase2()
{
name = "attachedProp",
applicationId = "4",
line = new Polyline() { units = "test", value = [3.0, 4.0] },
};
await using var serializeProcess = _factory.CreateSerializeProcess(
new ExceptionSendCacheManager(exceptionsAfter: 10),
new MemoryServerObjectManager(new()),
null,
default,
new SerializeProcessOptions(false, false, false, true)
{
MaxHttpSendSize = 1,
MaxCacheSize = 1,
MaxParallelism = 1,
}
);
var ex = await Assert.ThrowsAsync<SpeckleException>(async () => await serializeProcess.Serialize(@base));
await Verify(ex);
}
@@ -89,7 +134,7 @@ public class ExceptionTests
[InlineData("RevitObject.json.gz", "3416d3fe01c9196115514c4a2f41617b", 7818)]
public async Task Test_Exceptions_Receive_Server(string fileName, string rootId, int oldCount)
{
var closures = await TestFileManager.GetFileAsClosures(fileName);
var closures = TestFileManager.GetFileAsClosures(fileName);
closures.Count.Should().Be(oldCount);
await using var process = new DeserializeProcess(
@@ -114,7 +159,7 @@ public class ExceptionTests
[InlineData("RevitObject.json.gz", "3416d3fe01c9196115514c4a2f41617b", 7818, true)]
public async Task Test_Exceptions_Receive_Cache(string fileName, string rootId, int oldCount, bool? hasObject)
{
var closures = await TestFileManager.GetFileAsClosures(fileName);
var closures = TestFileManager.GetFileAsClosures(fileName);
closures.Count.Should().Be(oldCount);
await using var process = new DeserializeProcess(
@@ -144,4 +189,23 @@ public class ExceptionTests
}
await Verify(ex).UseParameters(hasObject);
}
[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);
Assert.Throws<SpeckleSerializeException>(() =>
{
var _ = serializer.Serialize(new BadBase()).ToList();
});
}
}
@@ -1,16 +1,23 @@
using Microsoft.Extensions.Logging.Abstractions;
using Speckle.Sdk.Host;
using System.Collections.Concurrent;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Sdk.Models;
using Speckle.Sdk.Serialisation;
using Speckle.Sdk.Serialisation.V2;
using Speckle.Sdk.Serialisation.V2.Send;
namespace Speckle.Sdk.Serialization.Tests;
public class ExplicitInterfaceTests
{
private readonly ISerializeProcessFactory _factory;
public ExplicitInterfaceTests()
{
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Base).Assembly, typeof(TestClass).Assembly);
var serviceCollection = new ServiceCollection();
serviceCollection.AddSpeckleSdk(new("Tests", "test"), "v3", typeof(TestClass).Assembly);
var serviceProvider = serviceCollection.BuildServiceProvider();
_factory = serviceProvider.GetRequiredService<ISerializeProcessFactory>();
}
[Fact]
@@ -18,19 +25,16 @@ public class ExplicitInterfaceTests
{
var testClass = new TestClass() { RegularProperty = "Hello" };
var objects = new Dictionary<string, string>();
await using var process2 = new SerializeProcess(
var objects = new ConcurrentDictionary<string, string>();
await using var serializeProcess = _factory.CreateSerializeProcess(
new ConcurrentDictionary<Id, Json>(),
objects,
null,
new DummySendCacheManager(objects),
new DummyServerObjectManager(),
new BaseChildFinder(new BasePropertyGatherer()),
new BaseSerializer(new DummySendCacheManager(objects), new ObjectSerializerFactory(new BasePropertyGatherer())),
new NullLoggerFactory(),
default,
new SerializeProcessOptions(false, false, true, true)
new SerializeProcessOptions(true, true, false, true)
);
await process2.Serialize(testClass);
await serializeProcess.Serialize(testClass);
await VerifyJsonDictionary(objects);
}
@@ -2,21 +2,46 @@
namespace Speckle.Sdk.Serialization.Tests.Framework;
public class ExceptionSendCacheManager(bool? hasObject = null) : ISqLiteJsonCacheManager
public class ExceptionSendCacheManager(bool? hasObject = null, int? exceptionsAfter = null) : ISqLiteJsonCacheManager
{
private readonly object _lock = new();
private int _count;
public void Dispose() { }
public IReadOnlyCollection<(string Id, string Json)> GetAllObjects() => throw new NotImplementedException();
public void DeleteObject(string id) => throw new NotImplementedException();
public void DeleteObject(string id) => CheckExceptions();
public string? GetObject(string id) => null;
public void SaveObject(string id, string json) => throw new NotImplementedException();
public void SaveObject(string id, string json) => CheckExceptions();
public void UpdateObject(string id, string json) => throw new NotImplementedException();
public void UpdateObject(string id, string json) => CheckExceptions();
public void SaveObjects(IEnumerable<(string id, string json)> items) => throw new NotImplementedException();
public void SaveObjects(IEnumerable<(string id, string json)> items) => CheckExceptions();
public bool HasObject(string objectId) => hasObject ?? throw new NotImplementedException();
private void CheckExceptions()
{
lock (_lock)
{
if (exceptionsAfter is not null)
{
if (exceptionsAfter.Value > _count)
{
_count++;
}
else
{
throw new Exception("Count exceeded");
}
}
else
{
throw new NotImplementedException();
}
}
}
}
@@ -1,7 +1,8 @@
using System.IO.Compression;
using System.Collections.Concurrent;
using System.IO.Compression;
using System.Reflection;
using Speckle.Newtonsoft.Json.Linq;
using Speckle.Objects.Data;
using Speckle.Objects.Geometry;
using Speckle.Sdk.Common;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
@@ -10,49 +11,50 @@ namespace Speckle.Sdk.Serialization.Tests.Framework;
public static class TestFileManager
{
static TestFileManager()
{
TypeLoader.Reset();
TypeLoader.Initialize(typeof(Base).Assembly, typeof(DataObject).Assembly, _assembly);
}
private static readonly Assembly s_assembly = Assembly.GetExecutingAssembly(); //test
private static readonly Assembly s_speckleAssembly = typeof(Base).Assembly; //speckle.sdk
private static readonly Assembly s_speckleObjectsAssembly = typeof(Polyline).Assembly; //speckle.sdk
private static readonly Dictionary<string, IReadOnlyDictionary<string, string>> s_objects = new();
private static readonly Assembly _assembly = Assembly.GetExecutingAssembly();
private static readonly Dictionary<string, IReadOnlyDictionary<string, string>> _objects = new();
public static async Task<IReadOnlyDictionary<string, string>> GetFileAsClosures(string fileName)
public static IReadOnlyDictionary<string, string> GetFileAsClosures(string fileName)
{
if (!_objects.TryGetValue(fileName, out var closure))
lock (s_objects)
{
var fullName = _assembly.GetManifestResourceNames().Single(x => x.EndsWith(fileName));
var json = await ReadJson(fullName);
closure = ReadAsObjects(json);
_objects.Add(fileName, closure);
if (!s_objects.TryGetValue(fileName, out var closure))
{
TypeLoader.Reset();
TypeLoader.Initialize(s_assembly, s_speckleAssembly, s_speckleObjectsAssembly);
var fullName = s_assembly.GetManifestResourceNames().Single(x => x.EndsWith(fileName));
var json = ReadJson(fullName);
closure = ReadAsObjects(json);
s_objects.Add(fileName, closure);
}
return closure;
}
return closure;
}
private static async Task<string> ReadJson(string fullName)
private static string ReadJson(string fullName)
{
await using var stream = _assembly.GetManifestResourceStream(fullName).NotNull();
using var stream = s_assembly.GetManifestResourceStream(fullName).NotNull();
if (fullName.EndsWith(".gz"))
{
await using var z = new GZipStream(stream, CompressionMode.Decompress);
using var z = new GZipStream(stream, CompressionMode.Decompress);
using var reader2 = new StreamReader(z);
return await reader2.ReadToEndAsync();
return reader2.ReadToEnd();
}
using var reader = new StreamReader(stream);
return await reader.ReadToEndAsync();
return reader.ReadToEnd();
}
private static Dictionary<string, string> ReadAsObjects(string json)
private static ConcurrentDictionary<string, string> ReadAsObjects(string json)
{
var jsonObjects = new Dictionary<string, string>();
var jsonObjects = new ConcurrentDictionary<string, string>();
var array = JArray.Parse(json);
foreach (var obj in array)
{
if (obj is JObject jobj)
{
jsonObjects.Add(jobj["id"].NotNull().Value<string>().NotNull(), jobj.ToString());
jsonObjects.TryAdd(jobj["id"].NotNull().Value<string>().NotNull(), jobj.ToString());
}
}
return jsonObjects;
@@ -1,13 +1,16 @@
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;
@@ -17,32 +20,17 @@ namespace Speckle.Sdk.Serialization.Tests;
public class SerializationTests
{
private class TestLoader(string json) : IObjectLoader
private readonly ISerializeProcessFactory _factory;
public SerializationTests()
{
public Task<(Json, IReadOnlyCollection<Id>)> GetAndCache(string rootId, DeserializeProcessOptions? options)
{
var childrenIds = ClosureParser.GetChildrenIds(new(json), default).Select(x => new Id(x)).ToList();
return Task.FromResult<(Json, IReadOnlyCollection<Id>)>((new(json), childrenIds));
}
var serviceCollection = new ServiceCollection();
serviceCollection.AddSpeckleSdk(new("Tests", "test"), "v3", typeof(TestClass).Assembly, typeof(Polyline).Assembly);
var serviceProvider = serviceCollection.BuildServiceProvider();
public string? LoadId(string id) => null;
public void Dispose() { }
_factory = serviceProvider.GetRequiredService<ISerializeProcessFactory>();
}
/*
[Test]
[TestCase("RevitObject.json")]
public async Task RunTest2(string fileName)
{
var fullName = _assembly.GetManifestResourceNames().Single(x => x.EndsWith(fileName));
var json = await ReadJson(fullName);
var closure = await ReadAsObjects(json);
using DeserializeProcess sut = new(null, new TestLoader(json), new TestTransport(closure));
var @base = await sut.Deserialize("551513ff4f3596024547fc818f1f3f70");
@base.Should().NotBeNull();
}*/
public class TestObjectLoader(IReadOnlyDictionary<string, string> idToObject) : IObjectLoader
{
public Task<(Json, IReadOnlyCollection<Id>)> GetAndCache(string rootId, DeserializeProcessOptions? options)
@@ -66,7 +54,7 @@ public class SerializationTests
[InlineData("RevitObject.json.gz")]
public async Task Basic_Namespace_Validation(string fileName)
{
var closures = await TestFileManager.GetFileAsClosures(fileName);
var closures = TestFileManager.GetFileAsClosures(fileName);
var deserializer = new SpeckleObjectDeserializer
{
ReadTransport = new TestTransport(closures),
@@ -106,7 +94,7 @@ public class SerializationTests
[InlineData("RevitObject.json.gz")]
public async Task Basic_Namespace_Validation_New(string fileName)
{
var closures = await TestFileManager.GetFileAsClosures(fileName);
var closures = TestFileManager.GetFileAsClosures(fileName);
await using var process = new DeserializeProcess(
new TestObjectLoader(closures),
null,
@@ -165,7 +153,7 @@ public class SerializationTests
[InlineData("RevitObject.json.gz", "3416d3fe01c9196115514c4a2f41617b", 7818)]
public async Task Roundtrip_Test_Old(string fileName, string _, int count)
{
var closures = await TestFileManager.GetFileAsClosures(fileName);
var closures = TestFileManager.GetFileAsClosures(fileName);
var deserializer = new SpeckleObjectDeserializer
{
ReadTransport = new TestTransport(closures),
@@ -199,7 +187,7 @@ public class SerializationTests
[InlineData("RevitObject.json.gz", "3416d3fe01c9196115514c4a2f41617b", 7818, 4674)]
public async Task Roundtrip_Test_New(string fileName, string rootId, int oldCount, int newCount)
{
var closures = await TestFileManager.GetFileAsClosures(fileName);
var closures = TestFileManager.GetFileAsClosures(fileName);
closures.Count.Should().Be(oldCount);
Base root;
@@ -226,14 +214,12 @@ public class SerializationTests
}
var newIdToJson = new ConcurrentDictionary<string, string>();
await using (
var serializeProcess = new SerializeProcess(
var serializeProcess = _factory.CreateSerializeProcess(
new ConcurrentDictionary<Id, Json>(),
newIdToJson,
null,
new DummySqLiteSendManager(),
new DummySendServerObjectManager(newIdToJson),
new BaseChildFinder(new BasePropertyGatherer()),
new BaseSerializer(new DummySqLiteSendManager(), new ObjectSerializerFactory(new BasePropertyGatherer())),
new NullLoggerFactory(),
default,
new SerializeProcessOptions(true, true, false, true)
)

Some files were not shown because too many files have changed in this diff Show More