Compare commits
298 Commits
3.1.0-dev.276
...
3.15.2
| Author | SHA1 | Date | |
|---|---|---|---|
| 74d40e40a9 | |||
| c81692ee5a | |||
| 7f50987201 | |||
| 7042cdb06a | |||
| af0fc9f669 | |||
| edbc884d74 | |||
| 025d7f70ba | |||
| 70acc06f37 | |||
| a2c99a537a | |||
| 906ff9c3ff | |||
| 515d45528d | |||
| abf86eda03 | |||
| f777050c10 | |||
| c4e956cdb4 | |||
| a662fb54c2 | |||
| 9a74195b24 | |||
| 0ca9162e7b | |||
| 070f21b075 | |||
| 9bf6995b15 | |||
| 43ebc84881 | |||
| 7652cd385d | |||
| a81aaca8fe | |||
| 57843cc454 | |||
| bf6ae0f6af | |||
| 309cead189 | |||
| 4980796cd6 | |||
| 12df19e431 | |||
| c186d98ea7 | |||
| 00a6619cbe | |||
| 49ef9917c4 | |||
| 94b0473157 | |||
| 8071990dd5 | |||
| 8c7dbc89aa | |||
| 676a3df153 | |||
| c75538e1c7 | |||
| 5d10b77ee4 | |||
| 82dca56fbd | |||
| 80d1df8eca | |||
| b5796245aa | |||
| 639c774f80 | |||
| 3bb5d1e73a | |||
| e01360ad03 | |||
| 2494b160e8 | |||
| b0da4510bf | |||
| 96392d0d2f | |||
| 0aacc3fe89 | |||
| 39f5257f85 | |||
| 2ea6348689 | |||
| 42c26e38bf | |||
| cb31fd1a08 | |||
| 67236abafe | |||
| 59a4f8f864 | |||
| 0e98e1cccd | |||
| 79c6f02544 | |||
| 07713b41e1 | |||
| c3f944dcf1 | |||
| 3c0b9e8b1c | |||
| 8890f8cb36 | |||
| a0eae88479 | |||
| 8785e49f73 | |||
| 6e35d6af6d | |||
| b67eb8d8af | |||
| 63f06c8541 | |||
| 3f49bb05d1 | |||
| 9a879fd1ac | |||
| c3230d5d91 | |||
| f1a64590d7 | |||
| c2735f0a32 | |||
| 0b01091209 | |||
| 6568781275 | |||
| 98223e251c | |||
| 6740659af4 | |||
| 08f702794a | |||
| 879ebf7e3c | |||
| 1046e2aafc | |||
| 5cb0eddf4e | |||
| e97ce83c6b | |||
| ea23e72c77 | |||
| 701013ad46 | |||
| 37358570ec | |||
| 02b9a73164 | |||
| fdc0842b03 | |||
| 530387b87c | |||
| b16e32d1ff | |||
| d91fccbb10 | |||
| 62f687b589 | |||
| c0d4d951df | |||
| 4e1753e1fd | |||
| 23d5dd44bc | |||
| 4c615ae8c6 | |||
| d5ee9fb76c | |||
| ea118bcdbb | |||
| ba456ee3eb | |||
| 10d283a9f7 | |||
| 647c4733cb | |||
| 30c9b17dab | |||
| f7ddc19086 | |||
| 6d06901b4f | |||
| 4b588fc287 | |||
| 786e683d89 | |||
| 8d999f4f9c | |||
| bb7542e254 | |||
| d6f6254a92 | |||
| f60f85b639 | |||
| bcdf73cc70 | |||
| 47e72ee1a7 | |||
| f3de5324db | |||
| 4dd6db886f | |||
| 4b82db8ea2 | |||
| 9e7f26f7a6 | |||
| b19f8c4219 | |||
| c517e61517 | |||
| b3e0623856 | |||
| e5d1ef2448 | |||
| 83c3de05fa | |||
| 507ded7d4a | |||
| e15029bab3 | |||
| a43fd44206 | |||
| 1bcd8ac3a4 | |||
| a8dc93e22b | |||
| 5a0f883b98 | |||
| a5d035671a | |||
| cd6ebad619 | |||
| 33c2e6e1a4 | |||
| b97702adb1 | |||
| 80c4f694ec | |||
| fb5042004f | |||
| c0a9291632 | |||
| b783d2acb6 | |||
| 93539adc1e | |||
| 98005933de | |||
| 50906b172a | |||
| 05f7353925 | |||
| 8328498553 | |||
| 59019bf846 | |||
| 3afaf61a1a | |||
| 424609fad0 | |||
| 46c067308e | |||
| 0e33e8df8f | |||
| bc81c21e9d | |||
| 7f8b59d348 | |||
| 44ba61e4a5 | |||
| 4f7b470901 | |||
| 8c6426d617 | |||
| 5562ce1a2d | |||
| 7019b8d7c6 | |||
| 58a0326060 | |||
| 55f83919d1 | |||
| 46c57b18be | |||
| 7b5ada57cd | |||
| e29b27bcd3 | |||
| ff1b688321 | |||
| 0be143d391 | |||
| c0a66a297a | |||
| aad604e819 | |||
| 48313cb082 | |||
| 0361a6e5b7 | |||
| 0e97782c29 | |||
| 298dedc3af | |||
| 422403d499 | |||
| b652ffa773 | |||
| 68ace02e2d | |||
| b6be7a351f | |||
| 1039e75d0c | |||
| 0f8752d5ab | |||
| 64a93345d6 | |||
| efc38d8f5c | |||
| e3ca75abe1 | |||
| b479d368ad | |||
| 8d3985f93b | |||
| 915a18dc98 | |||
| 5fcb3223d6 | |||
| 21851c06d2 | |||
| 3a9a633d30 | |||
| 7f092d529c | |||
| a4f0e0e4aa | |||
| 227729a0df | |||
| 178085f3f8 | |||
| 9794195e9c | |||
| a61c442930 | |||
| 68a407905d | |||
| 0f2abaf532 | |||
| 07634b6f6a | |||
| e938725d35 | |||
| d3369e3ce5 | |||
| d75a61d775 | |||
| 2ae4003afb | |||
| 24db4c4ae4 | |||
| edf63d4a1b | |||
| b5b0922e7f | |||
| ff390f772d | |||
| d69f0bba2a | |||
| 33c14fc14c | |||
| 536e58aacc | |||
| 88188aace6 | |||
| ad44a7cdbc | |||
| 38449dca9a | |||
| 764eb43838 | |||
| a84e6d89ca | |||
| 377829adae | |||
| a479440b66 | |||
| cc9639b179 | |||
| d44b4fa52b | |||
| ea6ca8c555 | |||
| 113f0fd551 | |||
| bcc4e25970 | |||
| b733ce5f29 | |||
| 1c8b2b82d7 | |||
| 11cd2dc1cb | |||
| 3789898ea2 | |||
| 0b3318f9e1 | |||
| 7589ad5f05 | |||
| 6df32262bd | |||
| 001ca1c287 | |||
| 59e459559b | |||
| d305fe59cb | |||
| f163b2822e | |||
| 630bb38b8b | |||
| bacff2da34 | |||
| 129d5285ed | |||
| 5d07fe0ea0 | |||
| 062e3c2838 | |||
| f4cc4bc77e | |||
| d395f03219 | |||
| 56ed399d70 | |||
| 7e0766fc7f | |||
| 0df343ebe1 | |||
| 3cad57ceb3 | |||
| 11937ad7c9 | |||
| 8210fde69a | |||
| fa6f90621e | |||
| f6f1852664 | |||
| b3f4190614 | |||
| 2fc0024cd2 | |||
| 300a5627fd | |||
| 22f029fe33 | |||
| c728266c88 | |||
| 7f2d57cdad | |||
| 81a22bd4cc | |||
| f64da099ab | |||
| f4ba200640 | |||
| 4e84766be9 | |||
| 73a95aded0 | |||
| 404600a839 | |||
| 93d517eab7 | |||
| 4110d90107 | |||
| 2d134bf7e1 | |||
| 686d0fd31c | |||
| b06878bbe1 | |||
| 64114167a8 | |||
| 0c7cd75353 | |||
| 3c5679ad2a | |||
| b043623021 | |||
| bd0997913a | |||
| ac0ab3904c | |||
| 5c9d672a2b | |||
| a79f0fd035 | |||
| 18d6653282 | |||
| d58a656c8a | |||
| 720d049145 | |||
| 129688a646 | |||
| fa66258b23 | |||
| 9f0c572993 | |||
| 158baff5b0 | |||
| 7a29d27e46 | |||
| 82e3d37dd1 | |||
| 9695ec8c51 | |||
| 15fa319433 | |||
| 793bbb9cd3 | |||
| 3291010d43 | |||
| 14732ce174 | |||
| ba655988b0 | |||
| 96822c4e66 | |||
| 08356de1ad | |||
| 375f5071ae | |||
| 5900a3c178 | |||
| d7bf324029 | |||
| c784fbf462 | |||
| 26f1802787 | |||
| a96e0f8c8e | |||
| 9f36c9cfe5 | |||
| 4959f277e8 | |||
| ea08b83f7a | |||
| b24dc685fa | |||
| 7cad14fe25 | |||
| 4d552b6834 | |||
| 378a91995e | |||
| 53b66dd26b | |||
| 9dd04c0881 | |||
| 474c18c29f | |||
| f20064c9f0 | |||
| 6f1b22d13a | |||
| ef19bfa69d | |||
| 45601f1a2f | |||
| adf3298baf | |||
| 8f5e5f675b | |||
| 9217e67a9d | |||
| ebccf6ff79 |
@@ -3,16 +3,9 @@
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"csharpier": {
|
||||
"version": "0.30.6",
|
||||
"version": "1.1.2",
|
||||
"commands": [
|
||||
"dotnet-csharpier"
|
||||
],
|
||||
"rollForward": false
|
||||
},
|
||||
"gitversion.tool": {
|
||||
"version": "5.12.0",
|
||||
"commands": [
|
||||
"dotnet-gitversion"
|
||||
"csharpier"
|
||||
],
|
||||
"rollForward": false
|
||||
}
|
||||
|
||||
@@ -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
@@ -1,6 +1,6 @@
|
||||
printWidth: 120
|
||||
useTabs: false
|
||||
tabWidth: 2
|
||||
indentSize: 2
|
||||
preprocessorSymbolSets:
|
||||
- ""
|
||||
- "DEBUG"
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# Coding standards, domain knowledge, and preferences that AI should follow
|
||||
|
||||
## C# Coding Standards
|
||||
|
||||
- Use the csharpier formatter for formatting C# code.
|
||||
- Use the .editorconfig file for code style settings.
|
||||
- Always use `var` when the type is obvious from the right side of the assignment.
|
||||
- Always add braces for `if`, `else`, `for`, `foreach`, `while`, and `do` statements, even if they are single-line statements.
|
||||
|
||||
## Testing
|
||||
|
||||
- Use xUnit for unit testing.
|
||||
- Use FluentAssertions for assertions in tests.
|
||||
- Use Moq for mocking dependencies in tests.
|
||||
@@ -0,0 +1,22 @@
|
||||
# Git Commit Instructions
|
||||
|
||||
To ensure high-quality and consistent commits, please follow these guidelines:
|
||||
|
||||
1. **Format your code**
|
||||
- Run the `csharpier` formatter on all C# files before committing.
|
||||
- Ensure your code adheres to the `.editorconfig` settings.
|
||||
|
||||
2. **Write clear commit messages**
|
||||
- Use the present tense ("Add feature" not "Added feature").
|
||||
- Start with a short summary (max 72 characters), followed by a blank line and a detailed description if necessary.
|
||||
|
||||
3. **Test your changes**
|
||||
- Run all unit tests before committing.
|
||||
- Add or update xUnit tests as needed.
|
||||
- Use AwesomeAssertions for assertions and Moq for mocking in tests.
|
||||
|
||||
4. **Review your changes**
|
||||
- Double-check for accidental debug code or commented-out code.
|
||||
- Ensure only relevant files are staged.
|
||||
|
||||
Thank you for helping maintain code quality!
|
||||
@@ -0,0 +1,61 @@
|
||||
name: Integration Test
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
speckle-sharp-sdk-ref:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
integration-test:
|
||||
env:
|
||||
CLIENT_DIR: "./client"
|
||||
CLIENT_REPO: "specklesystems/speckle-sharp-sdk"
|
||||
SERVER_DIR: "./server"
|
||||
SERVER_REPO: "specklesystems/speckle-server-internal"
|
||||
SOLUTION: "Speckle.Sdk.sln"
|
||||
SPECKLE_SERVER_IMAGE: "speckle-server:local"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout ${{ env.CLIENT_REPO }}
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
path: ${{ env.CLIENT_DIR }}
|
||||
repository: ${{ env.CLIENT_REPO }}
|
||||
ref: ${{ inputs.speckle-sharp-sdk-ref }}
|
||||
|
||||
- name: Checkout ${{ env.SERVER_REPO }}
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.SERVER_REPO }}
|
||||
path: ${{ env.SERVER_DIR }}
|
||||
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: 8.x.x
|
||||
# cache: true
|
||||
# cache-dependency-path: "**/packages.lock.json"
|
||||
|
||||
- name: 🏗️ Build Server
|
||||
run: docker build --file "./packages/server/Dockerfile" --tag ${{ env.SPECKLE_SERVER_IMAGE }} .
|
||||
working-directory: ${{ env.SERVER_DIR }}
|
||||
|
||||
- name: ⚙️ Spin up Server
|
||||
run: docker compose --file "../${{ env.CLIENT_DIR }}/docker-compose-internal.yml" up --wait
|
||||
working-directory: ${{ env.SERVER_DIR }}
|
||||
env:
|
||||
SPECKLE_SERVER_IMAGE: ${{ env.SPECKLE_SERVER_IMAGE }}
|
||||
|
||||
- name: 📦 Restore .NET Solution
|
||||
run: dotnet restore ${{ env.SOLUTION }} --locked-mode
|
||||
working-directory: ${{ env.CLIENT_DIR }}
|
||||
|
||||
- name: 🏗️ Build .NET Solution
|
||||
run: dotnet build ${{ env.SOLUTION }} --configuration Release --no-restore -warnaserror
|
||||
working-directory: ${{ env.CLIENT_DIR }}
|
||||
|
||||
- name: 🔨 Run .NET Integration Tests
|
||||
run: dotnet test ${{ env.SOLUTION }} --filter "(Category=Integration)&(Server!=Public)" --configuration Release --no-build --no-restore --verbosity=normal
|
||||
working-directory: ${{ env.CLIENT_DIR }}
|
||||
@@ -0,0 +1,62 @@
|
||||
name: Integration Test
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
docker-compose-file:
|
||||
required: true
|
||||
type: string
|
||||
use-internal-image:
|
||||
default: false
|
||||
type: boolean
|
||||
secrets:
|
||||
CODECOV_TOKEN:
|
||||
required: true
|
||||
jobs:
|
||||
integration-test:
|
||||
env:
|
||||
Solution: "Speckle.Sdk.sln"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: 8.x.x
|
||||
cache: true
|
||||
cache-dependency-path: "**/packages.lock.json"
|
||||
|
||||
- name: 🔐 Login to Github Container Registry
|
||||
if: ${{ inputs.use-internal-image }}
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: "ghcr.io"
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ github.token }}
|
||||
|
||||
- name: ⚙️ Spin up Server
|
||||
run: docker compose --file ${{ inputs.docker-compose-file }} up --wait
|
||||
|
||||
- name: 📦 Restore
|
||||
run: dotnet restore ${{ env.Solution }} --locked-mode
|
||||
|
||||
- name: 🏗️ Build
|
||||
run: dotnet build ${{ env.Solution }} --configuration Release --no-restore -warnaserror
|
||||
|
||||
- name: 🔨 Integration Tests against Public Server
|
||||
if: ${{ !inputs.use-internal-image }}
|
||||
run: dotnet test ${{ env.Solution }} --filter "(Category=Integration)&(Server!=Internal)" --configuration Release --no-build --no-restore --verbosity=normal /p:AltCover=true /p:AltCoverAttributeFilter=ExcludeFromCodeCoverage
|
||||
|
||||
- name: 🔨 Integration Tests against Internal Server
|
||||
if: ${{ inputs.use-internal-image }}
|
||||
run: dotnet test ${{ env.Solution }} --filter "(Category=Integration)&(Server!=Public)" --configuration Release --no-build --no-restore --verbosity=normal /p:AltCover=true /p:AltCoverAttributeFilter=ExcludeFromCodeCoverage
|
||||
|
||||
- name: Upload coverage reports to Codecov with GitHub Action
|
||||
uses: codecov/codecov-action@v6
|
||||
continue-on-error: true
|
||||
with:
|
||||
fail_ci_if_error: true
|
||||
files: tests/**/coverage.xml
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
+55
-22
@@ -1,32 +1,65 @@
|
||||
name: .NET CI Build
|
||||
name: PR Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
pull_request: {}
|
||||
push:
|
||||
branches:
|
||||
- "main" # Need to run for codecov to compare against the BASE
|
||||
|
||||
|
||||
jobs:
|
||||
build:
|
||||
env:
|
||||
Solution: "Speckle.Sdk.sln"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.x.x
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: 8.x.x
|
||||
cache: true
|
||||
cache-dependency-path: "**/packages.lock.json"
|
||||
|
||||
- name: 🔫 Build All
|
||||
run: ./build.sh
|
||||
- name: 📦 Tool Restore
|
||||
run: dotnet tool restore
|
||||
|
||||
- name: 📄 Format
|
||||
run: dotnet csharpier check .
|
||||
|
||||
- name: 📦 Restore
|
||||
run: dotnet restore ${{ env.Solution }} --locked-mode
|
||||
|
||||
- name: 🏗️ Build
|
||||
run: dotnet build ${{ env.Solution }} --configuration Release --no-restore -warnaserror
|
||||
|
||||
- name: 🔨 Unit Tests
|
||||
run: dotnet test ${{ env.Solution }} --configuration Release --filter "Category!=Integration" --no-build --no-restore --verbosity=normal /p:AltCover=true /p:AltCoverAttributeFilter=ExcludeFromCodeCoverage
|
||||
|
||||
- name: 🎁 Pack
|
||||
run: dotnet pack ${{ env.Solution }} --configuration Release --no-build
|
||||
|
||||
- name: Upload coverage reports to Codecov with GitHub Action
|
||||
uses: codecov/codecov-action@v6
|
||||
continue-on-error: true
|
||||
with:
|
||||
fail_ci_if_error: true
|
||||
files: tests/**/coverage.xml
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
integration-test-internal:
|
||||
uses: "./.github/workflows/integration-test.yml"
|
||||
with:
|
||||
docker-compose-file: "docker-compose-internal.yml"
|
||||
use-internal-image: true
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
- name: Upload coverage reports to Codecov with GitHub Action
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
files: tests/**/coverage.xml
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
integration-test-public:
|
||||
uses: "./.github/workflows/integration-test.yml"
|
||||
with:
|
||||
docker-compose-file: "docker-compose.yml"
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -2,35 +2,56 @@ name: .NET Build and Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main", "dev"]
|
||||
tags: ["3.*.*"]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: "nuget.org"
|
||||
permissions:
|
||||
id-token: write # enable GitHub OIDC token issuance for this job
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: 8.x.x
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
|
||||
|
||||
cache: true
|
||||
cache-dependency-path: "**/packages.lock.json"
|
||||
|
||||
- id: set-version
|
||||
name: Set version to output
|
||||
run: |
|
||||
TAG=${{ github.ref_name }}
|
||||
if [[ "${{ github.ref }}" != refs/tags/* ]]; then
|
||||
TAG="3.0.99.${{ github.run_number }}"
|
||||
fi
|
||||
SEMVER="${TAG}"
|
||||
FILE_VERSION=$(echo "$TAG" | sed -E 's/^([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
|
||||
FILE_VERSION="$FILE_VERSION.${{ github.run_number }}"
|
||||
|
||||
echo "semver=$SEMVER" >> "$GITHUB_OUTPUT"
|
||||
echo "fileVersion=$FILE_VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo $SEMVER
|
||||
echo $FILE_VERSION
|
||||
|
||||
- name: 🔫 Build and Pack
|
||||
run: ./build.sh pack
|
||||
|
||||
- name: Upload coverage reports to Codecov with GitHub Action
|
||||
uses: codecov/codecov-action@v5
|
||||
env:
|
||||
SEMVER: ${{ steps.set-version.outputs.SEMVER }}
|
||||
FILE_VERSION: ${{ steps.set-version.outputs.FILE_VERSION }}
|
||||
|
||||
|
||||
- name: NuGet login (OIDC → temp API key)
|
||||
uses: NuGet/login@v1
|
||||
id: login
|
||||
with:
|
||||
files: tests/**/coverage.xml
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
user: ${{ secrets.NUGET_USER }}
|
||||
|
||||
- name: Push to nuget.org
|
||||
run: dotnet nuget push output/*.nupkg --source "https://api.nuget.org/v3/index.json" --api-key ${{secrets.CONNECTORS_NUGET_TOKEN }} --skip-duplicate
|
||||
run: dotnet nuget push output/*.nupkg --source "https://api.nuget.org/v3/index.json" --api-key ${{steps.login.outputs.NUGET_API_KEY}}
|
||||
|
||||
@@ -15,6 +15,7 @@ tests/TestArchives/Scratch
|
||||
tools
|
||||
.vscode
|
||||
.idea/
|
||||
.volumes/
|
||||
|
||||
.DS_Store
|
||||
*.snupkg
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
dotnet 8.0.400
|
||||
+8
-21
@@ -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
@@ -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' or '$(TestProjectAnalyserRules)' == 'true' ">
|
||||
<NoWarn>
|
||||
<!-- Things we need to test -->
|
||||
CS0618;CA1034;CA2201;CA1051;CA1040;CA1724;CA1065;
|
||||
IDE0044;IDE0130;CA1508;
|
||||
<!-- Analysers that provide no tangeable value to a test project -->
|
||||
CA5394;CA2007;CA1852;CA1819;CA1711;CA1063;CA1816;CA2234;CS8618;CA1054;CA1810;CA2208;CA1019;CA1831;
|
||||
$(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>
|
||||
|
||||
@@ -1,38 +1,42 @@
|
||||
<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.Bcl.AsyncInterfaces" Version="[9.0.4,)" />
|
||||
<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="Newtonsoft.Json.Schema" Version="4.0.1" />
|
||||
<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.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||
<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="System.Text.Json" Version="8.0.5" />
|
||||
<PackageVersion Include="xunit" Version="2.9.3" />
|
||||
<PackageVersion Include="xunit.assert" Version="2.9.3" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.2" />
|
||||
<GlobalPackageReference Include="PolySharp" Version="1.15.0" />
|
||||
<GlobalPackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
|
||||
<GlobalPackageReference Include="GitVersion.MsBuild" Version="5.12.0" />
|
||||
<GlobalPackageReference Include="Speckle.InterfaceGenerator" Version="0.9.6" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
next-version: 3.0.0
|
||||
mode: ContinuousDeployment
|
||||
assembly-informational-format: "{Major}.{Minor}.{Patch}-{PreReleaseTag}"
|
||||
branches:
|
||||
main:
|
||||
regex: ^main$
|
||||
tag: rc
|
||||
develop:
|
||||
tag: dev
|
||||
pull-request:
|
||||
tag: pr
|
||||
@@ -8,18 +8,22 @@ Speckle | Sharp | SDK
|
||||
|
||||
### .NET SDK, Tests, and Objects
|
||||
|
||||
[](https://codecov.io/gh/specklesystems/speckle-sharp-sdk)
|
||||
[](https://codecov.io/gh/specklesystems/speckle-sharp-sdk)
|
||||
<a href="https://www.nuget.org/packages/Speckle.Sdk/"><img alt="NuGet Version" src="https://img.shields.io/nuget/v/Speckle.Sdk?label=Speckle.Sdk"></a>
|
||||
<a href="https://www.nuget.org/packages/Speckle.Objects/"><img alt="NuGet Version" src="https://img.shields.io/nuget/v/Speckle.Sdk?label=Speckle.Objects"></a>
|
||||
<a href="https://www.nuget.org/packages/Speckle.Automate.Sdk/"><img alt="NuGet Version" src="https://img.shields.io/nuget/v/Speckle.Sdk?label=Speckle.Automate.Sdk"></a>
|
||||
|
||||
> [!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!
|
||||
> Releases Speckle.Sdk and Speckle.Objects are reliable for production use, but the APIs may not be wholly stable, and there may be breaking changes between releases, with little documentation.
|
||||
|
||||
# Repo structure
|
||||
|
||||
This repo is the home of our next-generation Speckle .NET SDK. It uses .NET Standard 2.0 and has been tested on Windows and MacOS.
|
||||
|
||||
- **SDK**
|
||||
- [`Speckle.Sdk`](https://github.com/specklesystems/speckle-sharp-sdk/tree/dev/src/Speckle.Sdk): Transports, serialization, API wrappers, and logging.
|
||||
- [`Speckle.Sdk`](https://github.com/specklesystems/speckle-sharp-sdk/tree/dev/src/Speckle.Sdk): Send/Receive operations, Serialization, API wrappers, and more!.
|
||||
- [`Speckle.Sdk.Dependencies`](https://github.com/specklesystems/speckle-sharp-sdk/tree/dev/src/Speckle.Sdk.Dependencies): Dependencies and code that shouldn't cause conflicts in Host Apps. This uses [IL Repack](https://github.com/gluck/il-repack) to merge together and interalized only to be used by Speckle.
|
||||
- [`Speckle.Automate.Sdk`](https://github.com/specklesystems/speckle-sharp-sdk/tree/dev/src/Speckle.Automate.Sdk): .NET SDK for [Speckle Automate](https://www.speckle.systems/product/automate)
|
||||
- **Speckle Objects**
|
||||
- [`Speckle.Objects`](https://github.com/specklesystems/speckle-sharp-sdk/tree/dev/src/Speckle.Objects): The Speckle Objects classes used for conversions.
|
||||
- **Tests**
|
||||
@@ -27,11 +31,13 @@ This repo is the home of our next-generation Speckle .NET SDK. It uses .NET Stan
|
||||
|
||||
### Other repos
|
||||
|
||||
Make sure to also check and ⭐️ these other Speckle next generation repositories:
|
||||
Make sure to also check and ⭐️ these other repositories:
|
||||
|
||||
- [`speckle-sharp-connectors`](https://github.com/specklesystems/speckle-sharp-connectors): our csharp repo of next gen connectors
|
||||
- [`speckle-sketchup`](https://github.com/specklesystems/speckle-sketchup): Sketchup connector
|
||||
- [`speckle-powerbi`](https://github.com/specklesystems/speckle-powerbi): PowerBi connector
|
||||
- [`speckle-sharp-connectors`](https://github.com/specklesystems/speckle-sharp-connectors): our csharp repo of next gen connectors.
|
||||
- [`speckle-server`](https://github.com/specklesystems/speckle-server): the speckle server.
|
||||
- [`speckle-sketchup`](https://github.com/specklesystems/speckle-blender): Blender connector.
|
||||
- [`speckle-sketchup`](https://github.com/specklesystems/speckle-sketchup): Sketchup connector.
|
||||
- [`speckle-powerbi`](https://github.com/specklesystems/speckle-powerbi): PowerBi connector.
|
||||
- and more [connectors & tooling](https://github.com/specklesystems/)!
|
||||
|
||||
## Documentation
|
||||
@@ -44,19 +50,27 @@ Comprehensive developer and user documentation can be found in our:
|
||||
|
||||
### Building
|
||||
|
||||
Make sure you clone this repository together with its submodules: `git clone https://github.com/specklesystems/speckle-sharp-sdk.git -recursive`.
|
||||
Afterwards, just restore all the NuGet packages and hit Build!
|
||||
Ensure you're using a [8.0.4xx](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) .NET SDK.
|
||||
After cloning this repository, just restore all the NuGet packages and hit Build!
|
||||
|
||||
### Developing
|
||||
|
||||
This project is evolving fast, to better understand how to use Core we suggest checking out the Unit and Integration tests. Running the integration tests locally requires a local server running on your computer.
|
||||
It is highly recommended you use
|
||||
- Either Jetbrains Rider or Visual Studio 2022
|
||||
- Ensure your IDE is set to use [the correct .NET SDK version](https://github.com/specklesystems/speckle-sharp-sdk/blob/main/global.json) (newer major versions may work, but may incorrectly run analysers we haven't configured)
|
||||
- You should install the cshapier plugin ([Rider](https://plugins.jetbrains.com/plugin/18243-csharpier), [VS](https://marketplace.visualstudio.com/items?itemName=csharpier.CSharpier)) and configure it to run on save
|
||||
|
||||
We'll be also adding [preliminary documentation on our forum](https://discourse.speckle.works/c/speckle-insider/10).
|
||||
Docs are a bit patchy [https://docs.speckle.systems/developers/looking-for-developer-docs](https://docs.speckle.systems/developers/looking-for-developer-docs)
|
||||
|
||||
### Tests
|
||||
|
||||
There are two test projects, one for unit tests and one for integration tests. The latter needs a server running locally in order to run.
|
||||
There are several test projects. It is a requirement that all tests pass for PRs to be merged.
|
||||
|
||||
The Integration test projects require a local server to be running.
|
||||
You must have docker installed. Then you can run `docker compose up` from the root of the repo to start the required containers.
|
||||
|
||||
In CI, they will be run against both the public and private versions of the server.
|
||||
It is important that we remain compatible with both server versions.
|
||||
## Contributing
|
||||
|
||||
Before embarking on submitting a patch, please make sure you read:
|
||||
|
||||
+20
-1
@@ -9,6 +9,9 @@ EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Objects.Tests.Unit", "tests\Speckle.Objects.Tests.Unit\Speckle.Objects.Tests.Unit.csproj", "{A0338FC0-3011-498F-AD09-01230FABD3ED}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5CB96C27-FC5B-4A41-86B6-951AF99B8116}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
src\graphql.config.yml = src\graphql.config.yml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{35047EE7-AD1D-4741-80A7-8F0E874718E9}"
|
||||
EndProject
|
||||
@@ -20,10 +23,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "config", "config", "{DA2AED
|
||||
Directory.Packages.props = Directory.Packages.props
|
||||
global.json = global.json
|
||||
README.md = README.md
|
||||
GitVersion.yml = GitVersion.yml
|
||||
docker-compose.yml = docker-compose.yml
|
||||
CodeMetricsConfig.txt = CodeMetricsConfig.txt
|
||||
Directory.Build.Targets = Directory.Build.Targets
|
||||
.config\dotnet-tools.json = .config\dotnet-tools.json
|
||||
docker-compose-internal.yml = docker-compose-internal.yml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{58D37DA9-F948-48CA-9A73-F5BBBD533DBF}"
|
||||
@@ -38,6 +42,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.github\workflows\pr.yml = .github\workflows\pr.yml
|
||||
.github\workflows\release.yml = .github\workflows\release.yml
|
||||
.github\workflows\integration-test.yml = .github\workflows\integration-test.yml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Sdk.Tests.Performance", "tests\Speckle.Sdk.Tests.Performance\Speckle.Sdk.Tests.Performance.csproj", "{870E3396-E6F7-43AE-B120-E651FA4F46BD}"
|
||||
@@ -50,6 +55,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Sdk.Testing", "test
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "performance", "performance", "{FFB07238-87E8-463A-AA39-3B38AAAA94C1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Automate.Sdk", "src\Speckle.Automate.Sdk\Speckle.Automate.Sdk.csproj", "{4EB20EFA-5A38-415E-B3FD-29CA3ACD1EF5}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Automate.Sdk.Integration", "tests\Speckle.Automate.Sdk.Integration\Speckle.Automate.Sdk.Integration.csproj", "{B6129DC3-F285-4E5F-85E2-6D2533A4005E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -104,6 +113,14 @@ Global
|
||||
{7B617C0D-2354-415C-993C-5071D4113E27}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7B617C0D-2354-415C-993C-5071D4113E27}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7B617C0D-2354-415C-993C-5071D4113E27}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4EB20EFA-5A38-415E-B3FD-29CA3ACD1EF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4EB20EFA-5A38-415E-B3FD-29CA3ACD1EF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4EB20EFA-5A38-415E-B3FD-29CA3ACD1EF5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4EB20EFA-5A38-415E-B3FD-29CA3ACD1EF5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B6129DC3-F285-4E5F-85E2-6D2533A4005E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B6129DC3-F285-4E5F-85E2-6D2533A4005E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B6129DC3-F285-4E5F-85E2-6D2533A4005E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B6129DC3-F285-4E5F-85E2-6D2533A4005E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{A413E196-3696-4F48-B635-04B5F76BF9C9} = {5CB96C27-FC5B-4A41-86B6-951AF99B8116}
|
||||
@@ -118,5 +135,7 @@ Global
|
||||
{7B617C0D-2354-415C-993C-5071D4113E27} = {35047EE7-AD1D-4741-80A7-8F0E874718E9}
|
||||
{FF922B6D-D416-4348-8CB8-0C8B28691070} = {FFB07238-87E8-463A-AA39-3B38AAAA94C1}
|
||||
{870E3396-E6F7-43AE-B120-E651FA4F46BD} = {FFB07238-87E8-463A-AA39-3B38AAAA94C1}
|
||||
{4EB20EFA-5A38-415E-B3FD-29CA3ACD1EF5} = {5CB96C27-FC5B-4A41-86B6-951AF99B8116}
|
||||
{B6129DC3-F285-4E5F-85E2-6D2533A4005E} = {35047EE7-AD1D-4741-80A7-8F0E874718E9}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<Solution>
|
||||
<Folder Name="/build/">
|
||||
<Project Path="build/build.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/config/">
|
||||
<File Path=".config/dotnet-tools.json" />
|
||||
<File Path=".csharpierrc.yaml" />
|
||||
<File Path=".editorconfig" />
|
||||
<File Path="CodeMetricsConfig.txt" />
|
||||
<File Path="Directory.Build.props" />
|
||||
<File Path="Directory.Build.Targets" />
|
||||
<File Path="Directory.Packages.props" />
|
||||
<File Path="docker-compose-internal.yml" />
|
||||
<File Path="docker-compose.yml" />
|
||||
<File Path="global.json" />
|
||||
<File Path="README.md" />
|
||||
<File Path=".github\copilot-instructions.md" />
|
||||
<File Path=".github\git-commit-instructions.md" />
|
||||
</Folder>
|
||||
<Folder Name="/config/workflows/">
|
||||
<File Path=".github/workflows/integration-test-callable-from-server-repo.yml" />
|
||||
<File Path=".github/workflows/integration-test.yml" />
|
||||
<File Path=".github/workflows/pr.yml" />
|
||||
<File Path=".github/workflows/release.yml" />
|
||||
</Folder>
|
||||
<Folder Name="/performance/">
|
||||
<Project Path="tests/Speckle.Sdk.Serialization.Testing/Speckle.Sdk.Serialization.Testing.csproj" />
|
||||
<Project Path="tests/Speckle.Sdk.Tests.Performance/Speckle.Sdk.Tests.Performance.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/src/">
|
||||
<Project Path="src/Speckle.Automate.Sdk/Speckle.Automate.Sdk.csproj" />
|
||||
<Project Path="src/Speckle.Objects/Speckle.Objects.csproj" />
|
||||
<Project Path="src/Speckle.Sdk.Dependencies/Speckle.Sdk.Dependencies.csproj" />
|
||||
<Project Path="src/Speckle.Sdk/Speckle.Sdk.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/tests/">
|
||||
<Project Path="tests/Speckle.Sdk.Testing/Speckle.Sdk.Testing.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/tests/integration/">
|
||||
<Project Path="tests/Speckle.Automate.Sdk.Integration/Speckle.Automate.Sdk.Integration.csproj" />
|
||||
<Project Path="tests/Speckle.Sdk.Tests.Integration/Speckle.Sdk.Tests.Integration.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/tests/unit/">
|
||||
<Project Path="tests/Speckle.Objects.Tests.Unit/Speckle.Objects.Tests.Unit.csproj" />
|
||||
<Project Path="tests/Speckle.Sdk.Serialization.Tests/Speckle.Sdk.Serialization.Tests.csproj" />
|
||||
<Project Path="tests/Speckle.Sdk.Tests.Unit/Speckle.Sdk.Tests.Unit.csproj" />
|
||||
</Folder>
|
||||
</Solution>
|
||||
+36
-13
@@ -11,11 +11,19 @@ const string BUILD = "build";
|
||||
const string TEST = "test";
|
||||
const string INTEGRATION = "integration";
|
||||
const string PACK = "pack";
|
||||
const string PACK_LOCAL = "pack-local";
|
||||
const string CLEAN_LOCKS = "clean-locks";
|
||||
const string PERF = "perf";
|
||||
const string DEEP_CLEAN = "deep-clean";
|
||||
|
||||
static (string semver, string fileVerison) GetVersions()
|
||||
{
|
||||
string semver =
|
||||
Environment.GetEnvironmentVariable("SEMVER") ?? throw new ArgumentException("Expected SEMVER env var");
|
||||
string fileVersion =
|
||||
Environment.GetEnvironmentVariable("FILE_VERSION") ?? throw new ArgumentException("Expected FILE_VERSION env var");
|
||||
return (semver, fileVersion);
|
||||
}
|
||||
|
||||
Target(
|
||||
CLEAN_LOCKS,
|
||||
() =>
|
||||
@@ -32,7 +40,7 @@ Target(
|
||||
|
||||
Target(
|
||||
CLEAN,
|
||||
ForEach("**/output"),
|
||||
forEach: ["**/output"],
|
||||
dir =>
|
||||
{
|
||||
IEnumerable<string> GetDirectories(string d)
|
||||
@@ -58,22 +66,28 @@ 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, () => 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 () =>
|
||||
{
|
||||
await RunAsync("dotnet", $"build Speckle.Sdk.sln -c Release --no-restore -warnaserror").ConfigureAwait(false);
|
||||
var (version, fileVersion) = GetVersions();
|
||||
Console.WriteLine($"Version: {version} & {fileVersion}");
|
||||
await RunAsync(
|
||||
"dotnet",
|
||||
$"build Speckle.Sdk.sln -c Release --no-restore -warnaserror -p:Version={version} -p:FileVersion={fileVersion}"
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
);
|
||||
|
||||
Target(
|
||||
TEST,
|
||||
DependsOn(BUILD),
|
||||
dependsOn: [BUILD],
|
||||
Glob.Files(".", "**/*.Tests.Unit.csproj").Concat(Glob.Files(".", "**/*.Tests.csproj")),
|
||||
async file =>
|
||||
{
|
||||
@@ -87,7 +101,7 @@ Target(
|
||||
|
||||
Target(
|
||||
INTEGRATION,
|
||||
DependsOn(BUILD),
|
||||
dependsOn: [BUILD],
|
||||
async () =>
|
||||
{
|
||||
await RunAsync("docker", "compose -f docker-compose.yml up --wait").ConfigureAwait(false);
|
||||
@@ -152,11 +166,20 @@ Target(
|
||||
}
|
||||
);
|
||||
|
||||
static Task RunPack() => RunAsync("dotnet", "pack Speckle.Sdk.sln -c Release -o output --no-build");
|
||||
Target(
|
||||
PACK,
|
||||
dependsOn: [TEST],
|
||||
async () =>
|
||||
{
|
||||
{
|
||||
var (version, fileVersion) = GetVersions();
|
||||
Console.WriteLine($"Version: {version} & {fileVersion}");
|
||||
await RunAsync("dotnet", $"pack Speckle.Sdk.sln -c Release -o output --no-build -p:Version={version}")
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
Target(PACK, DependsOn(TEST), RunPack);
|
||||
Target(PACK_LOCAL, DependsOn(BUILD), RunPack);
|
||||
|
||||
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
@@ -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" />
|
||||
|
||||
@@ -4,15 +4,9 @@
|
||||
"net8.0": {
|
||||
"Bullseye": {
|
||||
"type": "Direct",
|
||||
"requested": "[5.0.0, )",
|
||||
"resolved": "5.0.0",
|
||||
"contentHash": "bqyt+m17ym+5aN45C5oZRAjuLDt8jKiCm/ys1XfymIXSkrTFwvI/QsbY3ucPSHDz7SF7uON7B57kXFv5H2k1ew=="
|
||||
},
|
||||
"GitVersion.MsBuild": {
|
||||
"type": "Direct",
|
||||
"requested": "[5.12.0, )",
|
||||
"resolved": "5.12.0",
|
||||
"contentHash": "dJuigXycpJNOiLT9or7mkHSkGFHgGW3/p6cNNYEKZBa7Hhp1FdX/cvqYWWYhRLpfoZOedeA7aRbYiOB3vW/dvA=="
|
||||
"requested": "[6.0.0, )",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "vgwwXfzs7jJrskWH7saHRMgPzziq/e86QZNWY1MnMxd7e+De7E7EX4K3C7yrvaK9y02SJoLxNxcLG/q5qUAghw=="
|
||||
},
|
||||
"Glob": {
|
||||
"type": "Direct",
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
name: "speckle-server"
|
||||
|
||||
services:
|
||||
####
|
||||
# Speckle Server dependencies
|
||||
#######
|
||||
postgres:
|
||||
image: "postgres:16.4-alpine3.20@sha256:d898b0b78a2627cb4ee63464a14efc9d296884f1b28c841b0ab7d7c42f1fffdf"
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_DB: speckle
|
||||
POSTGRES_USER: speckle
|
||||
POSTGRES_PASSWORD: speckle
|
||||
volumes:
|
||||
- ./.volumes/postgres-data:/var/lib/postgresql/data/
|
||||
healthcheck:
|
||||
# the -U user has to match the POSTGRES_USER value
|
||||
test: ["CMD-SHELL", "pg_isready -U speckle"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
|
||||
redis:
|
||||
image: "valkey/valkey:8.1-alpine@sha256:0d27f0bca0249f61d060029a6aaf2e16b2c417d68d02a508e1dfb763fa2948b4"
|
||||
restart: always
|
||||
volumes:
|
||||
- ./.volumes/redis-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
|
||||
minio:
|
||||
image: "minio/minio:RELEASE.2023-10-25T06-33-25Z"
|
||||
command: server /data --console-address ":9001"
|
||||
restart: always
|
||||
volumes:
|
||||
- ./.volumes/minio-data:/data
|
||||
ports:
|
||||
- '127.0.0.1:9000:9000'
|
||||
- '127.0.0.1:9001:9001'
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
"curl -s -o /dev/null http://127.0.0.1:9000/minio/index.html",
|
||||
]
|
||||
interval: 5s
|
||||
timeout: 30s
|
||||
retries: 30
|
||||
start_period: 10s
|
||||
|
||||
speckle-server:
|
||||
image: ${SPECKLE_SERVER_IMAGE:-ghcr.io/specklesystems/speckle-server:latest}
|
||||
restart: always
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- /nodejs/bin/node
|
||||
- -e
|
||||
- "try { require('node:http').request({headers: {'Content-Type': 'application/json'}, port:3000, hostname:'127.0.0.1', path:'/readiness', method: 'GET', timeout: 2000 }, (res) => { body = ''; res.on('data', (chunk) => {body += chunk;}); res.on('end', () => {process.exit(Number(res.statusCode != 200 || body.toLowerCase().includes('error')));}); }).end(); } catch { process.exit(1); }"
|
||||
interval: 10s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 90s
|
||||
ports:
|
||||
- "0.0.0.0:3000:3000"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
minio:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
# TODO: Change this to the URL of the speckle server, as accessed from the network
|
||||
CANONICAL_URL: "http://127.0.0.1:8080"
|
||||
SPECKLE_AUTOMATE_URL: "http://127.0.0.1:3030"
|
||||
FRONTEND_ORIGIN: "http://127.0.0.1:8081"
|
||||
|
||||
# TODO: Change thvolumes:
|
||||
REDIS_URL: "redis://redis"
|
||||
|
||||
S3_ENDPOINT: "http://minio:9000"
|
||||
S3_PUBLIC_ENDPOINT: "http://127.0.0.1:9000"
|
||||
S3_ACCESS_KEY: "minioadmin"
|
||||
S3_SECRET_KEY: "minioadmin"
|
||||
S3_BUCKET: "speckle-server"
|
||||
S3_CREATE_BUCKET: "true"
|
||||
|
||||
FILE_SIZE_LIMIT_MB: 100
|
||||
MAX_PROJECT_MODELS_PER_PAGE: 500
|
||||
|
||||
# TODO: Change this to a unique secret for this server
|
||||
SESSION_SECRET: "TODO:ReplaceWithLongString"
|
||||
|
||||
STRATEGY_LOCAL: "true"
|
||||
|
||||
POSTGRES_URL: 'postgres://speckle:speckle@postgres:5432/speckle'
|
||||
ENABLE_MP: "false"
|
||||
|
||||
LOG_PRETTY: "true"
|
||||
|
||||
FF_NEXT_GEN_FILE_IMPORTER_ENABLED: "true"
|
||||
FF_LARGE_FILE_IMPORTS_ENABLED: "true"
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: speckle-server
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
redis-data:
|
||||
minio-data:
|
||||
+15
-8
@@ -1,4 +1,3 @@
|
||||
version: "3.9"
|
||||
name: "speckle-server"
|
||||
|
||||
services:
|
||||
@@ -13,7 +12,7 @@ services:
|
||||
POSTGRES_USER: speckle
|
||||
POSTGRES_PASSWORD: speckle
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data/
|
||||
- ./.volumes/postgres-data:/var/lib/postgresql/data/
|
||||
healthcheck:
|
||||
# the -U user has to match the POSTGRES_USER value
|
||||
test: ["CMD-SHELL", "pg_isready -U speckle"]
|
||||
@@ -22,10 +21,10 @@ services:
|
||||
retries: 30
|
||||
|
||||
redis:
|
||||
image: "redis:6.0-alpine"
|
||||
image: "valkey/valkey:8.1-alpine@sha256:0d27f0bca0249f61d060029a6aaf2e16b2c417d68d02a508e1dfb763fa2948b4"
|
||||
restart: always
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
- ./.volumes/redis-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
|
||||
interval: 5s
|
||||
@@ -37,7 +36,10 @@ services:
|
||||
command: server /data --console-address ":9001"
|
||||
restart: always
|
||||
volumes:
|
||||
- minio-data:/data
|
||||
- ./.volumes/minio-data:/data
|
||||
ports:
|
||||
- '127.0.0.1:9000:9000'
|
||||
- '127.0.0.1:9001:9001'
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
@@ -57,7 +59,7 @@ services:
|
||||
- CMD
|
||||
- /nodejs/bin/node
|
||||
- -e
|
||||
- "try { require('node:http').request({headers: {'Content-Type': 'application/json'}, port:3000, hostname:'127.0.0.1', path:'/readiness', method: 'GET', timeout: 2000 }, (res) => { body = ''; res.on('data', (chunk) => {body += chunk;}); res.on('end', () => {process.exit(res.statusCode != 200 || body.toLowerCase().includes('error'));}); }).end(); } catch { process.exit(1); }"
|
||||
- "try { require('node:http').request({headers: {'Content-Type': 'application/json'}, port:3000, hostname:'127.0.0.1', path:'/readiness', method: 'GET', timeout: 2000 }, (res) => { body = ''; res.on('data', (chunk) => {body += chunk;}); res.on('end', () => {process.exit(Number(res.statusCode != 200 || body.toLowerCase().includes('error')));}); }).end(); } catch { process.exit(1); }"
|
||||
interval: 10s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
@@ -81,6 +83,7 @@ services:
|
||||
REDIS_URL: "redis://redis"
|
||||
|
||||
S3_ENDPOINT: "http://minio:9000"
|
||||
S3_PUBLIC_ENDPOINT: "http://127.0.0.1:9000"
|
||||
S3_ACCESS_KEY: "minioadmin"
|
||||
S3_SECRET_KEY: "minioadmin"
|
||||
S3_BUCKET: "speckle-server"
|
||||
@@ -93,7 +96,6 @@ services:
|
||||
SESSION_SECRET: "TODO:ReplaceWithLongString"
|
||||
|
||||
STRATEGY_LOCAL: "true"
|
||||
DEBUG: "speckle:*"
|
||||
|
||||
POSTGRES_URL: "postgres"
|
||||
POSTGRES_USER: "speckle"
|
||||
@@ -101,6 +103,11 @@ services:
|
||||
POSTGRES_DB: "speckle"
|
||||
ENABLE_MP: "false"
|
||||
|
||||
LOG_PRETTY: "true"
|
||||
|
||||
FF_NEXT_GEN_FILE_IMPORTER_ENABLED: "true"
|
||||
FF_LARGE_FILE_IMPORTS_ENABLED: "true"
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: speckle-server
|
||||
@@ -108,4 +115,4 @@ networks:
|
||||
volumes:
|
||||
postgres-data:
|
||||
redis-data:
|
||||
minio-data:
|
||||
minio-data:
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text.Json;
|
||||
using GraphQL;
|
||||
using GraphQL.Client.Http;
|
||||
using Speckle.Automate.Sdk.Schema;
|
||||
using Speckle.InterfaceGenerator;
|
||||
using Speckle.Sdk.Api;
|
||||
using Speckle.Sdk.Credentials;
|
||||
|
||||
namespace Speckle.Automate.Sdk;
|
||||
|
||||
[GenerateAutoInterface(VisibilityModifier = "public")]
|
||||
internal sealed class AutomationContextFactory(
|
||||
IClientFactory clientFactory,
|
||||
IAccountFactory accountFactory,
|
||||
IOperations operations
|
||||
) : IAutomationContextFactory
|
||||
{
|
||||
private static readonly JsonSerializerOptions s_jsonSerializerSettings = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
};
|
||||
|
||||
/// <inheritdoc cref="Initialize(AutomationRunData, string)"/>
|
||||
public async Task<IAutomationContext> Initialize(string automationRunData, string speckleToken)
|
||||
{
|
||||
var runData = JsonSerializer.Deserialize<AutomationRunData>(automationRunData, s_jsonSerializerSettings);
|
||||
|
||||
return await Initialize(runData, speckleToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Initialize(AutomationRunData, Account)"/>
|
||||
/// <exception cref="GraphQLHttpRequestException">Request failed on the HTTP layer (received a non-successful response code)</exception>
|
||||
/// <exception cref="AggregateException"><inheritdoc cref="Speckle.Sdk.Api.GraphQL.GraphQLErrorHandler.EnsureGraphQLSuccess(IGraphQLResponse)"/></exception>
|
||||
public async Task<IAutomationContext> Initialize(AutomationRunData automationRunData, string speckleToken)
|
||||
{
|
||||
Account account = await accountFactory
|
||||
.CreateAccount(automationRunData.SpeckleServerUrl, speckleToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return Initialize(automationRunData, account);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="AutomationContext"/> from the provided data
|
||||
/// </summary>
|
||||
public IAutomationContext Initialize(AutomationRunData automationRunData, Account account)
|
||||
{
|
||||
IClient client = clientFactory.Create(account);
|
||||
Stopwatch initTime = Stopwatch.StartNew();
|
||||
|
||||
return new AutomationContext(operations)
|
||||
{
|
||||
AutomationRunData = automationRunData,
|
||||
SpeckleClient = client,
|
||||
_speckleToken = account.token,
|
||||
_initTime = initTime,
|
||||
AutomationResult = new AutomationResult(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using GraphQL;
|
||||
using Speckle.Automate.Sdk.Schema;
|
||||
using Speckle.Automate.Sdk.Schema.Triggers;
|
||||
using Speckle.InterfaceGenerator;
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Sdk;
|
||||
using Speckle.Sdk.Api;
|
||||
using Speckle.Sdk.Api.GraphQL.Inputs;
|
||||
using Speckle.Sdk.Api.GraphQL.Models;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
using Version = Speckle.Sdk.Api.GraphQL.Models.Version;
|
||||
|
||||
namespace Speckle.Automate.Sdk;
|
||||
|
||||
[GenerateAutoInterface(VisibilityModifier = "public")]
|
||||
internal sealed class AutomationContext(IOperations operations) : IAutomationContext
|
||||
{
|
||||
public AutomationRunData AutomationRunData { get; set; }
|
||||
public string? ContextView
|
||||
{
|
||||
get => AutomationResult.ResultView;
|
||||
private set => AutomationResult.ResultView = value;
|
||||
}
|
||||
public required IClient SpeckleClient { get; init; }
|
||||
|
||||
public required string _speckleToken { get; init; }
|
||||
|
||||
// added for performance measuring
|
||||
public required Stopwatch _initTime { get; init; }
|
||||
|
||||
public required AutomationResult AutomationResult { get; init; }
|
||||
|
||||
public string RunStatus => AutomationResult.RunStatus;
|
||||
|
||||
public string? StatusMessage => AutomationResult.StatusMessage;
|
||||
public TimeSpan Elapsed => _initTime.Elapsed;
|
||||
|
||||
/// <summary>
|
||||
/// Receive version for automation.
|
||||
/// </summary>
|
||||
/// <returns> Commit object. </returns>
|
||||
/// <exception cref="SpeckleException">Throws if commit object is null.</exception>
|
||||
public async Task<Base> ReceiveVersion(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// TODO: this is a quick hack to keep implementation consistency. Move to proper receive many versions
|
||||
if (AutomationRunData.Triggers.First() is not VersionCreationTrigger trigger)
|
||||
{
|
||||
throw new SpeckleException("Processed automation run data without any triggers");
|
||||
}
|
||||
var versionId = trigger.Payload.VersionId;
|
||||
|
||||
var version = await SpeckleClient
|
||||
.Version.Get(versionId, AutomationRunData.ProjectId, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (version.referencedObject == null)
|
||||
{
|
||||
throw new SpeckleException(
|
||||
"The requested speckle model version has exceeded workspace version history limits or the reference object is otherwise null"
|
||||
);
|
||||
}
|
||||
|
||||
Base rootObject = await operations
|
||||
.Receive2(
|
||||
SpeckleClient.ServerUrl,
|
||||
AutomationRunData.ProjectId,
|
||||
version.referencedObject,
|
||||
SpeckleClient.Account.token,
|
||||
null,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await SpeckleClient
|
||||
.Version.Received(new(version.id, AutomationRunData.ProjectId, "automate_function"), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
Console.WriteLine($"It took {Elapsed.TotalSeconds} seconds to receive the speckle version {versionId}");
|
||||
return rootObject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates new version in the project.
|
||||
/// </summary>
|
||||
/// <param name="rootObject">Object to send to project.</param>
|
||||
/// <param name="model">The model to create the version under</param>
|
||||
/// <param name="versionMessage">Version message.</param>
|
||||
/// <param name="cancellationToken">Version message.</param>
|
||||
/// <returns>Version id.</returns>
|
||||
/// <exception cref="SpeckleException"> Throws if given model name is as same as with model name in automation run data.
|
||||
/// The reason is to prevent circular run loop in automation.</exception>
|
||||
public async Task<Version> CreateNewVersionInProject(
|
||||
Base rootObject,
|
||||
Model model,
|
||||
string versionMessage = "",
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
// Confirm target branch is not the same as source branch
|
||||
foreach (var trigger in AutomationRunData.Triggers)
|
||||
{
|
||||
if (trigger.Payload.ModelId == model.id)
|
||||
{
|
||||
throw new SpeckleException(
|
||||
$"""
|
||||
The target model: {model.name} ({model.id}) cannot match the model
|
||||
that triggered this automation:
|
||||
{trigger.Payload.ModelId}
|
||||
"""
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var (rootObjectId, _) = await operations
|
||||
.Send2(
|
||||
SpeckleClient.ServerUrl,
|
||||
AutomationRunData.ProjectId,
|
||||
SpeckleClient.Account.token,
|
||||
rootObject,
|
||||
null,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var newVersion = await SpeckleClient
|
||||
.Version.Create(
|
||||
new CreateVersionInput(rootObjectId, model.id, AutomationRunData.ProjectId, versionMessage),
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
AutomationResult.ResultVersions.Add(newVersion.id);
|
||||
|
||||
return newVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set context view for automation result view.
|
||||
/// </summary>
|
||||
/// <param name="resourceIds"> Resource contexts to bind into view.</param>
|
||||
/// <param name="includeSourceModelVersion"> Whether bind source version into result view or not.</param>
|
||||
/// <exception cref="SpeckleException"> Throws if there is no context to create result view.</exception>
|
||||
[MemberNotNull(nameof(ContextView))]
|
||||
[AutoInterfaceIgnore] //Ignore so we can explicitly add the MemberNotNull attibute to the interface method
|
||||
public void SetContextView(IReadOnlyCollection<string>? resourceIds = null, bool includeSourceModelVersion = true)
|
||||
{
|
||||
List<string> linkResources = new();
|
||||
if (includeSourceModelVersion)
|
||||
{
|
||||
foreach (var trigger in AutomationRunData.Triggers)
|
||||
{
|
||||
switch (trigger)
|
||||
{
|
||||
case VersionCreationTrigger versionCreationTrigger:
|
||||
{
|
||||
linkResources.Add($"{versionCreationTrigger.Payload.ModelId}@{versionCreationTrigger.Payload.VersionId}");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new SpeckleException($"Could not link resource specified by {trigger.TriggerType} trigger");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resourceIds is not null)
|
||||
{
|
||||
linkResources.AddRange(resourceIds);
|
||||
}
|
||||
|
||||
if (linkResources.Count == 0)
|
||||
{
|
||||
throw new SpeckleException("We do not have enough resource ids to compose a context view");
|
||||
}
|
||||
|
||||
ContextView = $"/projects/{AutomationRunData.ProjectId}/models/{string.Join(",", linkResources)}";
|
||||
}
|
||||
|
||||
public async Task ReportRunStatus()
|
||||
{
|
||||
ObjectResults? objectResults = null;
|
||||
if (RunStatus is "SUCCEEDED" or "FAILED")
|
||||
{
|
||||
objectResults = new ObjectResults
|
||||
{
|
||||
Version = 2,
|
||||
Values = new ObjectResultValues
|
||||
{
|
||||
BlobIds = AutomationResult.Blobs,
|
||||
ObjectResults = AutomationResult.ObjectResults,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
//language=graphql
|
||||
const string QUERY = """
|
||||
mutation AutomateFunctionRunStatusReport($projectId: String!, $functionRunId: String!, $status: AutomateRunStatus!, $statusMessage: String, $results: JSONObject, $contextView: String) {
|
||||
automateFunctionRunStatusReport(
|
||||
input: {projectId: $projectId, functionRunId: $functionRunId, status: $status, statusMessage: $statusMessage, contextView: $contextView, results: $results}
|
||||
)
|
||||
}
|
||||
""";
|
||||
GraphQLRequest request = new()
|
||||
{
|
||||
Query = QUERY,
|
||||
Variables = new
|
||||
{
|
||||
projectId = AutomationRunData.ProjectId,
|
||||
functionRunId = AutomationRunData.FunctionRunId,
|
||||
status = RunStatus,
|
||||
statusMessage = AutomationResult.StatusMessage,
|
||||
contextView = ContextView,
|
||||
results = objectResults,
|
||||
},
|
||||
};
|
||||
await SpeckleClient.ExecuteGraphQLRequest<Dictionary<string, object>>(request).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores result file in automation result. It will be available to download on Frontend if added.
|
||||
/// </summary>
|
||||
/// <param name="filePath"> File path to store.</param>
|
||||
/// <exception cref="FileNotFoundException"> Throws if given file path is not exist.</exception>
|
||||
/// <exception cref="SpeckleException"> Throws if upload requests return no result.</exception>
|
||||
public async Task StoreFileResult(string filePath)
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
throw new FileNotFoundException("The given file path doesn't exist", fileName: filePath);
|
||||
}
|
||||
|
||||
using MultipartFormDataContent formData = new();
|
||||
FileStream fileStream = new(filePath, FileMode.Open, FileAccess.Read);
|
||||
using StreamContent streamContent = new(fileStream);
|
||||
formData.Add(streamContent, "files", Path.GetFileName(filePath));
|
||||
HttpResponseMessage request = await SpeckleClient
|
||||
.GQLClient.HttpClient.PostAsync(
|
||||
new Uri(AutomationRunData.SpeckleServerUrl, $"api/stream/{AutomationRunData.ProjectId}/blob"),
|
||||
formData
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
request.EnsureSuccessStatusCode();
|
||||
string responseString = await request.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
Console.WriteLine("RESPONSE - " + responseString);
|
||||
BlobUploadResponse uploadResponse = JsonConvert.DeserializeObject<BlobUploadResponse>(responseString);
|
||||
if (uploadResponse.UploadResults.Count != 1)
|
||||
{
|
||||
throw new SpeckleException("Expected one upload result.");
|
||||
}
|
||||
|
||||
AutomationResult.Blobs.AddRange(uploadResponse.UploadResults.Select(r => r.BlobId));
|
||||
}
|
||||
|
||||
private void MarkRun(AutomationStatus status, string? statusMessage)
|
||||
{
|
||||
double duration = Elapsed.TotalSeconds;
|
||||
AutomationResult.StatusMessage = statusMessage;
|
||||
string statusValue = AutomationStatusMapping.Get(status);
|
||||
AutomationResult.RunStatus = statusValue;
|
||||
AutomationResult.Elapsed = duration;
|
||||
|
||||
string msg = $"Automation run {statusValue} after {duration} seconds.";
|
||||
if (statusMessage is not null)
|
||||
{
|
||||
msg += $"\n{statusMessage}";
|
||||
}
|
||||
|
||||
Console.WriteLine(msg);
|
||||
}
|
||||
|
||||
public void MarkRunFailed(string statusMessage) => MarkRun(AutomationStatus.Failed, statusMessage);
|
||||
|
||||
public void MarkRunException(string? statusMessage) => MarkRun(AutomationStatus.Exception, statusMessage);
|
||||
|
||||
public void MarkRunSuccess(string? statusMessage) => MarkRun(AutomationStatus.Succeeded, statusMessage);
|
||||
|
||||
/// <summary>
|
||||
/// Add a new error case to the run results.
|
||||
/// </summary>
|
||||
/// <param name="category">A short tag for the error type.</param>
|
||||
/// <param name="affectedObjects">A list of objects that are causing the result.</param>
|
||||
/// <param name="message">Optional error message.</param>
|
||||
/// <param name="metadata">User provided metadata key value pairs.</param>
|
||||
/// <param name="visualOverrides">Case specific 3D visual overrides.</param>
|
||||
/// <exception cref="ArgumentException">Throws if the provided <paramref name="affectedObjects"/> input is empty.</exception>
|
||||
public void AttachErrorToObjects(
|
||||
string category,
|
||||
IReadOnlyCollection<Base> affectedObjects,
|
||||
string? message = null,
|
||||
Dictionary<string, object>? metadata = null,
|
||||
Dictionary<string, object>? visualOverrides = null
|
||||
) => AttachResultToObjects(ObjectResultLevel.Error, category, affectedObjects, message, metadata, visualOverrides);
|
||||
|
||||
/// <summary>
|
||||
/// Add a new warning case to the run results.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="AttachErrorToObjects"/>
|
||||
public void AttachWarningToObjects(
|
||||
string category,
|
||||
IReadOnlyCollection<Base> affectedObjects,
|
||||
string? message = null,
|
||||
Dictionary<string, object>? metadata = null,
|
||||
Dictionary<string, object>? visualOverrides = null
|
||||
) => AttachResultToObjects(ObjectResultLevel.Warning, category, affectedObjects, message, metadata, visualOverrides);
|
||||
|
||||
/// <summary>
|
||||
/// Add a new info case to the run results.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="AttachErrorToObjects"/>
|
||||
public void AttachInfoToObjects(
|
||||
string category,
|
||||
IReadOnlyCollection<Base> affectedObjects,
|
||||
string? message = null,
|
||||
Dictionary<string, object>? metadata = null,
|
||||
Dictionary<string, object>? visualOverrides = null
|
||||
) => AttachResultToObjects(ObjectResultLevel.Info, category, affectedObjects, message, metadata, visualOverrides);
|
||||
|
||||
/// <summary>
|
||||
/// Add a new success case to the run results.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="AttachErrorToObjects"/>
|
||||
public void AttachSuccessToObjects(
|
||||
string category,
|
||||
IReadOnlyCollection<Base> affectedObjects,
|
||||
string? message = null,
|
||||
Dictionary<string, object>? metadata = null,
|
||||
Dictionary<string, object>? visualOverrides = null
|
||||
) => AttachResultToObjects(ObjectResultLevel.Success, category, affectedObjects, message, metadata, visualOverrides);
|
||||
|
||||
/// <summary>
|
||||
/// Add a new case to the run results.
|
||||
/// </summary>
|
||||
/// <param name="level">The level assigned to this result.</param>
|
||||
/// <inheritdoc cref="AttachErrorToObjects"/>
|
||||
public void AttachResultToObjects(
|
||||
ObjectResultLevel level,
|
||||
string category,
|
||||
IReadOnlyCollection<Base> affectedObjects,
|
||||
string? message = null,
|
||||
Dictionary<string, object>? metadata = null,
|
||||
Dictionary<string, object>? visualOverrides = null
|
||||
)
|
||||
{
|
||||
if (affectedObjects.Count == 0)
|
||||
{
|
||||
throw new ArgumentException($"Need at least one affected object to report a(n) {level}");
|
||||
}
|
||||
|
||||
string levelString = ObjectResultLevelMapping.Get(level);
|
||||
Dictionary<string, string?> ids = affectedObjects.ToDictionary(
|
||||
x => x.id.NotNull($"You can only attach {level} results to objects with an id"),
|
||||
x => x.applicationId
|
||||
);
|
||||
|
||||
Console.WriteLine($"Created new {levelString.ToUpper()} category: {category} caused by: {message}");
|
||||
|
||||
ResultCase resultCase = new()
|
||||
{
|
||||
Category = category,
|
||||
Level = levelString,
|
||||
ObjectAppIds = ids,
|
||||
Message = message,
|
||||
Metadata = metadata,
|
||||
VisualOverrides = visualOverrides,
|
||||
};
|
||||
|
||||
AutomationResult.ObjectResults.Add(resultCase);
|
||||
}
|
||||
}
|
||||
|
||||
public partial interface IAutomationContext
|
||||
{
|
||||
[MemberNotNull(nameof(ContextView))]
|
||||
public void SetContextView(IReadOnlyCollection<string>? resourceIds = null, bool includeSourceModelVersion = true);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Speckle.Automate.Sdk.DataAnnotations;
|
||||
|
||||
/// <summary>
|
||||
/// If specified, the given function input will be redacted in all contexts.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.All)]
|
||||
public sealed class SecretAttribute : Attribute { }
|
||||
@@ -0,0 +1,195 @@
|
||||
using System.CommandLine;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Schema;
|
||||
using Newtonsoft.Json.Schema.Generation;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Speckle.Automate.Sdk.DataAnnotations;
|
||||
using Speckle.Automate.Sdk.Schema;
|
||||
using Speckle.InterfaceGenerator;
|
||||
using Speckle.Sdk;
|
||||
|
||||
namespace Speckle.Automate.Sdk;
|
||||
|
||||
/// <summary>
|
||||
/// Provides mechanisms to execute any function that conforms to the AutomateFunction "interface"
|
||||
/// </summary>
|
||||
[GenerateAutoInterface(VisibilityModifier = "public")]
|
||||
internal class AutomationRunner(IAutomationContextFactory contextFactory) : IAutomationRunner
|
||||
{
|
||||
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
|
||||
public async Task<IAutomationContext> RunFunction<TInput>(
|
||||
Func<IAutomationContext, TInput, Task> automateFunction,
|
||||
AutomationRunData automationRunData,
|
||||
string speckleToken,
|
||||
TInput inputs
|
||||
)
|
||||
where TInput : struct
|
||||
{
|
||||
var automationContext = await contextFactory.Initialize(automationRunData, speckleToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
await automateFunction.Invoke(automationContext, inputs).ConfigureAwait(false);
|
||||
if (automationContext.RunStatus is not ("FAILED" or "SUCCEEDED"))
|
||||
{
|
||||
automationContext.MarkRunSuccess(
|
||||
"WARNING: Automate assumed a success status, but it was not marked as so by the function."
|
||||
);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (!ex.IsFatal())
|
||||
{
|
||||
Console.WriteLine(ex.ToString());
|
||||
automationContext.MarkRunException("Function error. Check the automation run logs for details.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (automationContext.ContextView is null)
|
||||
{
|
||||
automationContext.SetContextView();
|
||||
}
|
||||
|
||||
await automationContext.ReportRunStatus().ConfigureAwait(false);
|
||||
}
|
||||
return automationContext;
|
||||
}
|
||||
|
||||
public async Task<IAutomationContext> RunFunction(
|
||||
Func<IAutomationContext, Task> automateFunction,
|
||||
AutomationRunData automationRunData,
|
||||
string speckleToken
|
||||
) =>
|
||||
await RunFunction(
|
||||
async (context, _) => await automateFunction(context).ConfigureAwait(false),
|
||||
automationRunData,
|
||||
speckleToken,
|
||||
new Fake()
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
private struct Fake { }
|
||||
|
||||
/// <summary>
|
||||
/// Main entrypoint to execute an Automate function with no input data
|
||||
/// </summary>
|
||||
/// <param name="args">The command line arguments passed into the function by automate</param>
|
||||
/// <param name="automateFunction">The automate function to execute</param>
|
||||
/// <remarks>This should always be called in your own functions, as it contains the logic to trigger the function automatically.</remarks>
|
||||
public async Task<int> Main(string[] args, Func<IAutomationContext, Task> automateFunction)
|
||||
{
|
||||
return await Main(
|
||||
args,
|
||||
async (IAutomationContext context, Fake _) => await automateFunction(context).ConfigureAwait(false)
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main entrypoint to execute an Automate function with input data of type <typeparamref name="TInput"/>.
|
||||
/// </summary>
|
||||
/// <param name="args">The command line arguments passed into the function by automate</param>
|
||||
/// <param name="automateFunction">The automate function to execute</param>
|
||||
/// <typeparam name="TInput">The provided input data</typeparam>
|
||||
/// <remarks>This should always be called in your own functions, as it contains the logic to trigger the function automatically.</remarks>
|
||||
public async Task<int> Main<TInput>(string[] args, Func<IAutomationContext, TInput, Task> automateFunction)
|
||||
where TInput : struct
|
||||
{
|
||||
Argument<string> pathArg = new(name: "Input Path", description: "A file path to retrieve function inputs");
|
||||
RootCommand rootCommand = new();
|
||||
|
||||
// a stupid hack to be able to exit with a specific integer exit code
|
||||
// read more at https://github.com/dotnet/command-line-api/issues/1570
|
||||
var exitCode = 0;
|
||||
|
||||
rootCommand.AddArgument(pathArg);
|
||||
rootCommand.SetHandler(
|
||||
async inputPath =>
|
||||
{
|
||||
try
|
||||
{
|
||||
FunctionRunData<TInput> data = FunctionRunDataParser.FromPath<TInput>(inputPath);
|
||||
|
||||
var context = await RunFunction(
|
||||
automateFunction,
|
||||
data.AutomationRunData,
|
||||
data.SpeckleToken,
|
||||
data.FunctionInputs
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (context.RunStatus is "EXCEPTION")
|
||||
{
|
||||
exitCode = 1;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
exitCode = 1;
|
||||
throw;
|
||||
}
|
||||
},
|
||||
pathArg
|
||||
);
|
||||
|
||||
Argument<string> schemaFilePathArg = new(
|
||||
name: "Function inputs file path",
|
||||
description: "A token to talk to the Speckle server with"
|
||||
);
|
||||
|
||||
Command generateSchemaCommand = new("generate-schema", "Generate JSON schema for the function inputs");
|
||||
generateSchemaCommand.AddArgument(schemaFilePathArg);
|
||||
generateSchemaCommand.SetHandler(
|
||||
schemaFilePath =>
|
||||
{
|
||||
try
|
||||
{
|
||||
JSchemaGenerator generator = new() { ContractResolver = new CamelCasePropertyNamesContractResolver() };
|
||||
generator.GenerationProviders.Add(new SpeckleSecretProvider());
|
||||
JSchema schema = generator.Generate(typeof(TInput));
|
||||
schema.ToString(SchemaVersion.Draft2019_09);
|
||||
File.WriteAllText(schemaFilePath, schema.ToString());
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
exitCode = 1;
|
||||
throw;
|
||||
}
|
||||
},
|
||||
schemaFilePathArg
|
||||
);
|
||||
rootCommand.Add(generateSchemaCommand);
|
||||
|
||||
await rootCommand.InvokeAsync(args).ConfigureAwait(false);
|
||||
|
||||
// if we've gotten this far, the execution should technically be completed as expected
|
||||
// thus exiting with 0 is the semantically correct thing to do
|
||||
return exitCode;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class SpeckleSecretProvider : JSchemaGenerationProvider
|
||||
{
|
||||
public override JSchema? GetSchema(JSchemaTypeGenerationContext context)
|
||||
{
|
||||
var attributes = context.MemberProperty?.AttributeProvider?.GetAttributes(false) ?? new List<Attribute>();
|
||||
var isSecretString = attributes.Any(att => att is SecretAttribute);
|
||||
|
||||
if (isSecretString)
|
||||
{
|
||||
return CreateSchemaWithWriteOnly(context.ObjectType, context.Required);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static JSchema CreateSchemaWithWriteOnly(Type type, Required required)
|
||||
{
|
||||
JSchemaGenerator generator = new();
|
||||
JSchema schema = generator.Generate(type, required != Required.Always);
|
||||
|
||||
schema.WriteOnly = true;
|
||||
|
||||
return schema;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Speckle.Automate.Sdk.Schema;
|
||||
|
||||
public class AutomationResult
|
||||
{
|
||||
public double Elapsed { get; set; }
|
||||
public string? ResultView { get; set; }
|
||||
public List<string> ResultVersions { get; set; } = new();
|
||||
public List<string> Blobs { get; set; } = new();
|
||||
public string RunStatus { get; set; } = AutomationStatusMapping.Get(AutomationStatus.Running);
|
||||
public string? StatusMessage { get; set; }
|
||||
public List<ResultCase> ObjectResults { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Speckle.Automate.Sdk.Schema.Triggers;
|
||||
|
||||
namespace Speckle.Automate.Sdk.Schema;
|
||||
|
||||
///<summary>
|
||||
/// Values of the project, model and automation that triggered this function run.
|
||||
///</summary>
|
||||
public readonly struct AutomationRunData
|
||||
{
|
||||
[JsonRequired]
|
||||
public required string ProjectId { get; init; }
|
||||
|
||||
[JsonRequired]
|
||||
public required Uri SpeckleServerUrl { get; init; }
|
||||
|
||||
[JsonRequired]
|
||||
public required string AutomationId { get; init; }
|
||||
|
||||
[JsonRequired]
|
||||
public required string AutomationRunId { get; init; }
|
||||
|
||||
[JsonRequired]
|
||||
public required string FunctionRunId { get; init; }
|
||||
|
||||
[JsonRequired]
|
||||
public required List<VersionCreationTrigger> Triggers { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Speckle.Automate.Sdk.Schema;
|
||||
|
||||
///<summary>
|
||||
/// Set the status of the automation.
|
||||
///</summary>
|
||||
public enum AutomationStatus
|
||||
{
|
||||
Initializing,
|
||||
Running,
|
||||
Failed,
|
||||
Succeeded,
|
||||
Exception,
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace Speckle.Automate.Sdk.Schema;
|
||||
|
||||
public abstract class AutomationStatusMapping
|
||||
{
|
||||
private const string INITIALIZING = "INITIALIZING";
|
||||
private const string RUNNING = "RUNNING";
|
||||
private const string FAILED = "FAILED";
|
||||
private const string SUCCEEDED = "SUCCEEDED";
|
||||
private const string EXCEPTION = "EXCEPTION";
|
||||
|
||||
public static string Get(AutomationStatus status) =>
|
||||
status switch
|
||||
{
|
||||
AutomationStatus.Running => RUNNING,
|
||||
AutomationStatus.Failed => FAILED,
|
||||
AutomationStatus.Succeeded => SUCCEEDED,
|
||||
AutomationStatus.Initializing => INITIALIZING,
|
||||
AutomationStatus.Exception => EXCEPTION,
|
||||
_ => throw new ArgumentOutOfRangeException($"Not valid value for enum {status}"),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Speckle.Automate.Sdk.Schema;
|
||||
|
||||
public readonly struct BlobUploadResponse
|
||||
{
|
||||
public required List<UploadResult> UploadResults { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Speckle.Automate.Sdk.Schema;
|
||||
|
||||
/// <summary>
|
||||
/// Required data to run a function.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"> Type for <see cref="FunctionInputs"/>.</typeparam>
|
||||
public sealed class FunctionRunData<T>
|
||||
{
|
||||
[JsonRequired]
|
||||
public required string SpeckleToken { get; init; }
|
||||
|
||||
[JsonRequired]
|
||||
public required AutomationRunData AutomationRunData { get; init; }
|
||||
|
||||
public required T? FunctionInputs { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Speckle.Automate.Sdk.Schema;
|
||||
|
||||
public static class FunctionRunDataParser
|
||||
{
|
||||
private static readonly JsonSerializerOptions s_jsonSerializerSettings = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Function run data parser from json file path./>
|
||||
/// </summary>
|
||||
/// <param name="inputLocation"> Path to retrieve function run data.</param>
|
||||
/// <typeparam name="T"> Type for function inputs.</typeparam>
|
||||
/// <returns>The data to be able to run function.</returns>
|
||||
/// <exception cref="JsonException">Json was not valid</exception>
|
||||
/// <exception cref="FileNotFoundException"> Throws unless file exists.</exception>
|
||||
public static FunctionRunData<T> FromPath<T>(string inputLocation)
|
||||
{
|
||||
string inputJsonString = ReadInputData(inputLocation);
|
||||
//It's important to use System.Text.Json here. The template FunctionInputs are decorated with STJ attributes
|
||||
FunctionRunData<T>? functionRunData = JsonSerializer.Deserialize<FunctionRunData<T>>(
|
||||
inputJsonString,
|
||||
s_jsonSerializerSettings
|
||||
);
|
||||
|
||||
if (functionRunData is null)
|
||||
{
|
||||
throw new JsonException($"Function run data couldn't deserialized at {inputLocation}");
|
||||
}
|
||||
|
||||
return functionRunData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read text from file.
|
||||
/// </summary>
|
||||
/// <param name="inputLocation"> Path to check file is exist.</param>
|
||||
/// <returns>Text in file.</returns>
|
||||
/// <exception cref="FileNotFoundException"> Throws unless file exists.</exception>
|
||||
private static string ReadInputData(string inputLocation)
|
||||
{
|
||||
if (!File.Exists(inputLocation))
|
||||
{
|
||||
throw new FileNotFoundException($"Cannot find the function inputs file at {inputLocation}");
|
||||
}
|
||||
|
||||
return File.ReadAllText(inputLocation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Speckle.Automate.Sdk.Schema;
|
||||
|
||||
public enum ObjectResultLevel
|
||||
{
|
||||
Success,
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Speckle.Automate.Sdk.Schema;
|
||||
|
||||
public abstract class ObjectResultLevelMapping
|
||||
{
|
||||
private const string SUCCESS = "SUCCESS";
|
||||
private const string INFO = "INFO";
|
||||
private const string WARNING = "WARNING";
|
||||
private const string ERROR = "ERROR";
|
||||
|
||||
public static string Get(ObjectResultLevel level) =>
|
||||
level switch
|
||||
{
|
||||
ObjectResultLevel.Error => ERROR,
|
||||
ObjectResultLevel.Warning => WARNING,
|
||||
ObjectResultLevel.Info => INFO,
|
||||
ObjectResultLevel.Success => SUCCESS,
|
||||
_ => throw new ArgumentOutOfRangeException($"Not valid value for enum {level}"),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Speckle.Automate.Sdk.Schema;
|
||||
|
||||
public readonly struct ObjectResultValues
|
||||
{
|
||||
public required List<ResultCase> ObjectResults { get; init; }
|
||||
public required List<string> BlobIds { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Speckle.Automate.Sdk.Schema;
|
||||
|
||||
public readonly struct ObjectResults
|
||||
{
|
||||
public int Version { get; init; }
|
||||
public ObjectResultValues Values { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Speckle.Automate.Sdk.Schema;
|
||||
|
||||
public readonly struct ResultCase
|
||||
{
|
||||
public required string Category { get; init; }
|
||||
public required string Level { get; init; }
|
||||
public required Dictionary<string, string?> ObjectAppIds { get; init; }
|
||||
public required string? Message { get; init; }
|
||||
public required Dictionary<string, object>? Metadata { get; init; }
|
||||
public required Dictionary<string, object>? VisualOverrides { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Speckle.Automate.Sdk.Schema.Triggers;
|
||||
|
||||
public abstract class AutomationRunTriggerBase
|
||||
{
|
||||
public required string TriggerType { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Speckle.Automate.Sdk.Schema.Triggers;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a single version creation trigger for the automation run.
|
||||
/// </summary>
|
||||
public sealed class VersionCreationTrigger : AutomationRunTriggerBase
|
||||
{
|
||||
public const string VERSION_CREATION_TRIGGER_TYPE = "versionCreation";
|
||||
|
||||
[JsonRequired]
|
||||
public required VersionCreationTriggerPayload Payload { get; init; }
|
||||
|
||||
public VersionCreationTrigger() { }
|
||||
|
||||
[SetsRequiredMembers]
|
||||
public VersionCreationTrigger(string modelId, string versionId)
|
||||
{
|
||||
Payload = new() { ModelId = modelId, VersionId = versionId };
|
||||
TriggerType = VERSION_CREATION_TRIGGER_TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the version creation trigger payload.
|
||||
/// </summary>
|
||||
public sealed record VersionCreationTriggerPayload
|
||||
{
|
||||
[JsonRequired]
|
||||
public required string ModelId { get; init; }
|
||||
|
||||
[JsonRequired]
|
||||
public required string VersionId { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Speckle.Automate.Sdk.Schema;
|
||||
|
||||
public readonly struct UploadResult
|
||||
{
|
||||
public required string BlobId { get; init; }
|
||||
public required string FileName { get; init; }
|
||||
public required int UploadStatus { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Speckle.Objects.Geometry;
|
||||
using Speckle.Sdk;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Serialisation.V2;
|
||||
|
||||
namespace Speckle.Automate.Sdk;
|
||||
|
||||
public static class ServiceRegistration
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets-up the serviceCollection with all the services in Speckle.Automate.Sdk and Speckle.Sdk
|
||||
/// </summary>
|
||||
/// <param name="serviceCollection"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddAutomateSdk(this IServiceCollection serviceCollection)
|
||||
{
|
||||
var executingAssembly = Assembly.GetExecutingAssembly().GetName();
|
||||
var speckleAssembly = typeof(Base).Assembly.GetName();
|
||||
AddAutomateSdk(
|
||||
serviceCollection,
|
||||
new SpeckleSdkOptions(
|
||||
new(executingAssembly.FullName, "automatefunction"),
|
||||
executingAssembly.Version?.ToString() ?? "Unknown",
|
||||
speckleAssembly.Version?.ToString(),
|
||||
[typeof(Base).Assembly, typeof(Point).Assembly]
|
||||
)
|
||||
);
|
||||
|
||||
return serviceCollection;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddAutomateSdk(
|
||||
this IServiceCollection serviceCollection,
|
||||
SpeckleSdkOptions speckleSdkOptions
|
||||
)
|
||||
{
|
||||
serviceCollection.AddSpeckleSdk(speckleSdkOptions);
|
||||
|
||||
//Overwrite the SDK's default IDeserializeProcessFactory to ensure SQLite is not used to cache objects
|
||||
serviceCollection.AddTransient<IDeserializeProcessFactory, DeserializeProcessFactoryNoCache>();
|
||||
|
||||
//Add automate assembly services
|
||||
serviceCollection.AddTransient<IAutomationContextFactory, AutomationContextFactory>();
|
||||
serviceCollection.AddTransient<IAutomationRunner, AutomationRunner>();
|
||||
|
||||
return serviceCollection;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup Label="Compiler Properties">
|
||||
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Nugetspec Package Properties">
|
||||
<PackageId>Speckle.Automate.Sdk</PackageId>
|
||||
<Description>Speckle Automate SDK</Description>
|
||||
<PackageTags>$(PackageTags) speckle automation</PackageTags>
|
||||
</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.Automate.Sdk.Tests.Integration" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" />
|
||||
<PackageReference Include="System.CommandLine" NoWarn="NU5104" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0'">
|
||||
<PackageReference Include="System.Text.Json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Project References">
|
||||
<ProjectReference Include="..\Speckle.Objects\Speckle.Objects.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,79 @@
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Sdk;
|
||||
|
||||
namespace Speckle.Automate.Sdk.Test;
|
||||
|
||||
public class TestAppSettings
|
||||
{
|
||||
public string? SpeckleToken { get; set; }
|
||||
public string? SpeckleServerUrl { get; set; }
|
||||
public string? SpeckleProjectId { get; set; }
|
||||
public string? SpeckleAutomationId { get; set; }
|
||||
}
|
||||
|
||||
public static class TestAutomateEnvironment
|
||||
{
|
||||
public static TestAppSettings? AppSettings { get; private set; }
|
||||
|
||||
private static string GetEnvironmentVariable(string environmentVariableName)
|
||||
{
|
||||
var value = TryGetEnvironmentVariable(environmentVariableName);
|
||||
|
||||
if (value is null)
|
||||
{
|
||||
throw new SpeckleException($"Cannot run tests without a {environmentVariableName} environment variable");
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static string? TryGetEnvironmentVariable(string environmentVariableName)
|
||||
{
|
||||
return Environment.GetEnvironmentVariable(environmentVariableName);
|
||||
}
|
||||
|
||||
private static TestAppSettings? GetAppSettings()
|
||||
{
|
||||
if (AppSettings != null)
|
||||
{
|
||||
return AppSettings;
|
||||
}
|
||||
|
||||
var path = "./appsettings.json";
|
||||
var json = File.ReadAllText(path);
|
||||
|
||||
var appSettings = JsonConvert.DeserializeObject<TestAppSettings>(json);
|
||||
|
||||
AppSettings = appSettings;
|
||||
|
||||
return AppSettings;
|
||||
}
|
||||
|
||||
public static string GetSpeckleToken()
|
||||
{
|
||||
return GetAppSettings()?.SpeckleToken ?? GetEnvironmentVariable("SPECKLE_TOKEN");
|
||||
}
|
||||
|
||||
public static Uri GetSpeckleServerUrl()
|
||||
{
|
||||
var urlString =
|
||||
GetAppSettings()?.SpeckleServerUrl ?? TryGetEnvironmentVariable("SPECKLE_SERVER_URL") ?? "http://127.0.0.1:3000";
|
||||
|
||||
return new Uri(urlString);
|
||||
}
|
||||
|
||||
public static string GetSpeckleProjectId()
|
||||
{
|
||||
return GetAppSettings()?.SpeckleProjectId ?? GetEnvironmentVariable("SPECKLE_PROJECT_ID");
|
||||
}
|
||||
|
||||
public static string GetSpeckleAutomationId()
|
||||
{
|
||||
return GetAppSettings()?.SpeckleAutomationId ?? GetEnvironmentVariable("SPECKLE_AUTOMATION_ID");
|
||||
}
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
AppSettings = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
using GraphQL;
|
||||
using Speckle.Automate.Sdk.Schema;
|
||||
using Speckle.Automate.Sdk.Schema.Triggers;
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Sdk.Api;
|
||||
using Speckle.Sdk.Api.GraphQL.Models.Responses;
|
||||
|
||||
namespace Speckle.Automate.Sdk.Test;
|
||||
|
||||
internal class TestAutomationRun
|
||||
{
|
||||
[JsonRequired]
|
||||
public required string AutomationRunId { get; init; }
|
||||
|
||||
[JsonRequired]
|
||||
public required string FunctionRunId { get; init; }
|
||||
|
||||
[JsonRequired]
|
||||
public required IReadOnlyList<TestAutomationRunTrigger> Triggers { get; init; }
|
||||
}
|
||||
|
||||
internal class TestAutomationRunTrigger : AutomationRunTriggerBase
|
||||
{
|
||||
/// <remarks>This should really be a TestAutomationRunTriggerPayload, but right now, they look the samee</remarks>
|
||||
public required VersionCreationTriggerPayload Payload { get; init; }
|
||||
}
|
||||
|
||||
public static class TestAutomateUtils
|
||||
{
|
||||
public static async Task<AutomationRunData> CreateTestRun(
|
||||
IClient speckleClient,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
//language=graphql
|
||||
const string QUERY = """
|
||||
mutation Mutation($projectId: ID!, $automationId: ID!) {
|
||||
data:projectMutations {
|
||||
data:automationMutations(projectId: $projectId) {
|
||||
data:createTestAutomationRun(automationId: $automationId) {
|
||||
automationRunId
|
||||
functionRunId
|
||||
triggers {
|
||||
payload {
|
||||
modelId
|
||||
versionId
|
||||
}
|
||||
triggerType
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
GraphQLRequest request = new(
|
||||
query: QUERY,
|
||||
variables: new
|
||||
{
|
||||
automationId = TestAutomateEnvironment.GetSpeckleAutomationId(),
|
||||
projectId = TestAutomateEnvironment.GetSpeckleProjectId(),
|
||||
}
|
||||
);
|
||||
|
||||
var res = await speckleClient
|
||||
.ExecuteGraphQLRequest<RequiredResponse<RequiredResponse<RequiredResponse<TestAutomationRun>>>>(
|
||||
request,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var runData = res.data.data.data;
|
||||
var triggerData = runData.Triggers[0].Payload;
|
||||
|
||||
string modelId = triggerData.ModelId;
|
||||
string versionId = triggerData.VersionId;
|
||||
|
||||
var data = new AutomationRunData()
|
||||
{
|
||||
ProjectId = TestAutomateEnvironment.GetSpeckleProjectId(),
|
||||
SpeckleServerUrl = TestAutomateEnvironment.GetSpeckleServerUrl(),
|
||||
AutomationId = TestAutomateEnvironment.GetSpeckleAutomationId(),
|
||||
AutomationRunId = runData.AutomationRunId,
|
||||
FunctionRunId = runData.FunctionRunId,
|
||||
Triggers = [new(modelId: modelId, versionId: versionId)],
|
||||
};
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,620 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dependencies": {
|
||||
".NETStandard,Version=v2.0": {
|
||||
"Microsoft.SourceLink.GitHub": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.0, )",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Build.Tasks.Git": "8.0.0",
|
||||
"Microsoft.SourceLink.Common": "8.0.0"
|
||||
}
|
||||
},
|
||||
"NETStandard.Library": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.0.3, )",
|
||||
"resolved": "2.0.3",
|
||||
"contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "1.1.0"
|
||||
}
|
||||
},
|
||||
"Newtonsoft.Json.Schema": {
|
||||
"type": "Direct",
|
||||
"requested": "[4.0.1, )",
|
||||
"resolved": "4.0.1",
|
||||
"contentHash": "rbHUKp5WTIbqmLEeJ21nTTDGcfR0LA7bVMzm0bYc3yx6NFKiCIHzzvYbwA4Sqgs7+wNldc5nBlkbithWj8IZig==",
|
||||
"dependencies": {
|
||||
"Newtonsoft.Json": "13.0.3"
|
||||
}
|
||||
},
|
||||
"PolySharp": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.15.0, )",
|
||||
"resolved": "1.15.0",
|
||||
"contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g=="
|
||||
},
|
||||
"Speckle.InterfaceGenerator": {
|
||||
"type": "Direct",
|
||||
"requested": "[0.9.6, )",
|
||||
"resolved": "0.9.6",
|
||||
"contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w=="
|
||||
},
|
||||
"System.CommandLine": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.0.0-beta4.22272.1, )",
|
||||
"resolved": "2.0.0-beta4.22272.1",
|
||||
"contentHash": "1uqED/q2H0kKoLJ4+hI2iPSBSEdTuhfCYADeJrAqERmiGQ2NNacYKRNEQ+gFbU4glgVyK8rxI+ZOe1onEtr/Pg==",
|
||||
"dependencies": {
|
||||
"System.Memory": "4.5.4"
|
||||
}
|
||||
},
|
||||
"System.Text.Json": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.5, )",
|
||||
"resolved": "8.0.5",
|
||||
"contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Bcl.AsyncInterfaces": "8.0.0",
|
||||
"System.Buffers": "4.5.1",
|
||||
"System.Memory": "4.5.5",
|
||||
"System.Runtime.CompilerServices.Unsafe": "6.0.0",
|
||||
"System.Text.Encodings.Web": "8.0.0",
|
||||
"System.Threading.Tasks.Extensions": "4.5.4"
|
||||
}
|
||||
},
|
||||
"GraphQL.Client.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "h7uzWFORHZ+CCjwr/ThAyXMr0DPpzEANDa4Uo54wqCQ+j7qUKwqYTgOrb1W40sqbvNaZm9v/X7It31SUw0maHA==",
|
||||
"dependencies": {
|
||||
"GraphQL.Primitives": "6.0.0"
|
||||
}
|
||||
},
|
||||
"GraphQL.Client.Abstractions.Websocket": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "Nr9bPf8gIOvLuXpqEpqr9z9jslYFJOvd0feHth3/kPqeR3uMbjF5pjiwh4jxyMcxHdr8Pb6QiXkV3hsSyt0v7A==",
|
||||
"dependencies": {
|
||||
"GraphQL.Client.Abstractions": "6.0.0"
|
||||
}
|
||||
},
|
||||
"GraphQL.Primitives": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "yg72rrYDapfsIUrul7aF6wwNnTJBOFvuA9VdDTQpPa8AlAriHbufeXYLBcodKjfUdkCnaiggX1U/nEP08Zb5GA=="
|
||||
},
|
||||
"Microsoft.Build.Tasks.Git": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
|
||||
},
|
||||
"Microsoft.Data.Sqlite.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.5",
|
||||
"contentHash": "FTerRmQPqHrCrnoUzhBu+E+1DNGwyrAMLqHkAqOOOu5pGfyMOj8qQUBxI/gDtWtG11p49UxSfWmBzRNlwZqfUg==",
|
||||
"dependencies": {
|
||||
"SQLitePCLRaw.core": "2.1.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "nOP8R1mVb/6mZtm2qgAJXn/LFm/2kMjHDAg/QJLFG6CuWYJtaD3p1BwQhufBVvRzL9ceJ/xF0SQ0qsI2GkDQAA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "65MrmXCziWaQFrI0UHkQbesrX5wTwf9XPjY5yFm/VkgJKFJ5gqvXRoXjIZcf2wLi5ZlwGz/oMYfyURVCWbM5iw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Binder": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "vJ9xvOZCnUAIHcGC3SU35r3HKmHTVIeHzo6u/qzlHAqD8m6xv92MLin4oJntTvkpKxVX3vI1GFFkIQtU3AdlsQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "B2WqEox8o+4KUOpL7rZPyh6qYjik8tHi2tN8Z9jZkHzED8ElYgZa/h6K+xliB435SqUcWT290Fr2aa8BtZjn8A=="
|
||||
},
|
||||
"Microsoft.Extensions.Options": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "UpZLNLBpIZ0GTebShui7xXYh6DmBHjWM8NxGxZbdQh/bPZ5e6YswqI+bru6BnEL5eWiOdodsXtEz3FROcgi/qg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
|
||||
"Microsoft.Extensions.Primitives": "2.2.0",
|
||||
"System.ComponentModel.Annotations": "4.5.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Primitives": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==",
|
||||
"dependencies": {
|
||||
"System.Memory": "4.5.1",
|
||||
"System.Runtime.CompilerServices.Unsafe": "4.5.1"
|
||||
}
|
||||
},
|
||||
"Microsoft.NETCore.Platforms": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.1.0",
|
||||
"contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A=="
|
||||
},
|
||||
"Microsoft.NETCore.Targets": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.1.0",
|
||||
"contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg=="
|
||||
},
|
||||
"Microsoft.SourceLink.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
|
||||
},
|
||||
"Newtonsoft.Json": {
|
||||
"type": "Transitive",
|
||||
"resolved": "13.0.3",
|
||||
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
|
||||
},
|
||||
"SQLitePCLRaw.bundle_e_sqlite3": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.4",
|
||||
"contentHash": "EWI1olKDjFEBMJu0+3wuxwziIAdWDVMYLhuZ3Qs84rrz+DHwD00RzWPZCa+bLnHCf3oJwuFZIRsHT5p236QXww==",
|
||||
"dependencies": {
|
||||
"SQLitePCLRaw.lib.e_sqlite3": "2.1.4",
|
||||
"SQLitePCLRaw.provider.e_sqlite3": "2.1.4"
|
||||
}
|
||||
},
|
||||
"SQLitePCLRaw.core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.4",
|
||||
"contentHash": "inBjvSHo9UDKneGNzfUfDjK08JzlcIhn1+SP5Y3m6cgXpCxXKCJDy6Mka7LpgSV+UZmKSnC8rTwB0SQ0xKu5pA==",
|
||||
"dependencies": {
|
||||
"System.Memory": "4.5.3"
|
||||
}
|
||||
},
|
||||
"SQLitePCLRaw.lib.e_sqlite3": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.4",
|
||||
"contentHash": "2C9Q9eX7CPLveJA0rIhf9RXAvu+7nWZu1A2MdG6SD/NOu26TakGgL1nsbc0JAspGijFOo3HoN79xrx8a368fBg=="
|
||||
},
|
||||
"SQLitePCLRaw.provider.e_sqlite3": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.4",
|
||||
"contentHash": "CSlb5dUp1FMIkez9Iv5EXzpeq7rHryVNqwJMWnpq87j9zWZexaEMdisDktMsnnrzKM6ahNrsTkjqNodTBPBxtQ==",
|
||||
"dependencies": {
|
||||
"SQLitePCLRaw.core": "2.1.4"
|
||||
}
|
||||
},
|
||||
"System.Buffers": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.1",
|
||||
"contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg=="
|
||||
},
|
||||
"System.ComponentModel.Annotations": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.0",
|
||||
"contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg=="
|
||||
},
|
||||
"System.Memory": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.5",
|
||||
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==",
|
||||
"dependencies": {
|
||||
"System.Buffers": "4.5.1",
|
||||
"System.Numerics.Vectors": "4.4.0",
|
||||
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
|
||||
}
|
||||
},
|
||||
"System.Numerics.Vectors": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.4.0",
|
||||
"contentHash": "UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ=="
|
||||
},
|
||||
"System.Reactive": {
|
||||
"type": "Transitive",
|
||||
"resolved": "5.0.0",
|
||||
"contentHash": "erBZjkQHWL9jpasCE/0qKAryzVBJFxGHVBAvgRN1bzM0q2s1S4oYREEEL0Vb+1kA/6BKb5FjUZMp5VXmy+gzkQ==",
|
||||
"dependencies": {
|
||||
"System.Runtime.InteropServices.WindowsRuntime": "4.3.0",
|
||||
"System.Threading.Tasks.Extensions": "4.5.4"
|
||||
}
|
||||
},
|
||||
"System.Runtime": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "1.1.0",
|
||||
"Microsoft.NETCore.Targets": "1.1.0"
|
||||
}
|
||||
},
|
||||
"System.Runtime.CompilerServices.Unsafe": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
|
||||
},
|
||||
"System.Runtime.InteropServices.WindowsRuntime": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "J4GUi3xZQLUBasNwZnjrffN8i5wpHrBtZoLG+OhRyGo/+YunMRWWtwoMDlUAIdmX0uRfpHIBDSV6zyr3yf00TA==",
|
||||
"dependencies": {
|
||||
"System.Runtime": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.Text.Encodings.Web": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==",
|
||||
"dependencies": {
|
||||
"System.Buffers": "4.5.1",
|
||||
"System.Memory": "4.5.5",
|
||||
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
|
||||
}
|
||||
},
|
||||
"System.Threading.Tasks.Extensions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.4",
|
||||
"contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
|
||||
"dependencies": {
|
||||
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
|
||||
}
|
||||
},
|
||||
"speckle.objects": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Speckle.Sdk": "[1.0.0, )"
|
||||
}
|
||||
},
|
||||
"speckle.sdk": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"GraphQL.Client": "[6.0.0, )",
|
||||
"Microsoft.CSharp": "[4.7.0, )",
|
||||
"Microsoft.Data.Sqlite": "[7.0.5, )",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )",
|
||||
"Microsoft.Extensions.Logging": "[2.2.0, )",
|
||||
"Speckle.DoubleNumerics": "[4.1.0, )",
|
||||
"Speckle.Newtonsoft.Json": "[13.0.2, )",
|
||||
"Speckle.Sdk.Dependencies": "[1.0.0, )"
|
||||
}
|
||||
},
|
||||
"speckle.sdk.dependencies": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Microsoft.Bcl.AsyncInterfaces": "[9.0.4, )"
|
||||
}
|
||||
},
|
||||
"GraphQL.Client": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[6.0.0, )",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "8yPNBbuVBpTptivyAlak4GZvbwbUcjeQTL4vN1HKHRuOykZ4r7l5fcLS6vpyPyLn0x8FsL31xbOIKyxbmR9rbA==",
|
||||
"dependencies": {
|
||||
"GraphQL.Client.Abstractions": "6.0.0",
|
||||
"GraphQL.Client.Abstractions.Websocket": "6.0.0",
|
||||
"System.Reactive": "5.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Bcl.AsyncInterfaces": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[9.0.4, )",
|
||||
"resolved": "9.0.4",
|
||||
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
|
||||
"dependencies": {
|
||||
"System.Threading.Tasks.Extensions": "4.5.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.CSharp": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[4.7.0, )",
|
||||
"resolved": "4.7.0",
|
||||
"contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA=="
|
||||
},
|
||||
"Microsoft.Data.Sqlite": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[7.0.5, )",
|
||||
"resolved": "7.0.5",
|
||||
"contentHash": "KGxbPeWsQMnmQy43DSBxAFtHz3l2JX8EWBSGUCvT3CuZ8KsuzbkqMIJMDOxWtG8eZSoCDI04aiVQjWuuV8HmSw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Data.Sqlite.Core": "7.0.5",
|
||||
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.2.0, )",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
|
||||
},
|
||||
"Microsoft.Extensions.Logging": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.2.0, )",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "Nxqhadc9FCmFHzU+fz3oc8sFlE6IadViYg8dfUdGzJZ2JUxnCsRghBhhOWdM4B2zSZqEc+0BjliBh/oNdRZuig==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Binder": "2.2.0",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "2.2.0",
|
||||
"Microsoft.Extensions.Options": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Speckle.DoubleNumerics": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[4.1.0, )",
|
||||
"resolved": "4.1.0",
|
||||
"contentHash": "20DtS+FsDRsOD9+AU3TwNFZ0qrKo5f6f7B5ZR9wStsIHHHC9k7DpjbCvuNtmnSjx54MD+TJC7wV2f5iyGVPj1A=="
|
||||
},
|
||||
"Speckle.Newtonsoft.Json": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[13.0.2, )",
|
||||
"resolved": "13.0.2",
|
||||
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
|
||||
}
|
||||
},
|
||||
"net8.0": {
|
||||
"Microsoft.SourceLink.GitHub": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.0, )",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Build.Tasks.Git": "8.0.0",
|
||||
"Microsoft.SourceLink.Common": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Newtonsoft.Json.Schema": {
|
||||
"type": "Direct",
|
||||
"requested": "[4.0.1, )",
|
||||
"resolved": "4.0.1",
|
||||
"contentHash": "rbHUKp5WTIbqmLEeJ21nTTDGcfR0LA7bVMzm0bYc3yx6NFKiCIHzzvYbwA4Sqgs7+wNldc5nBlkbithWj8IZig==",
|
||||
"dependencies": {
|
||||
"Newtonsoft.Json": "13.0.3"
|
||||
}
|
||||
},
|
||||
"PolySharp": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.15.0, )",
|
||||
"resolved": "1.15.0",
|
||||
"contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g=="
|
||||
},
|
||||
"Speckle.InterfaceGenerator": {
|
||||
"type": "Direct",
|
||||
"requested": "[0.9.6, )",
|
||||
"resolved": "0.9.6",
|
||||
"contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w=="
|
||||
},
|
||||
"System.CommandLine": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.0.0-beta4.22272.1, )",
|
||||
"resolved": "2.0.0-beta4.22272.1",
|
||||
"contentHash": "1uqED/q2H0kKoLJ4+hI2iPSBSEdTuhfCYADeJrAqERmiGQ2NNacYKRNEQ+gFbU4glgVyK8rxI+ZOe1onEtr/Pg=="
|
||||
},
|
||||
"GraphQL.Client.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "h7uzWFORHZ+CCjwr/ThAyXMr0DPpzEANDa4Uo54wqCQ+j7qUKwqYTgOrb1W40sqbvNaZm9v/X7It31SUw0maHA==",
|
||||
"dependencies": {
|
||||
"GraphQL.Primitives": "6.0.0"
|
||||
}
|
||||
},
|
||||
"GraphQL.Client.Abstractions.Websocket": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "Nr9bPf8gIOvLuXpqEpqr9z9jslYFJOvd0feHth3/kPqeR3uMbjF5pjiwh4jxyMcxHdr8Pb6QiXkV3hsSyt0v7A==",
|
||||
"dependencies": {
|
||||
"GraphQL.Client.Abstractions": "6.0.0"
|
||||
}
|
||||
},
|
||||
"GraphQL.Primitives": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "yg72rrYDapfsIUrul7aF6wwNnTJBOFvuA9VdDTQpPa8AlAriHbufeXYLBcodKjfUdkCnaiggX1U/nEP08Zb5GA=="
|
||||
},
|
||||
"Microsoft.Build.Tasks.Git": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
|
||||
},
|
||||
"Microsoft.Data.Sqlite.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.5",
|
||||
"contentHash": "FTerRmQPqHrCrnoUzhBu+E+1DNGwyrAMLqHkAqOOOu5pGfyMOj8qQUBxI/gDtWtG11p49UxSfWmBzRNlwZqfUg==",
|
||||
"dependencies": {
|
||||
"SQLitePCLRaw.core": "2.1.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "nOP8R1mVb/6mZtm2qgAJXn/LFm/2kMjHDAg/QJLFG6CuWYJtaD3p1BwQhufBVvRzL9ceJ/xF0SQ0qsI2GkDQAA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "65MrmXCziWaQFrI0UHkQbesrX5wTwf9XPjY5yFm/VkgJKFJ5gqvXRoXjIZcf2wLi5ZlwGz/oMYfyURVCWbM5iw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Binder": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "vJ9xvOZCnUAIHcGC3SU35r3HKmHTVIeHzo6u/qzlHAqD8m6xv92MLin4oJntTvkpKxVX3vI1GFFkIQtU3AdlsQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "B2WqEox8o+4KUOpL7rZPyh6qYjik8tHi2tN8Z9jZkHzED8ElYgZa/h6K+xliB435SqUcWT290Fr2aa8BtZjn8A=="
|
||||
},
|
||||
"Microsoft.Extensions.Options": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "UpZLNLBpIZ0GTebShui7xXYh6DmBHjWM8NxGxZbdQh/bPZ5e6YswqI+bru6BnEL5eWiOdodsXtEz3FROcgi/qg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
|
||||
"Microsoft.Extensions.Primitives": "2.2.0",
|
||||
"System.ComponentModel.Annotations": "4.5.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Primitives": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==",
|
||||
"dependencies": {
|
||||
"System.Memory": "4.5.1",
|
||||
"System.Runtime.CompilerServices.Unsafe": "4.5.1"
|
||||
}
|
||||
},
|
||||
"Microsoft.SourceLink.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
|
||||
},
|
||||
"Newtonsoft.Json": {
|
||||
"type": "Transitive",
|
||||
"resolved": "13.0.3",
|
||||
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
|
||||
},
|
||||
"SQLitePCLRaw.bundle_e_sqlite3": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.4",
|
||||
"contentHash": "EWI1olKDjFEBMJu0+3wuxwziIAdWDVMYLhuZ3Qs84rrz+DHwD00RzWPZCa+bLnHCf3oJwuFZIRsHT5p236QXww==",
|
||||
"dependencies": {
|
||||
"SQLitePCLRaw.lib.e_sqlite3": "2.1.4",
|
||||
"SQLitePCLRaw.provider.e_sqlite3": "2.1.4"
|
||||
}
|
||||
},
|
||||
"SQLitePCLRaw.core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.4",
|
||||
"contentHash": "inBjvSHo9UDKneGNzfUfDjK08JzlcIhn1+SP5Y3m6cgXpCxXKCJDy6Mka7LpgSV+UZmKSnC8rTwB0SQ0xKu5pA==",
|
||||
"dependencies": {
|
||||
"System.Memory": "4.5.3"
|
||||
}
|
||||
},
|
||||
"SQLitePCLRaw.lib.e_sqlite3": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.4",
|
||||
"contentHash": "2C9Q9eX7CPLveJA0rIhf9RXAvu+7nWZu1A2MdG6SD/NOu26TakGgL1nsbc0JAspGijFOo3HoN79xrx8a368fBg=="
|
||||
},
|
||||
"SQLitePCLRaw.provider.e_sqlite3": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.4",
|
||||
"contentHash": "CSlb5dUp1FMIkez9Iv5EXzpeq7rHryVNqwJMWnpq87j9zWZexaEMdisDktMsnnrzKM6ahNrsTkjqNodTBPBxtQ==",
|
||||
"dependencies": {
|
||||
"SQLitePCLRaw.core": "2.1.4"
|
||||
}
|
||||
},
|
||||
"System.ComponentModel.Annotations": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.0",
|
||||
"contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg=="
|
||||
},
|
||||
"System.Memory": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.3",
|
||||
"contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA=="
|
||||
},
|
||||
"System.Reactive": {
|
||||
"type": "Transitive",
|
||||
"resolved": "5.0.0",
|
||||
"contentHash": "erBZjkQHWL9jpasCE/0qKAryzVBJFxGHVBAvgRN1bzM0q2s1S4oYREEEL0Vb+1kA/6BKb5FjUZMp5VXmy+gzkQ=="
|
||||
},
|
||||
"System.Runtime.CompilerServices.Unsafe": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.1",
|
||||
"contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw=="
|
||||
},
|
||||
"speckle.objects": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Speckle.Sdk": "[1.0.0, )"
|
||||
}
|
||||
},
|
||||
"speckle.sdk": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"GraphQL.Client": "[6.0.0, )",
|
||||
"Microsoft.Data.Sqlite": "[7.0.5, )",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )",
|
||||
"Microsoft.Extensions.Logging": "[2.2.0, )",
|
||||
"Speckle.DoubleNumerics": "[4.1.0, )",
|
||||
"Speckle.Newtonsoft.Json": "[13.0.2, )",
|
||||
"Speckle.Sdk.Dependencies": "[1.0.0, )"
|
||||
}
|
||||
},
|
||||
"speckle.sdk.dependencies": {
|
||||
"type": "Project"
|
||||
},
|
||||
"GraphQL.Client": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[6.0.0, )",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "8yPNBbuVBpTptivyAlak4GZvbwbUcjeQTL4vN1HKHRuOykZ4r7l5fcLS6vpyPyLn0x8FsL31xbOIKyxbmR9rbA==",
|
||||
"dependencies": {
|
||||
"GraphQL.Client.Abstractions": "6.0.0",
|
||||
"GraphQL.Client.Abstractions.Websocket": "6.0.0",
|
||||
"System.Reactive": "5.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Data.Sqlite": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[7.0.5, )",
|
||||
"resolved": "7.0.5",
|
||||
"contentHash": "KGxbPeWsQMnmQy43DSBxAFtHz3l2JX8EWBSGUCvT3CuZ8KsuzbkqMIJMDOxWtG8eZSoCDI04aiVQjWuuV8HmSw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Data.Sqlite.Core": "7.0.5",
|
||||
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.2.0, )",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
|
||||
},
|
||||
"Microsoft.Extensions.Logging": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.2.0, )",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "Nxqhadc9FCmFHzU+fz3oc8sFlE6IadViYg8dfUdGzJZ2JUxnCsRghBhhOWdM4B2zSZqEc+0BjliBh/oNdRZuig==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Binder": "2.2.0",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "2.2.0",
|
||||
"Microsoft.Extensions.Options": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Speckle.DoubleNumerics": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[4.1.0, )",
|
||||
"resolved": "4.1.0",
|
||||
"contentHash": "20DtS+FsDRsOD9+AU3TwNFZ0qrKo5f6f7B5ZR9wStsIHHHC9k7DpjbCvuNtmnSjx54MD+TJC7wV2f5iyGVPj1A=="
|
||||
},
|
||||
"Speckle.Newtonsoft.Json": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[13.0.2, )",
|
||||
"resolved": "13.0.2",
|
||||
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using Speckle.Objects.Geometry;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
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>
|
||||
/// 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>
|
||||
/// If true, the text is oriented to face the screen (camera-aligned).
|
||||
/// </summary>
|
||||
public required bool screenOriented { 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 axis vectors will be ignored if screenOriented is true
|
||||
/// </summary>
|
||||
public required 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,
|
||||
}
|
||||
@@ -12,6 +12,13 @@ public class RevitObject : DataObject, IRevitObject
|
||||
public required string family { get; set; }
|
||||
public required string category { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The level constraint of the object.
|
||||
/// For objects constrained by multiple levels, this represents the base constraint.
|
||||
/// For objects with no level constraint, this should be null.
|
||||
/// </summary>
|
||||
public required string? level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A Curve or Point object representing the location of a Revit element.
|
||||
/// </summary>
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// Flat polygon, defined by an outer boundary and inner loops.
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Geometry.Region")]
|
||||
public class Region : Base, IHasArea, IHasBoundingBox, ITransformable, IDisplayValue<List<Mesh>>
|
||||
{
|
||||
/// <summary>
|
||||
/// Boundary of a region.
|
||||
/// Should be a planar, closed, non-self-intersecting ICurve.
|
||||
/// </summary>
|
||||
public required ICurve boundary { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Loops (voids) in the region.
|
||||
/// Each loop should be planar, closed, non-self-intersecting ICurve, located inside the boundary.
|
||||
/// The loops should not intersect or touch each other.
|
||||
/// </summary>
|
||||
public required List<ICurve> innerLoops { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The units of object's coordinates.
|
||||
/// This should be one of <see cref="Units"/>
|
||||
/// </summary>
|
||||
public required string units { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indication whether the region is just a geometry (false) or has a hatch pattern (true).
|
||||
/// It's a distinction for receiving in apps that support both Region and Hatch (aka region with hatch pattern)
|
||||
/// </summary>
|
||||
public required bool hasHatchPattern { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double area { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Box? bbox { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DetachProperty]
|
||||
public List<Mesh> displayValue { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TransformTo(Transform transform, out ITransformable transformed)
|
||||
{
|
||||
// assign self to the returned object, in case transformation fails
|
||||
transformed = this;
|
||||
|
||||
// transform boundary
|
||||
if (boundary is ITransformable boundaryTransformable)
|
||||
{
|
||||
boundaryTransformable.TransformTo(transform, out ITransformable transformedBoundaryResult);
|
||||
var transformedBoundary = (ICurve)transformedBoundaryResult;
|
||||
|
||||
// transform inner loops
|
||||
var transformedLoops = new List<ICurve>();
|
||||
foreach (var loop in innerLoops)
|
||||
{
|
||||
if (loop is ITransformable loopTransformable)
|
||||
{
|
||||
loopTransformable.TransformTo(transform, out ITransformable transformedLoop);
|
||||
transformedLoops.Add((ICurve)transformedLoop);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// transform display meshes
|
||||
var transformedMeshes = new List<Mesh>();
|
||||
foreach (var mesh in displayValue)
|
||||
{
|
||||
mesh.TransformTo(transform, out ITransformable transformedMesh);
|
||||
transformedMeshes.Add((Mesh)transformedMesh);
|
||||
}
|
||||
|
||||
// if boundary and loops transformations succeeded
|
||||
transformed = new Region
|
||||
{
|
||||
boundary = transformedBoundary,
|
||||
innerLoops = transformedLoops,
|
||||
hasHatchPattern = hasHatchPattern,
|
||||
displayValue = transformedMeshes,
|
||||
units = units,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
[SpeckleType("Objects.Geometry.SolidX")]
|
||||
public class SolidX : RawEncodedObject;
|
||||
@@ -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>>();
|
||||
|
||||
@@ -2,6 +2,7 @@ using Speckle.Objects.Geometry;
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Objects.Primitive;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Models.Data;
|
||||
|
||||
namespace Speckle.Objects;
|
||||
|
||||
@@ -110,15 +111,7 @@ public interface IDisplayValue<out T> : ISpeckleObject
|
||||
|
||||
#region Data objects
|
||||
|
||||
/// <summary>
|
||||
/// Specifies properties on objects to be used for data-based workflows
|
||||
/// </summary>
|
||||
public interface IProperties : ISpeckleObject
|
||||
{
|
||||
Dictionary<string, object?> properties { get; }
|
||||
}
|
||||
|
||||
public interface IDataObject : IProperties, IDisplayValue<IReadOnlyList<Base>>
|
||||
public interface IDataObject : IProperties, IDisplayValue<IReadOnlyList<Base>>, ISpeckleObject
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the object, primarily used to decorate the object for consumption in frontend and other apps
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using Speckle.Objects.Geometry;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Other;
|
||||
|
||||
/// <summary>
|
||||
/// Camera class to represent a perspective camera for a 3D view.
|
||||
/// </summary>
|
||||
/// <remarks>Assumes a Z-up, right-handed convention for orientation vectors</remarks>
|
||||
[SpeckleType("Objects.Other.Camera")]
|
||||
public class Camera : Base
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the view that is created by this camera
|
||||
/// </summary>
|
||||
public required string name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The location of the camera
|
||||
/// </summary>
|
||||
public required Point position { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The unit up vector of the camera
|
||||
/// </summary>
|
||||
public required Vector up { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The unit forward vector of the camera
|
||||
/// </summary>
|
||||
public required Vector forward { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Speckle.Objects.Data;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Models.Proxies;
|
||||
|
||||
namespace Speckle.Objects.Other;
|
||||
|
||||
/// <summary>
|
||||
/// Proxy for levels as DataObject value.
|
||||
/// <remarks> These proxy lives in Objects library because it depends on DataObject</remarks>
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Other.LevelProxy")]
|
||||
public class LevelProxy : Base, IProxyCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of application ids of objects that use this level
|
||||
/// </summary>
|
||||
public required List<string> objects { get; set; }
|
||||
|
||||
public required DataObject value { get; set; }
|
||||
}
|
||||
@@ -20,4 +20,6 @@ public class RawEncoding : Base // note: at this stage, since we're using this f
|
||||
public static class RawEncodingFormats
|
||||
{
|
||||
public const string RHINO_3DM = "3dm";
|
||||
public const string ACAD_DWG = "dwg";
|
||||
public const string ACAD_SAT = "sat";
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Drawing;
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Models.Proxies;
|
||||
|
||||
namespace Speckle.Objects.Other;
|
||||
|
||||
@@ -36,23 +35,6 @@ public class RenderMaterial : Base
|
||||
public Color emissiveColor
|
||||
{
|
||||
get => Color.FromArgb(emissive);
|
||||
set => diffuse = value.ToArgb();
|
||||
set => emissive = value.ToArgb();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to store render material to object relationships in root collections
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Other.RenderMaterialProxy")]
|
||||
public class RenderMaterialProxy : Base, IProxyCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of application ids of objects that use this render material
|
||||
/// </summary>
|
||||
public required List<string> objects { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The render material used by <see cref="objects"/>
|
||||
/// </summary>
|
||||
public required RenderMaterial value { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Models.Proxies;
|
||||
|
||||
namespace Speckle.Objects.Other;
|
||||
|
||||
/// <summary>
|
||||
/// Used to store render material to object relationships in root collections
|
||||
/// <remarks> These proxy lives in Objects library because it depends on RenderMaterial</remarks>
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Other.RenderMaterialProxy")]
|
||||
public class RenderMaterialProxy : Base, IProxyCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of application ids of objects that use this render material
|
||||
/// </summary>
|
||||
public required List<string> objects { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The render material used by <see cref="objects"/>
|
||||
/// </summary>
|
||||
public required RenderMaterial value { get; set; }
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -2,12 +2,6 @@
|
||||
"version": 2,
|
||||
"dependencies": {
|
||||
".NETStandard,Version=v2.0": {
|
||||
"GitVersion.MsBuild": {
|
||||
"type": "Direct",
|
||||
"requested": "[5.12.0, )",
|
||||
"resolved": "5.12.0",
|
||||
"contentHash": "dJuigXycpJNOiLT9or7mkHSkGFHgGW3/p6cNNYEKZBa7Hhp1FdX/cvqYWWYhRLpfoZOedeA7aRbYiOB3vW/dvA=="
|
||||
},
|
||||
"Microsoft.SourceLink.GitHub": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.0, )",
|
||||
@@ -234,7 +228,6 @@
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"GraphQL.Client": "[6.0.0, )",
|
||||
"Microsoft.Bcl.AsyncInterfaces": "[5.0.0, )",
|
||||
"Microsoft.CSharp": "[4.7.0, )",
|
||||
"Microsoft.Data.Sqlite": "[7.0.5, )",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )",
|
||||
@@ -245,7 +238,10 @@
|
||||
}
|
||||
},
|
||||
"speckle.sdk.dependencies": {
|
||||
"type": "Project"
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Microsoft.Bcl.AsyncInterfaces": "[9.0.4, )"
|
||||
}
|
||||
},
|
||||
"GraphQL.Client": {
|
||||
"type": "CentralTransitive",
|
||||
@@ -260,9 +256,9 @@
|
||||
},
|
||||
"Microsoft.Bcl.AsyncInterfaces": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[5.0.0, )",
|
||||
"resolved": "5.0.0",
|
||||
"contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
|
||||
"requested": "[9.0.4, )",
|
||||
"resolved": "9.0.4",
|
||||
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
|
||||
"dependencies": {
|
||||
"System.Threading.Tasks.Extensions": "4.5.4"
|
||||
}
|
||||
@@ -315,12 +311,6 @@
|
||||
}
|
||||
},
|
||||
"net8.0": {
|
||||
"GitVersion.MsBuild": {
|
||||
"type": "Direct",
|
||||
"requested": "[5.12.0, )",
|
||||
"resolved": "5.12.0",
|
||||
"contentHash": "dJuigXycpJNOiLT9or7mkHSkGFHgGW3/p6cNNYEKZBa7Hhp1FdX/cvqYWWYhRLpfoZOedeA7aRbYiOB3vW/dvA=="
|
||||
},
|
||||
"Microsoft.SourceLink.GitHub": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.0, )",
|
||||
@@ -484,7 +474,6 @@
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"GraphQL.Client": "[6.0.0, )",
|
||||
"Microsoft.CSharp": "[4.7.0, )",
|
||||
"Microsoft.Data.Sqlite": "[7.0.5, )",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )",
|
||||
"Microsoft.Extensions.Logging": "[2.2.0, )",
|
||||
@@ -507,12 +496,6 @@
|
||||
"System.Reactive": "5.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.CSharp": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[4.7.0, )",
|
||||
"resolved": "4.7.0",
|
||||
"contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA=="
|
||||
},
|
||||
"Microsoft.Data.Sqlite": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[7.0.5, )",
|
||||
|
||||
@@ -5,6 +5,7 @@ public interface ISdkActivity : IDisposable
|
||||
void SetTag(string key, object? value);
|
||||
void RecordException(Exception e);
|
||||
string TraceId { get; }
|
||||
string SpanId { get; }
|
||||
void SetStatus(SdkActivityStatusCode code);
|
||||
|
||||
void InjectHeaders(Action<string, string> header);
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Speckle.Connectors.Logging;
|
||||
|
||||
namespace Speckle.Sdk.Logging;
|
||||
|
||||
public interface ISdkActivityFactory : IDisposable
|
||||
{
|
||||
ISdkActivity? Start(string? name = default, [CallerMemberName] string source = "");
|
||||
ISdkActivity? Start(
|
||||
string? name = null,
|
||||
SdkActivityKind kind = SdkActivityKind.Internal,
|
||||
[CallerMemberName] string source = ""
|
||||
);
|
||||
|
||||
ISdkActivity? StartRemote(
|
||||
string traceContext,
|
||||
SdkActivityKind kind,
|
||||
string? name = null,
|
||||
[CallerMemberName] string source = ""
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace Speckle.Sdk.Dependencies;
|
||||
|
||||
/// <summary>
|
||||
/// For various reasons related to our use of ILRepack.FullAuto,
|
||||
/// we cannot use Channels from the SDK project.
|
||||
/// We have to keep usage of it inside the Sdk.Dependencies project.
|
||||
///
|
||||
/// For the sake of quick development, I've wrapped the <see cref="Channel"/> class here in a type
|
||||
/// that is safe to use from the SDK project.
|
||||
///
|
||||
/// As and when we need more functions, we can add them here.
|
||||
///
|
||||
/// And yes... I'm not very happy about the way we've set this up
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public sealed class RepackedChannel<T>
|
||||
{
|
||||
private readonly Channel<T> _channel;
|
||||
|
||||
public RepackedChannel(int capacity, bool singleReader, bool singleWriter)
|
||||
{
|
||||
_channel = Channel.CreateBounded<T>(
|
||||
new BoundedChannelOptions(capacity)
|
||||
{
|
||||
FullMode = BoundedChannelFullMode.Wait,
|
||||
SingleReader = singleReader,
|
||||
SingleWriter = singleWriter,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public void CompleteWriter() => _channel.Writer.Complete();
|
||||
|
||||
public ValueTask WriteAsync(T item, CancellationToken cancellationToken) =>
|
||||
_channel.Writer.WriteAsync(item, cancellationToken);
|
||||
|
||||
public IAsyncEnumerable<T> ReadAllAsync(CancellationToken cancellationToken) =>
|
||||
_channel.Reader.ReadAllAsync(cancellationToken);
|
||||
|
||||
// public async Task ReadAllAsync(Func<T, Task> callback, CancellationToken cancellationToken)
|
||||
// {
|
||||
// await foreach (T item in _channel.Reader.ReadAllAsync(cancellationToken))
|
||||
// {
|
||||
// await callback.Invoke(item).ConfigureAwait(false);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -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,7 +5,7 @@ namespace Speckle.Sdk.Dependencies.Serialization;
|
||||
|
||||
public abstract class ChannelLoader<T>(CancellationToken cancellationToken)
|
||||
{
|
||||
private const int RECEIVE_CAPACITY = 5000;
|
||||
private const int RECEIVE_CAPACITY = 10000;
|
||||
|
||||
private const int HTTP_GET_CHUNK_SIZE = 500;
|
||||
private const int MAX_PARALLELISM_HTTP = 4;
|
||||
@@ -109,6 +109,9 @@ public abstract class ChannelLoader<T>(CancellationToken cancellationToken)
|
||||
Exception = ex;
|
||||
_channel.Writer.TryComplete(ex);
|
||||
//cancel everything!
|
||||
_cts.Cancel();
|
||||
if (!_cts.IsCancellationRequested)
|
||||
{
|
||||
_cts.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,18 +5,16 @@ 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 = 10000;
|
||||
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;
|
||||
private const int HTTP_CAPACITY = 500;
|
||||
private const int MAX_CACHE_WRITE_PARALLELISM = 4;
|
||||
private const int MAX_CACHE_BATCH = 500;
|
||||
|
||||
private readonly CancellationTokenSource _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
private const int MAX_CACHE_WRITE_PARALLELISM = 1;
|
||||
private const int MAX_CACHE_BATCH = 1000;
|
||||
|
||||
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, singleReader: true)
|
||||
.WithTimeout(HTTP_BATCH_TIMEOUT)
|
||||
.ReadAllConcurrently(MAX_CACHE_WRITE_PARALLELISM, SaveToCache, _cts.Token)
|
||||
.ReadAllConcurrently(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,58 @@
|
||||
<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>
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0'">
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
|
||||
</ItemGroup>
|
||||
<Target Name="BeforeILRepackPrepareBuild" BeforeTargets="ILRepackPrepareBuild">
|
||||
<ItemGroup>
|
||||
<!--
|
||||
We're Being selective about which assemblies we're il-repacking
|
||||
Avoiding repacling `Microsoft.Bcl.AsyncInterfaces.dll` because we need types like `ValueTask` and `IAsyncEnumerable` to be external
|
||||
|
||||
Yes, this does beg the question, why are we using `IlRepack.FullAuto` instead of raw ILRepack. Well the truth is, I'd like to move away from FullAuto
|
||||
since it's unmaintaned and is lagging behind ILRepack version.
|
||||
-->
|
||||
<_ILRepackIncludeAssemblies_Items Include="$(OutputPath)System.Numerics.Vectors.dll" />
|
||||
<_ILRepackIncludeAssemblies_Items Include="$(OutputPath)System.Runtime.CompilerServices.Unsafe.dll" />
|
||||
<_ILRepackIncludeAssemblies_Items Include="$(OutputPath)System.Memory.dll" />
|
||||
<_ILRepackIncludeAssemblies_Items Include="$(OutputPath)Open.ChannelExtensions.dll" />
|
||||
<_ILRepackIncludeAssemblies_Items Include="$(OutputPath)System.Threading.Channels.dll" />
|
||||
<_ILRepackIncludeAssemblies_Items Include="$(OutputPath)System.Collections.Immutable.dll" />
|
||||
<_ILRepackIncludeAssemblies_Items Include="$(OutputPath)Polly.dll" />
|
||||
<_ILRepackIncludeAssemblies_Items Include="$(OutputPath)Polly.Contrib.WaitAndRetry.dll" />
|
||||
<_ILRepackIncludeAssemblies_Items Include="$(OutputPath)Polly.Extensions.Http.dll" />
|
||||
<_ILRepackIncludeAssemblies_Items Include="$(OutputPath)Microsoft.Extensions.ObjectPool.dll" />
|
||||
<_ILRepackExcludeAssemblies_Items Include="$(OutputPath)*.dll" Exclude="@(_ILRepackIncludeAssemblies_Items)" />
|
||||
</ItemGroup>
|
||||
<Message
|
||||
Text="These are the packages we are NOT ilrepacking '@(_ILRepackExcludeAssemblies_Items)'"
|
||||
Importance="high"
|
||||
/>
|
||||
<PropertyGroup>
|
||||
<ILRepackExcludeAssemblies>@(_ILRepackExcludeAssemblies_Items)</ILRepackExcludeAssemblies>
|
||||
</PropertyGroup>
|
||||
</Target>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
namespace Speckle.Connectors.Logging;
|
||||
|
||||
public enum SdkActivityKind
|
||||
{
|
||||
/// <summary>
|
||||
/// Default value.
|
||||
/// Indicates that the Activity represents an internal operation within an application, as opposed to an operations with remote parents or children.
|
||||
/// </summary>
|
||||
Internal = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Server activity represents request incoming from external component.
|
||||
/// </summary>
|
||||
Server = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Client activity represents outgoing request to the external component.
|
||||
/// </summary>
|
||||
Client = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Producer activity represents output provided to external components.
|
||||
/// </summary>
|
||||
Producer = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Consumer activity represents output received from an external component.
|
||||
/// </summary>
|
||||
Consumer = 4,
|
||||
}
|
||||
@@ -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;
|
||||
@@ -41,13 +41,7 @@ public sealed class SpeckleHttpClientHandler : DelegatingHandler
|
||||
activity?.InjectHeaders((k, v) => request.Headers.TryAddWithoutValidation(k, v));
|
||||
|
||||
var policyResult = await _resiliencePolicy
|
||||
.ExecuteAndCaptureAsync(
|
||||
ctx =>
|
||||
{
|
||||
return base.SendAsync(request, cancellationToken);
|
||||
},
|
||||
context
|
||||
)
|
||||
.ExecuteAndCaptureAsync(ctx => base.SendAsync(request, cancellationToken), context)
|
||||
.ConfigureAwait(false);
|
||||
context.TryGetValue("retryCount", out var retryCount);
|
||||
activity?.SetTag("retryCount", retryCount);
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,12 +2,6 @@
|
||||
"version": 2,
|
||||
"dependencies": {
|
||||
".NETStandard,Version=v2.0": {
|
||||
"GitVersion.MsBuild": {
|
||||
"type": "Direct",
|
||||
"requested": "[5.12.0, )",
|
||||
"resolved": "5.12.0",
|
||||
"contentHash": "dJuigXycpJNOiLT9or7mkHSkGFHgGW3/p6cNNYEKZBa7Hhp1FdX/cvqYWWYhRLpfoZOedeA7aRbYiOB3vW/dvA=="
|
||||
},
|
||||
"ILRepack.FullAuto": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.6.0, )",
|
||||
@@ -17,11 +11,20 @@
|
||||
"ILRepack": "2.0.33"
|
||||
}
|
||||
},
|
||||
"Microsoft.Bcl.AsyncInterfaces": {
|
||||
"type": "Direct",
|
||||
"requested": "[9.0.4, )",
|
||||
"resolved": "9.0.4",
|
||||
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
|
||||
"dependencies": {
|
||||
"System.Threading.Tasks.Extensions": "4.5.4"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
@@ -44,13 +47,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": {
|
||||
@@ -88,11 +91,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"
|
||||
}
|
||||
},
|
||||
@@ -123,8 +126,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"
|
||||
@@ -157,24 +160,9 @@
|
||||
"dependencies": {
|
||||
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
|
||||
}
|
||||
},
|
||||
"Microsoft.Bcl.AsyncInterfaces": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[5.0.0, )",
|
||||
"resolved": "9.0.2",
|
||||
"contentHash": "1CED0BGD7dCKsbe7tDhzpPB2Qdi9x35QChu6zkBEI4s0T5bDkkttGReqQnOeOfRNSxtP2WvpX6Ik/0O93XDuMw==",
|
||||
"dependencies": {
|
||||
"System.Threading.Tasks.Extensions": "4.5.4"
|
||||
}
|
||||
}
|
||||
},
|
||||
"net8.0": {
|
||||
"GitVersion.MsBuild": {
|
||||
"type": "Direct",
|
||||
"requested": "[5.12.0, )",
|
||||
"resolved": "5.12.0",
|
||||
"contentHash": "dJuigXycpJNOiLT9or7mkHSkGFHgGW3/p6cNNYEKZBa7Hhp1FdX/cvqYWWYhRLpfoZOedeA7aRbYiOB3vW/dvA=="
|
||||
},
|
||||
"ILRepack.FullAuto": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.6.0, )",
|
||||
@@ -186,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",
|
||||
@@ -202,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",
|
||||
@@ -241,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",
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using Speckle.InterfaceGenerator;
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Sdk.Api.GraphQL.Resources;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Credentials;
|
||||
using Speckle.Sdk.Helpers;
|
||||
using Speckle.Sdk.Logging;
|
||||
using Speckle.Sdk.Transports;
|
||||
using Speckle.Sdk.Transports.ServerUtils;
|
||||
|
||||
namespace Speckle.Sdk.Api.Blob;
|
||||
|
||||
public partial interface IBlobApi : IDisposable;
|
||||
|
||||
/// <summary>
|
||||
/// Low level access to the blob API
|
||||
/// </summary>
|
||||
/// <seealso cref="FileImportResource"/>
|
||||
/// <seealso cref="ServerApi"/>
|
||||
[GenerateAutoInterface]
|
||||
public sealed class BlobApi : IBlobApi
|
||||
{
|
||||
public const int DEFAULT_TIMEOUT_SECONDS = SpeckleHttp.DEFAULT_TIMEOUT_SECONDS;
|
||||
private static readonly string[] s_filenameSeparator = ["filename="];
|
||||
|
||||
private readonly ISdkActivityFactory _activityFactory;
|
||||
|
||||
/// <summary>
|
||||
/// HTTP client for communicating with Speckle Server with auth token header
|
||||
/// </summary>
|
||||
private readonly HttpClient _authedClient;
|
||||
|
||||
/// <summary>
|
||||
/// HTTP client for communicating with pre-signed s3 url
|
||||
/// </summary>
|
||||
private readonly HttpClient _unauthedClient;
|
||||
|
||||
public BlobApi(
|
||||
ISpeckleHttp speckleHttp,
|
||||
ISdkActivityFactory activityFactory,
|
||||
Account account,
|
||||
int timeoutSeconds = DEFAULT_TIMEOUT_SECONDS
|
||||
)
|
||||
{
|
||||
_activityFactory = activityFactory;
|
||||
_authedClient = speckleHttp.CreateHttpClient(
|
||||
new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip },
|
||||
timeoutSeconds: timeoutSeconds,
|
||||
authorizationToken: account.token
|
||||
);
|
||||
_authedClient.BaseAddress = new(account.serverInfo.url);
|
||||
|
||||
_unauthedClient = speckleHttp.CreateHttpClient(
|
||||
new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip },
|
||||
timeoutSeconds: timeoutSeconds
|
||||
);
|
||||
}
|
||||
|
||||
private static string GetBlobDownloadPath(string blobId, HttpResponseMessage response)
|
||||
{
|
||||
response.Content.Headers.TryGetValues("Content-Disposition", out IEnumerable<string>? cdHeaderValues);
|
||||
var cdHeader = (cdHeaderValues?.FirstOrDefault()).NotNull(
|
||||
"Expected response from server to contain attachment header"
|
||||
);
|
||||
string fileName = cdHeader.Split(s_filenameSeparator, StringSplitOptions.None)[1].TrimStart('"').TrimEnd('"');
|
||||
return Path.Combine(
|
||||
SpecklePathProvider.BlobStoragePath(),
|
||||
$"{blobId[..Models.Blob.LocalHashPrefixLength]}-{fileName}"
|
||||
);
|
||||
}
|
||||
|
||||
/// <param name="blobId">The ID of the blob to download</param>
|
||||
/// <param name="progress"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <exception cref="HttpRequestException">Request for the blob fails</exception>
|
||||
/// <exception cref="OperationCanceledException"></exception>
|
||||
/// <returns>File Path of the downloaded file</returns>
|
||||
public async Task<string> DownloadBlob(
|
||||
string projectId,
|
||||
string blobId,
|
||||
string? pathOverride = null,
|
||||
IProgress<ProgressArgs>? progress = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
using var _ = _activityFactory.Start();
|
||||
|
||||
var url = new Uri($"api/stream/{projectId}/blob/{blobId}", UriKind.Relative);
|
||||
|
||||
using var response = await _authedClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
string fileLocation = pathOverride ?? GetBlobDownloadPath(blobId, response);
|
||||
using var source = new ProgressStream(
|
||||
#if NET5_0_OR_GREATER
|
||||
await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false),
|
||||
#else
|
||||
await response.Content.ReadAsStreamAsync().ConfigureAwait(false),
|
||||
#endif
|
||||
response.Content.Headers.ContentLength,
|
||||
progress,
|
||||
true
|
||||
);
|
||||
|
||||
using var fs = new FileStream(fileLocation, FileMode.OpenOrCreate);
|
||||
#if NET5_0_OR_GREATER
|
||||
await source.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
|
||||
#else
|
||||
await source.CopyToAsync(fs).ConfigureAwait(false);
|
||||
#endif
|
||||
return fileLocation;
|
||||
}
|
||||
|
||||
/// <summary>Queries the server for diff of the given <paramref name="blobIds"/></summary>
|
||||
/// <param name="blobIds"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns>A list of blob ids that the server doesn't have</returns>
|
||||
/// <exception cref="HttpRequestException">Request for the blob fails</exception>
|
||||
/// <exception cref="OperationCanceledException"></exception>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public async Task<List<string>> HasBlobs(
|
||||
string projectId,
|
||||
IReadOnlyCollection<string> blobIds,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
using var _ = _activityFactory.Start();
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var payload = JsonConvert.SerializeObject(blobIds);
|
||||
|
||||
var url = new Uri($"/api/stream/{projectId}/blob/diff", UriKind.Relative);
|
||||
|
||||
using StringContent stringContent = new(payload, Encoding.UTF8, "application/json");
|
||||
|
||||
using var response = await _authedClient.PostAsync(url, stringContent, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
var responseString = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
#else
|
||||
var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
#endif
|
||||
var parsed = JsonConvert
|
||||
.DeserializeObject<List<string>>(responseString)
|
||||
.NotNull($"Failed to deserialize successful response {response.Content}");
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uploads a single file to the given S3 url.
|
||||
/// This method should be used together with the <see cref="FileImportResource"/> <see cref="FileImportResource.GenerateUploadUrl"/> method,
|
||||
/// which generates a pre-signed S3 url, that can be used to upload the file to.
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns>etag header</returns>
|
||||
/// <seealso cref="FileImportResource"/>
|
||||
/// <exception cref="HttpRequestException"></exception>
|
||||
/// <exception cref="ArgumentException">Unexpected response header the server</exception>
|
||||
/// <exception cref="FileNotFoundException"><paramref name="filePath"/> does not point to a file</exception>
|
||||
/// <exception cref="OperationCanceledException"></exception>
|
||||
public async Task<string> UploadFile(
|
||||
string filePath,
|
||||
Uri url,
|
||||
IProgress<ProgressArgs>? progress = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
using var _ = _activityFactory.Start();
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
throw new FileNotFoundException("File not found.", filePath);
|
||||
}
|
||||
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
|
||||
using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
||||
using var requestMessage = new HttpRequestMessage(HttpMethod.Put, url);
|
||||
requestMessage.Content = progress is null
|
||||
? new StreamContent(fileStream)
|
||||
: new ProgressContent(new StreamContent(fileStream), progress);
|
||||
|
||||
requestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
|
||||
requestMessage.Content.Headers.ContentLength = fileInfo.Length;
|
||||
|
||||
using var response = await _unauthedClient.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return BlobApiHelpers.ParseEtagHeader(response.Headers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uploads blobs via the <c>"/api/stream/:streamId/blob"</c> endpoint
|
||||
/// </summary>
|
||||
/// <param name="projectId"></param>
|
||||
/// <param name="blobPaths"></param>
|
||||
/// <param name="progress"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
public async Task UploadBlobs(
|
||||
string projectId,
|
||||
IReadOnlyCollection<(string id, string filePath)> blobPaths,
|
||||
IProgress<ProgressArgs>? progress,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
using var _ = _activityFactory.Start();
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (blobPaths.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var multipartFormDataContent = new MultipartFormDataContent();
|
||||
foreach (var (id, filePath) in blobPaths)
|
||||
{
|
||||
var fileName = Path.GetFileName(filePath);
|
||||
|
||||
var stream = File.OpenRead(filePath);
|
||||
var fsc = new StreamContent(stream);
|
||||
multipartFormDataContent.Add(fsc, $"hash:{id}", fileName);
|
||||
}
|
||||
|
||||
using HttpContent content = progress is null
|
||||
? multipartFormDataContent
|
||||
: new ProgressContent(multipartFormDataContent, progress);
|
||||
|
||||
var url = new Uri($"/api/stream/{projectId}/blob", UriKind.Relative);
|
||||
|
||||
using var response = await _authedClient.PostAsync(url, content, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
[AutoInterfaceIgnore]
|
||||
public void Dispose()
|
||||
{
|
||||
_authedClient.Dispose();
|
||||
_unauthedClient.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Speckle.InterfaceGenerator;
|
||||
using Speckle.Sdk.Credentials;
|
||||
using Speckle.Sdk.Helpers;
|
||||
using Speckle.Sdk.Logging;
|
||||
|
||||
namespace Speckle.Sdk.Api.Blob;
|
||||
|
||||
[GenerateAutoInterface]
|
||||
public sealed class BlobApiFactory(ISpeckleHttp speckleHttp, ISdkActivityFactory activityFactory) : IBlobApiFactory
|
||||
{
|
||||
public IBlobApi Create(Account account, int timeoutSeconds = BlobApi.DEFAULT_TIMEOUT_SECONDS) =>
|
||||
new BlobApi(speckleHttp, activityFactory, account, timeoutSeconds);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Speckle.Sdk.Api.GraphQL;
|
||||
using Speckle.Sdk.Api.GraphQL.Models;
|
||||
|
||||
namespace Speckle.Sdk.Api;
|
||||
|
||||
@@ -22,10 +23,15 @@ public class SpeckleGraphQLException : SpeckleException
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a "FORBIDDEN" or "UNAUTHORIZED" GraphQL error as an exception.
|
||||
/// Represents a "FORBIDDEN" or "UNAUTHENTICATED" or "UNAUTHORIZED" or "UNAUTHORIZED_ACCESS_ERROR" GraphQL error as an exception.
|
||||
/// https://www.apollographql.com/docs/apollo-server/v2/data/errors/#unauthenticated
|
||||
/// https://www.apollographql.com/docs/apollo-server/v2/data/errors/#forbidden
|
||||
/// https://github.com/specklesystems/speckle-server/blob/v2.23.18/packages/server/modules/shared/errors/index.ts#L34
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Server is a bit inconsistent with these error codes, hence there's 4 different codes that mean "auth no work"
|
||||
/// Apollo no longer considers "FORBIDDEN" or "UNAUTHENTICATED" as built in error codes, so everything is custom anyway.
|
||||
/// </remarks>
|
||||
public sealed class SpeckleGraphQLForbiddenException : SpeckleGraphQLException
|
||||
{
|
||||
public SpeckleGraphQLForbiddenException() { }
|
||||
@@ -96,3 +102,43 @@ public sealed class SpeckleGraphQLInvalidQueryException : SpeckleGraphQLExceptio
|
||||
public SpeckleGraphQLInvalidQueryException(string? message, Exception? innerException)
|
||||
: base(message, innerException) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a <c>WORKSPACES_MODULE_DISABLED_ERROR</c> GraphQL error as an exception
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A GraphQL request for workspace resources was made to a server that does not have the <c>FF_WORKSPACES_MODULE_ENABLED</c> feature flag enabled
|
||||
/// </remarks>
|
||||
public sealed class SpeckleGraphQLWorkspaceNotEnabledException : SpeckleGraphQLException
|
||||
{
|
||||
public SpeckleGraphQLWorkspaceNotEnabledException() { }
|
||||
|
||||
public SpeckleGraphQLWorkspaceNotEnabledException(string? message)
|
||||
: base(message) { }
|
||||
|
||||
public SpeckleGraphQLWorkspaceNotEnabledException(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) { }
|
||||
}
|
||||
|
||||
public sealed class CannotCreateCommitException : SpeckleGraphQLException
|
||||
{
|
||||
public CannotCreateCommitException() { }
|
||||
|
||||
public CannotCreateCommitException(string? message)
|
||||
: base(message) { }
|
||||
|
||||
public CannotCreateCommitException(string? message, Exception? innerException)
|
||||
: base(message, innerException) { }
|
||||
}
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Net.WebSockets;
|
||||
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.Blob;
|
||||
using Speckle.Sdk.Api.GraphQL;
|
||||
using Speckle.Sdk.Api.GraphQL.Resources;
|
||||
using Speckle.Sdk.Api.GraphQL.Serializer;
|
||||
using Speckle.Sdk.Credentials;
|
||||
using Speckle.Sdk.Dependencies;
|
||||
using Speckle.Sdk.Helpers;
|
||||
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,14 +32,17 @@ public sealed class Client : ISpeckleGraphQLClient, IDisposable
|
||||
public ProjectInviteResource ProjectInvite { get; }
|
||||
public CommentResource Comment { get; }
|
||||
public SubscriptionResource Subscription { get; }
|
||||
public WorkspaceResource Workspace { get; }
|
||||
public ServerResource Server { get; }
|
||||
public FileImportResource FileImport { get; }
|
||||
public ModelIngestionResource Ingestion { get; }
|
||||
|
||||
public Uri ServerUrl => new(Account.serverInfo.url);
|
||||
|
||||
[JsonIgnore]
|
||||
public Account Account { get; }
|
||||
|
||||
private HttpClient HttpClient { get; }
|
||||
|
||||
[AutoInterfaceIgnore]
|
||||
public GraphQLHttpClient GQLClient { get; }
|
||||
|
||||
/// <param name="account"></param>
|
||||
@@ -44,14 +50,16 @@ public sealed class Client : ISpeckleGraphQLClient, IDisposable
|
||||
public Client(
|
||||
ILogger<Client> logger,
|
||||
ISdkActivityFactory activityFactory,
|
||||
ISpeckleApplication application,
|
||||
ISpeckleHttp speckleHttp,
|
||||
Account account
|
||||
IGraphQLClientFactory graphqlClientFactory,
|
||||
IBlobApiFactory blobApiFactory,
|
||||
[NotNull] Account? account
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_activityFactory = activityFactory;
|
||||
|
||||
Account = account ?? throw new ArgumentException("Provided account is null.");
|
||||
GQLClient = graphqlClientFactory.CreateGraphQLClient(account);
|
||||
|
||||
Project = new(this);
|
||||
Model = new(this);
|
||||
@@ -61,22 +69,44 @@ public sealed class Client : ISpeckleGraphQLClient, IDisposable
|
||||
ProjectInvite = new(this);
|
||||
Comment = new(this);
|
||||
Subscription = new(this);
|
||||
|
||||
HttpClient = CreateHttpClient(application, speckleHttp, account);
|
||||
|
||||
GQLClient = CreateGraphQLClient(account, HttpClient);
|
||||
Workspace = new(this);
|
||||
Server = new(this);
|
||||
FileImport = new(this, blobApiFactory.Create(account));
|
||||
Ingestion = new(this);
|
||||
}
|
||||
|
||||
[AutoInterfaceIgnore]
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
FileImport.Dispose();
|
||||
Subscription.Dispose();
|
||||
GQLClient.Dispose();
|
||||
}
|
||||
catch (Exception ex) when (!ex.IsFatal()) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure the <see cref="GQLClient"/>'s websocket is fully initialized.
|
||||
/// <br/>
|
||||
/// You don't <i>need</i> to call this function, if you don't, then it will be setup for you when you call <see cref="SubscribeTo"/> (e.g. when you create a <see cref="Subscription"/>),
|
||||
/// but due to <see cref="GraphQL"/>'s WebSocket implementation, it's not awaited (deferred) thus the subscription make take a while to actually be setup.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We only use websockets for GraphQL subscriptions, so if you're not using subscriptions, don't call this
|
||||
///
|
||||
/// Note. due to other sources (potentially on the GraphQL side) you still need a ~100ms delay between setting up the subscription, and being able to relaibly trigger it
|
||||
/// This should only really negatively affect test projects.
|
||||
/// </remarks>
|
||||
public async Task InitializeWebsocket()
|
||||
{
|
||||
if (GQLClient.WebSocketSubProtocol is null)
|
||||
{
|
||||
await GQLClient.InitializeWebsocketConnection().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task<T> ExecuteWithResiliencePolicies<T>(Func<Task<T>> func) =>
|
||||
await GraphQLRetry
|
||||
.ExecuteAsync<T, SpeckleGraphQLInternalErrorException>(
|
||||
@@ -96,7 +126,7 @@ public sealed class Client : ISpeckleGraphQLClient, IDisposable
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}" />
|
||||
public async Task<T> ExecuteGraphQLRequest<T>(GraphQLRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var activity = _activityFactory.Start();
|
||||
@@ -132,6 +162,7 @@ public sealed class Client : ISpeckleGraphQLClient, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
[AutoInterfaceIgnore]
|
||||
IDisposable ISpeckleGraphQLClient.SubscribeTo<T>(GraphQLRequest request, Action<object, T> callback) =>
|
||||
SubscribeTo(request, callback);
|
||||
|
||||
@@ -176,62 +207,4 @@ public sealed class Client : ISpeckleGraphQLClient, IDisposable
|
||||
throw new SpeckleGraphQLException($"Subscription for {typeof(T)} failed to start", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static GraphQLHttpClient CreateGraphQLClient(Account account, HttpClient httpClient)
|
||||
{
|
||||
var gQLClient = new GraphQLHttpClient(
|
||||
new GraphQLHttpClientOptions
|
||||
{
|
||||
EndPoint = new Uri(new Uri(account.serverInfo.url), "/graphql"),
|
||||
UseWebSocketForQueriesAndMutations = false,
|
||||
WebSocketProtocol = "graphql-ws",
|
||||
ConfigureWebSocketConnectionInitPayload = _ =>
|
||||
{
|
||||
return SpeckleHttp.CanAddAuth(account.token, out string? authValue)
|
||||
? new { Authorization = authValue }
|
||||
: null;
|
||||
},
|
||||
},
|
||||
new NewtonsoftJsonSerializer(
|
||||
new JsonSerializerSettings()
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver { IgnoreIsSpecifiedMembers = true }, //(Default)
|
||||
MissingMemberHandling = MissingMemberHandling.Error, //(not default) If you query for a member that doesn't exist, this will throw (except websocket responses see https://github.com/graphql-dotnet/graphql-client/issues/660)
|
||||
Converters =
|
||||
{
|
||||
new ConstantCaseEnumConverter(),
|
||||
} //(Default) enums will be serialized using the GraphQL const case standard
|
||||
,
|
||||
}
|
||||
),
|
||||
httpClient
|
||||
);
|
||||
|
||||
gQLClient.WebSocketReceiveErrors.Subscribe(e =>
|
||||
{
|
||||
if (e is WebSocketException we)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"WebSocketException: {we.Message} (WebSocketError {we.WebSocketErrorCode}, ErrorCode {we.ErrorCode}, NativeErrorCode {we.NativeErrorCode}"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Exception in websocket receive stream: {e}");
|
||||
}
|
||||
});
|
||||
return gQLClient;
|
||||
}
|
||||
|
||||
private static HttpClient CreateHttpClient(ISpeckleApplication application, ISpeckleHttp speckleHttp, Account account)
|
||||
{
|
||||
var httpClient = speckleHttp.CreateHttpClient(timeoutSeconds: 30, authorizationToken: account.token);
|
||||
|
||||
httpClient.DefaultRequestHeaders.Add("apollographql-client-name", application.ApplicationAndVersion);
|
||||
httpClient.DefaultRequestHeaders.Add(
|
||||
"apollographql-client-version",
|
||||
Assembly.GetExecutingAssembly().GetName().Version?.ToString()
|
||||
);
|
||||
return httpClient;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Speckle.InterfaceGenerator;
|
||||
using Speckle.Sdk.Api.Blob;
|
||||
using Speckle.Sdk.Credentials;
|
||||
using Speckle.Sdk.Helpers;
|
||||
using Speckle.Sdk.Logging;
|
||||
|
||||
namespace Speckle.Sdk.Api;
|
||||
@@ -10,10 +10,10 @@ namespace Speckle.Sdk.Api;
|
||||
public class ClientFactory(
|
||||
ILoggerFactory loggerFactory,
|
||||
ISdkActivityFactory activityFactory,
|
||||
ISpeckleApplication application,
|
||||
ISpeckleHttp speckleHttp
|
||||
IGraphQLClientFactory graphQLClientFactory,
|
||||
IBlobApiFactory blobApiFactory
|
||||
) : 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, graphQLClientFactory, blobApiFactory, account);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||
// ReSharper disable InconsistentNaming
|
||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||
|
||||
//This enum isn't explicitly defined in the schema, instead its usages are int typed (But represent an enum)
|
||||
/// <remarks>
|
||||
/// This enum isn't explicitly defined in the schema, instead its usages are int typed (But represent an enum)
|
||||
/// </remarks>
|
||||
public enum FileUploadConversionStatus
|
||||
{
|
||||
Queued = 0,
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// ReSharper disable InconsistentNaming
|
||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||
|
||||
/// <remarks>
|
||||
/// string based enum
|
||||
/// </remarks>
|
||||
public enum ModelIngestionStatus
|
||||
{
|
||||
cancelled,
|
||||
failed,
|
||||
processing,
|
||||
queued,
|
||||
success,
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||
|
||||
/// <remarks>
|
||||
/// string based enum
|
||||
/// </remarks>
|
||||
public enum ProjectCommentsUpdatedMessageType
|
||||
{
|
||||
ARCHIVED,
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||
|
||||
/// <remarks>
|
||||
/// string based enum
|
||||
/// </remarks>
|
||||
public enum ProjectFileImportUpdatedMessageType
|
||||
{
|
||||
CREATED,
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// ReSharper disable InconsistentNaming
|
||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||
|
||||
/// <remarks>
|
||||
/// string based enum
|
||||
/// </remarks>
|
||||
public enum ProjectModelIngestionUpdatedMessageType
|
||||
{
|
||||
cancellationRequested,
|
||||
created,
|
||||
deleted,
|
||||
updated,
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||
|
||||
/// <remarks>
|
||||
/// string based enum
|
||||
/// </remarks>
|
||||
public enum ProjectModelsUpdatedMessageType
|
||||
{
|
||||
CREATED,
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||
|
||||
/// <remarks>
|
||||
/// string based enum
|
||||
/// </remarks>
|
||||
public enum ProjectPendingModelsUpdatedMessageType
|
||||
{
|
||||
CREATED,
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||
|
||||
/// <remarks>
|
||||
/// string based enum
|
||||
/// </remarks>
|
||||
public enum ProjectUpdatedMessageType
|
||||
{
|
||||
DELETED,
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||
|
||||
/// <remarks>
|
||||
/// string based enum
|
||||
/// </remarks>
|
||||
public enum ProjectVersionsUpdatedMessageType
|
||||
{
|
||||
CREATED,
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||
// ReSharper disable InconsistentNaming
|
||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||
|
||||
/// <remarks>
|
||||
/// string based enum
|
||||
/// </remarks>
|
||||
public enum ProjectVisibility
|
||||
{
|
||||
Private,
|
||||
Public,
|
||||
|
||||
[Obsolete("Use Public instead")]
|
||||
Unlisted,
|
||||
Workspace,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||
// ReSharper disable InconsistentNaming
|
||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||
|
||||
/// <remarks>
|
||||
/// string based enum
|
||||
/// </remarks>
|
||||
public enum ResourceType
|
||||
{
|
||||
commit,
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||
|
||||
/// <remarks>
|
||||
/// string based enum
|
||||
/// </remarks>
|
||||
public enum UserProjectsUpdatedMessageType
|
||||
{
|
||||
ADDED,
|
||||
|
||||
@@ -28,10 +28,13 @@ internal static class GraphQLErrorHandler
|
||||
var ex = code switch
|
||||
{
|
||||
"GRAPHQL_PARSE_FAILED" or "GRAPHQL_VALIDATION_FAILED" => new SpeckleGraphQLInvalidQueryException(message),
|
||||
"FORBIDDEN" or "UNAUTHENTICATED" => new SpeckleGraphQLForbiddenException(message),
|
||||
"FORBIDDEN" or "UNAUTHENTICATED" or "UNAUTHORIZED" or "UNAUTHORIZED_ACCESS_ERROR" =>
|
||||
new SpeckleGraphQLForbiddenException(message),
|
||||
"STREAM_NOT_FOUND" => new SpeckleGraphQLStreamNotFoundException(message),
|
||||
"BAD_USER_INPUT" => new SpeckleGraphQLBadInputException(message),
|
||||
"INTERNAL_SERVER_ERROR" => new SpeckleGraphQLInternalErrorException(message),
|
||||
"WORKSPACES_MODULE_DISABLED_ERROR" => new SpeckleGraphQLWorkspaceNotEnabledException(message),
|
||||
"COMMIT_CREATE_ERROR" => new CannotCreateCommitException(message),
|
||||
_ => new SpeckleGraphQLException(message),
|
||||
};
|
||||
exceptions.Add(ex);
|
||||
|
||||
@@ -40,7 +40,8 @@ public static class GraphQLHttpClientExtensions
|
||||
response.EnsureGraphQLSuccess();
|
||||
|
||||
string versionString = response.Data.data.data;
|
||||
if (versionString == "dev")
|
||||
//Local server builds will have a non-numerical version string
|
||||
if (versionString == "dev" || versionString == "custom")
|
||||
{
|
||||
return new Version(999, 999, 999);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
namespace Speckle.Sdk.Api.GraphQL.Inputs;
|
||||
|
||||
internal sealed record CommentContentInput(IReadOnlyCollection<string>? blobIds, object? doc);
|
||||
internal record CommentContentInput(IReadOnlyCollection<string>? blobIds, object? doc);
|
||||
|
||||
internal sealed record CreateCommentInput(
|
||||
internal record CreateCommentInput(
|
||||
CommentContentInput content,
|
||||
string projectId,
|
||||
string resourceIdString,
|
||||
@@ -10,10 +10,10 @@ internal sealed record CreateCommentInput(
|
||||
object? viewerState
|
||||
);
|
||||
|
||||
internal sealed record EditCommentInput(CommentContentInput content, string commentId, string projectId);
|
||||
internal record EditCommentInput(CommentContentInput content, string commentId, string projectId);
|
||||
|
||||
internal sealed record CreateCommentReplyInput(CommentContentInput content, string threadId, string projectId);
|
||||
internal record CreateCommentReplyInput(CommentContentInput content, string threadId, string projectId);
|
||||
|
||||
public sealed record MarkCommentViewedInput(string commentId, string projectId);
|
||||
public record MarkCommentViewedInput(string commentId, string projectId);
|
||||
|
||||
public sealed record ArchiveCommentInput(string commentId, string projectId, bool archived = true);
|
||||
public record ArchiveCommentInput(string commentId, string projectId, bool archived = true);
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
namespace Speckle.Sdk.Api.GraphQL.Inputs;
|
||||
|
||||
public record GenerateFileUploadUrlInput(string projectId, string fileName);
|
||||
|
||||
[Obsolete(FileImportInputBase.FILE_IMPORT_DEPRECATION_MESSAGE)]
|
||||
public record StartFileImportInput(string projectId, string modelId, string fileId, string etag);
|
||||
|
||||
[Obsolete(FileImportInputBase.FILE_IMPORT_DEPRECATION_MESSAGE)]
|
||||
public record FileImportResult(
|
||||
double durationSeconds,
|
||||
double downloadDurationSeconds,
|
||||
double parseDurationSeconds,
|
||||
string parser,
|
||||
string? versionId
|
||||
);
|
||||
|
||||
public abstract class FileImportInputBase
|
||||
{
|
||||
internal const string FILE_IMPORT_DEPRECATION_MESSAGE =
|
||||
"Part of the old API surface and will be removed in the future. Use the new ingestion API instead. Field will be deleted on June 1st, 2026";
|
||||
|
||||
[Obsolete(FileImportInputBase.FILE_IMPORT_DEPRECATION_MESSAGE)]
|
||||
protected FileImportInputBase() { }
|
||||
|
||||
public required string projectId { get; init; }
|
||||
public required string jobId { get; init; }
|
||||
public required IReadOnlyCollection<string> warnings { get; init; }
|
||||
|
||||
[Obsolete(FileImportInputBase.FILE_IMPORT_DEPRECATION_MESSAGE)]
|
||||
public required FileImportResult result { get; init; }
|
||||
}
|
||||
|
||||
#pragma warning disable CA1822 //Mark members as static
|
||||
|
||||
[Obsolete(FILE_IMPORT_DEPRECATION_MESSAGE)]
|
||||
public sealed class FileImportSuccessInput() : FileImportInputBase()
|
||||
{
|
||||
public const string TYPE_STATUS = "success";
|
||||
|
||||
public string status => TYPE_STATUS;
|
||||
}
|
||||
|
||||
[Obsolete(FILE_IMPORT_DEPRECATION_MESSAGE)]
|
||||
public sealed class FileImportErrorInput() : FileImportInputBase()
|
||||
{
|
||||
public const string TYPE_STATUS = "error";
|
||||
|
||||
public string status => TYPE_STATUS;
|
||||
public required string reason { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Sdk.Api.GraphQL.Enums;
|
||||
|
||||
namespace Speckle.Sdk.Api.GraphQL.Inputs;
|
||||
|
||||
public record SourceDataInput(
|
||||
string sourceApplicationSlug,
|
||||
string sourceApplicationVersion,
|
||||
string? fileName,
|
||||
long? fileSizeBytes
|
||||
);
|
||||
|
||||
public record ModelIngestionCreateInput(
|
||||
string modelId,
|
||||
string projectId,
|
||||
string progressMessage,
|
||||
SourceDataInput sourceData,
|
||||
int? maxIdleTimeoutSeconds = null
|
||||
);
|
||||
|
||||
public record ModelIngestionUpdateInput(string ingestionId, string projectId, string progressMessage, double? progress);
|
||||
|
||||
public record ModelIngestionSuccessInput(
|
||||
string ingestionId,
|
||||
string projectId,
|
||||
string rootObjectId,
|
||||
string? versionMessage
|
||||
);
|
||||
|
||||
public record ModelIngestionFailedInput(
|
||||
string ingestionId,
|
||||
string projectId,
|
||||
string errorReason,
|
||||
string? errorStacktrace
|
||||
)
|
||||
{
|
||||
public static ModelIngestionFailedInput FromException(string ingestionId, string projectId, Exception ex)
|
||||
{
|
||||
return new ModelIngestionFailedInput(ingestionId, projectId, ex.Message, ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public record ModelIngestionCancelledInput(string ingestionId, string projectId, string cancellationMessage);
|
||||
|
||||
public record ModelIngestionStartProcessingInput(
|
||||
string ingestionId,
|
||||
string projectId,
|
||||
string progressMessage,
|
||||
SourceDataInput sourceData
|
||||
);
|
||||
|
||||
public record ModelIngestionRequeueInput(string ingestionId, string projectId, string progressMessage);
|
||||
|
||||
public record ProjectModelIngestionSubscriptionInput(
|
||||
string projectId,
|
||||
ModelIngestionReference ingestionReference,
|
||||
[property: JsonIgnore] ProjectModelIngestionUpdatedMessageType messageType
|
||||
)
|
||||
{
|
||||
// The Newtonsoft serializer is setup to handle SCREAMING_CASE enums.
|
||||
// But the API requires the enum to look exactly like they are
|
||||
[JsonProperty(nameof(messageType))]
|
||||
public string serializedType => messageType.ToString();
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// <c>@oneOf</c> i.e. server expects <b>either</b> <paramref name="ingestionId"/> or <paramref name="modelId"/>, but not both.
|
||||
/// </remarks>
|
||||
/// <param name="ingestionId"></param>
|
||||
/// <param name="modelId"></param>
|
||||
public record ModelIngestionReference(string? ingestionId, string? modelId);
|
||||
@@ -1,9 +1,9 @@
|
||||
namespace Speckle.Sdk.Api.GraphQL.Inputs;
|
||||
|
||||
public sealed record CreateModelInput(string name, string? description, string projectId);
|
||||
public record CreateModelInput(string name, string? description, string projectId);
|
||||
|
||||
public sealed record DeleteModelInput(string id, string projectId);
|
||||
public record DeleteModelInput(string id, string projectId);
|
||||
|
||||
public sealed record UpdateModelInput(string id, string? name, string? description, string projectId);
|
||||
public record UpdateModelInput(string id, string? name, string? description, string projectId);
|
||||
|
||||
public sealed record ModelVersionsFilter(IReadOnlyList<string> priorityIds, bool? priorityIdsOnly);
|
||||
public record ModelVersionsFilter(IReadOnlyList<string> priorityIds, bool? priorityIdsOnly);
|
||||
|
||||
@@ -2,15 +2,22 @@
|
||||
|
||||
namespace Speckle.Sdk.Api.GraphQL.Inputs;
|
||||
|
||||
public sealed record ProjectCommentsFilter(bool? includeArchived, bool? loadedVersionsOnly, string? resourceIdString);
|
||||
public record ProjectCommentsFilter(bool? includeArchived, bool? loadedVersionsOnly, string? resourceIdString);
|
||||
|
||||
public sealed record ProjectCreateInput(string? name, string? description, ProjectVisibility? visibility);
|
||||
public record ProjectCreateInput(string? name, string? description, ProjectVisibility? visibility);
|
||||
|
||||
public sealed record ProjectInviteCreateInput(string? email, string? role, string? serverRole, string? userId);
|
||||
public record WorkspaceProjectCreateInput(
|
||||
string? name,
|
||||
string? description,
|
||||
ProjectVisibility? visibility,
|
||||
string workspaceId
|
||||
);
|
||||
|
||||
public sealed record ProjectInviteUseInput(bool accept, string projectId, string token);
|
||||
public record ProjectInviteCreateInput(string? email, string? role, string? serverRole, string? userId);
|
||||
|
||||
public sealed record ProjectModelsFilter(
|
||||
public record ProjectInviteUseInput(bool accept, string projectId, string token);
|
||||
|
||||
public record ProjectModelsFilter(
|
||||
IReadOnlyList<string>? contributors = null,
|
||||
IReadOnlyList<string>? excludeIds = null,
|
||||
IReadOnlyList<string>? ids = null,
|
||||
@@ -19,7 +26,7 @@ public sealed record ProjectModelsFilter(
|
||||
IReadOnlyList<string>? sourceApps = null
|
||||
);
|
||||
|
||||
public sealed record ProjectUpdateInput(
|
||||
public record ProjectUpdateInput(
|
||||
string id,
|
||||
string? name = null,
|
||||
string? description = null,
|
||||
@@ -27,6 +34,6 @@ public sealed record ProjectUpdateInput(
|
||||
ProjectVisibility? visibility = null
|
||||
);
|
||||
|
||||
public sealed record ProjectUpdateRoleInput(string userId, string projectId, string? role);
|
||||
public record ProjectUpdateRoleInput(string userId, string projectId, string? role);
|
||||
|
||||
public sealed record UserProjectsFilter(string search, IReadOnlyList<string>? onlyWithRoles = null);
|
||||
public record WorkspaceProjectsFilter(string? search, bool? withProjectRoleOnly);
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
namespace Speckle.Sdk.Api.GraphQL.Inputs;
|
||||
|
||||
public sealed record ViewerUpdateTrackingTarget(
|
||||
string projectId,
|
||||
string resourceIdString,
|
||||
bool? loadedVersionsOnly = null
|
||||
);
|
||||
public record ViewerUpdateTrackingTarget(string projectId, string resourceIdString, bool? loadedVersionsOnly = null);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user