From ea92af57f8bf1b07819deef4f34479528f41d4f5 Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Wed, 3 Aug 2022 23:36:03 +0800 Subject: [PATCH] Move Rust backend to rspc (#345) * move Rust backend to rspc * move server to Axum + remove parts of old bridge * move frontend over to @rspc/client * move core to rspc + update deps * fix Typescript errors + upgrade deps * document invalidate_query! macro * general cleanup + upgrade to PCR 0.6.0 * prisma error handling * upgrade to rspc 0.0.4 * update vite-plugin-ssr * fix typescript * fix builds * put landing page app name back * hardcode ffmpeg version on Windows * rename 'command' to 'mutation' to line up with react-query terminology * upgrade rspc to v0.0.5 + fix types * use shared ffmpeg binaries * general Typescript cleanup * fix clippy workflow failing due to tauri proc-macro Co-authored-by: Brendan Allan --- .cspell/project_words.txt | 6 +- .eslintrc.js | 38 +- .github/scripts/setup-system.ps1 | 8 +- .github/workflows/clippy.yml | 4 + Cargo.lock | 1044 ++- README.md | 14 +- apps/desktop/dist/.placeholder | 0 apps/desktop/package.json | 30 +- apps/desktop/src-tauri/Cargo.toml | 18 +- apps/desktop/src-tauri/rustfmt.toml | 1 - apps/desktop/src-tauri/src/main.rs | 102 +- apps/desktop/src/index.tsx | 61 +- apps/landing/package.json | 117 +- apps/landing/server/index.ts | 61 +- apps/landing/server/tsconfig.json | 22 - apps/landing/src/App.tsx | 2 +- apps/landing/src/components/NavBar.tsx | 14 +- .../src/pages/blog/index.page.server.ts | 9 +- apps/landing/src/pages/blog/index.page.tsx | 2 +- .../landing/src/pages/blog/post.page.route.ts | 2 +- .../src/pages/blog/post.page.server.ts | 11 +- apps/landing/src/pages/blog/post.page.tsx | 4 +- apps/landing/src/pages/careers.page.tsx | 4 +- apps/landing/src/pages/changelog.page.tsx | 2 +- .../architecture/distributed-data-sync.tsx | 2 +- apps/landing/src/pages/faq.page.tsx | 2 +- apps/landing/src/pages/index.page.tsx | 2 +- apps/landing/src/pages/roadmap.page.tsx | 2 +- apps/landing/src/pages/team.page.tsx | 2 +- .../src/renderer/_default.page.client.tsx | 55 +- .../src/renderer/_default.page.server.tsx | 2 +- apps/landing/tsconfig.json | 15 +- apps/landing/vite.config.ts | 12 +- apps/server/Cargo.toml | 10 +- apps/server/src/main.rs | 319 +- apps/server/src/utils.rs | 28 + apps/web/package.json | 22 +- apps/web/src/App.tsx | 150 +- core/.rustfmt.toml | 3 +- core/Cargo.toml | 17 +- core/bindings/Client.ts | 10 - core/bindings/ClientCommand.ts | 4 - core/bindings/ClientQuery.ts | 4 - core/bindings/ClientState.ts | 11 - core/bindings/ConfigMetadata.ts | 3 - core/bindings/CoreEvent.ts | 5 - core/bindings/CoreResource.ts | 7 - core/bindings/CoreResponse.ts | 12 - core/bindings/DirectoryWithContents.ts | 4 - core/bindings/EncryptionAlgorithm.ts | 2 - core/bindings/File.ts | 5 - core/bindings/FileKind.ts | 3 - core/bindings/FilePath.ts | 4 - core/bindings/JobReport.ts | 4 - core/bindings/JobStatus.ts | 3 - core/bindings/LibraryCommand.ts | 3 - core/bindings/LibraryConfig.ts | 3 - core/bindings/LibraryConfigWrapped.ts | 4 - core/bindings/LibraryNode.ts | 4 - core/bindings/LibraryQuery.ts | 3 - core/bindings/LibraryState.ts | 3 - core/bindings/LocationResource.ts | 4 - core/bindings/NodeConfig.ts | 3 - core/bindings/NodeState.ts | 3 - core/bindings/Platform.ts | 3 - core/bindings/Statistics.ts | 3 - core/bindings/Tag.ts | 3 - core/bindings/TagOnFile.ts | 5 - core/bindings/TagWithFiles.ts | 5 - core/bindings/Volume.ts | 3 - core/index.ts | 148 +- core/package.json | 7 +- core/prisma/Cargo.toml | 2 +- core/src/api/files.rs | 105 + core/src/api/jobs.rs | 88 + core/src/api/libraries.rs | 101 + core/src/api/locations.rs | 184 + core/src/api/mod.rs | 137 + core/src/api/tags.rs | 139 + core/src/api/utils/invalidate.rs | 119 + core/src/api/utils/mod.rs | 3 + core/src/api/volumes.rs | 7 + core/src/encode/thumb.rs | 24 +- core/src/file/cas/identifier.rs | 84 +- core/src/file/explorer/mod.rs | 3 - core/src/file/{indexer/mod.rs => indexer.rs} | 11 +- core/src/file/mod.rs | 199 - core/src/job/job_manager.rs | 50 +- core/src/job/mod.rs | 10 +- core/src/job/worker.rs | 85 +- core/src/lib.rs | 603 +- core/src/library/library_config.rs | 8 +- core/src/library/library_ctx.rs | 20 +- core/src/library/library_manager.rs | 46 +- core/src/library/loader.rs | 1 - core/src/library/mod.rs | 15 - core/src/library/statistics.rs | 105 - core/src/node/config.rs | 10 +- core/src/node/mod.rs | 8 +- core/src/node/state.rs | 1 - core/src/sys/locations.rs | 159 +- core/src/sys/mod.rs | 14 - core/src/sys/volumes.rs | 209 +- core/src/util/db.rs | 6 +- package.json | 6 +- packages/client/package.json | 13 +- packages/client/src/ClientProvider.tsx | 18 - packages/client/src/bridge.ts | 125 - .../client/src/context/AppPropsContext.tsx | 2 - packages/client/src/hooks/index.ts | 1 - packages/client/src/hooks/useCoreEvents.tsx | 59 - packages/client/src/index.ts | 5 +- packages/client/src/rspc.ts | 100 + packages/client/src/stores/index.ts | 2 - .../client/src/stores/useInspectorStore.ts | 43 - packages/client/src/stores/useLibraryStore.ts | 8 +- packages/client/src/window.d.ts | 7 - packages/config/interface.tsconfig.json | 2 +- packages/interface/package.json | 71 +- packages/interface/src/App.tsx | 40 +- packages/interface/src/AppLayout.tsx | 2 +- packages/interface/src/AppRouter.tsx | 9 +- .../src/components/device/Device.tsx | 3 - .../src/components/dialog/DemoDialog.tsx | 0 .../src/components/file/FileItem.tsx | 18 +- .../src/components/file/FileList.tsx | 73 +- .../src/components/file/FileThumb.tsx | 5 +- .../interface/src/components/file/Icon.tsx | 0 .../src/components/file/Inspector.tsx | 57 +- .../interface/src/components/file/Sidebar.tsx | 35 +- .../src/components/jobs/RunningJobsWidget.tsx | 4 +- .../src/components/layout/Dialog.tsx | 3 +- .../src/components/layout/TopBar.tsx | 28 +- .../components/location/LocationListItem.tsx | 27 +- .../src/components/primitive/Variants.tsx | 2 - .../interface/src/hooks/useFocusState.tsx | 2 +- packages/interface/src/screens/Content.tsx | 4 +- packages/interface/src/screens/Debug.tsx | 13 +- packages/interface/src/screens/Explorer.tsx | 17 +- packages/interface/src/screens/Overview.tsx | 9 +- packages/interface/src/screens/Photos.tsx | 1 - packages/interface/src/screens/Redirect.tsx | 2 +- packages/interface/src/screens/Tag.tsx | 7 +- .../src/screens/settings/Settings.tsx | 15 - .../settings/client/ExtensionsSettings.tsx | 1 - .../settings/client/GeneralSettings.tsx | 2 +- .../screens/settings/info/AboutSpacedrive.tsx | 2 - .../src/screens/settings/info/Support.tsx | 2 - .../library/LibraryGeneralSettings.tsx | 4 +- .../settings/library/LocationSettings.tsx | 15 +- .../settings/library/SecuritySettings.tsx | 2 - .../screens/settings/library/TagsSettings.tsx | 19 +- .../settings/node/ExperimentalSettings.tsx | 2 +- .../settings/node/LibrariesSettings.tsx | 42 +- .../src/screens/settings/node/P2PSettings.tsx | 4 +- packages/ui/package.json | 42 +- packages/ui/src/ContextMenu.tsx | 10 +- pnpm-lock.yaml | 6524 ++++++++--------- 158 files changed, 5762 insertions(+), 6810 deletions(-) delete mode 100644 apps/desktop/dist/.placeholder delete mode 100644 apps/landing/server/tsconfig.json create mode 100644 apps/server/src/utils.rs delete mode 100644 core/bindings/Client.ts delete mode 100644 core/bindings/ClientCommand.ts delete mode 100644 core/bindings/ClientQuery.ts delete mode 100644 core/bindings/ClientState.ts delete mode 100644 core/bindings/ConfigMetadata.ts delete mode 100644 core/bindings/CoreEvent.ts delete mode 100644 core/bindings/CoreResource.ts delete mode 100644 core/bindings/CoreResponse.ts delete mode 100644 core/bindings/DirectoryWithContents.ts delete mode 100644 core/bindings/EncryptionAlgorithm.ts delete mode 100644 core/bindings/File.ts delete mode 100644 core/bindings/FileKind.ts delete mode 100644 core/bindings/FilePath.ts delete mode 100644 core/bindings/JobReport.ts delete mode 100644 core/bindings/JobStatus.ts delete mode 100644 core/bindings/LibraryCommand.ts delete mode 100644 core/bindings/LibraryConfig.ts delete mode 100644 core/bindings/LibraryConfigWrapped.ts delete mode 100644 core/bindings/LibraryNode.ts delete mode 100644 core/bindings/LibraryQuery.ts delete mode 100644 core/bindings/LibraryState.ts delete mode 100644 core/bindings/LocationResource.ts delete mode 100644 core/bindings/NodeConfig.ts delete mode 100644 core/bindings/NodeState.ts delete mode 100644 core/bindings/Platform.ts delete mode 100644 core/bindings/Statistics.ts delete mode 100644 core/bindings/Tag.ts delete mode 100644 core/bindings/TagOnFile.ts delete mode 100644 core/bindings/TagWithFiles.ts delete mode 100644 core/bindings/Volume.ts create mode 100644 core/src/api/files.rs create mode 100644 core/src/api/jobs.rs create mode 100644 core/src/api/libraries.rs create mode 100644 core/src/api/locations.rs create mode 100644 core/src/api/mod.rs create mode 100644 core/src/api/tags.rs create mode 100644 core/src/api/utils/invalidate.rs create mode 100644 core/src/api/utils/mod.rs create mode 100644 core/src/api/volumes.rs delete mode 100644 core/src/file/explorer/mod.rs rename core/src/file/{indexer/mod.rs => indexer.rs} (97%) delete mode 100644 core/src/library/loader.rs delete mode 100644 core/src/library/statistics.rs delete mode 100644 core/src/node/state.rs delete mode 100644 packages/client/src/ClientProvider.tsx delete mode 100644 packages/client/src/bridge.ts delete mode 100644 packages/client/src/hooks/index.ts delete mode 100644 packages/client/src/hooks/useCoreEvents.tsx create mode 100644 packages/client/src/rspc.ts delete mode 100644 packages/client/src/stores/useInspectorStore.ts delete mode 100644 packages/client/src/window.d.ts delete mode 100644 packages/interface/src/components/dialog/DemoDialog.tsx delete mode 100644 packages/interface/src/components/file/Icon.tsx diff --git a/.cspell/project_words.txt b/.cspell/project_words.txt index 63d75c0f9..882cf4dda 100644 --- a/.cspell/project_words.txt +++ b/.cspell/project_words.txt @@ -35,4 +35,8 @@ marietti vijay sharma naveen -noco \ No newline at end of file +noco +rspc +rspcws +stringly +specta diff --git a/.eslintrc.js b/.eslintrc.js index f7d773462..0945e58c7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,21 +1,19 @@ module.exports = { - root: true, - parser: '@typescript-eslint/parser', - parserOptions: { - "project": [ - "apps/desktop/tsconfig.json", - "apps/web/tsconfig.json", - "apps/landing/tsconfig.json", - "packages/client/tsconfig.json", - "packages/interface/tsconfig.json", - "packages/ui/tsconfig.json", - ], - }, - plugins: [ - '@typescript-eslint', - ], - extends: [ - 'standard-with-typescript', - 'prettier', - ], -}; \ No newline at end of file + root: true, + parser: '@typescript-eslint/parser', + parserOptions: { + project: [ + 'apps/desktop/tsconfig.json', + 'apps/web/tsconfig.json', + 'apps/landing/tsconfig.json', + 'packages/client/tsconfig.json', + 'packages/interface/tsconfig.json', + 'packages/ui/tsconfig.json' + ] + }, + plugins: ['@typescript-eslint'], + extends: ['standard-with-typescript', 'prettier'], + rules: { + '@typescript-eslint/explicit-function-return-type': 'off' + } +}; diff --git a/.github/scripts/setup-system.ps1 b/.github/scripts/setup-system.ps1 index 29efe7a36..cb098db7c 100644 --- a/.github/scripts/setup-system.ps1 +++ b/.github/scripts/setup-system.ps1 @@ -139,10 +139,10 @@ Write-Host Write-Host "Downloading the latest ffmpeg build..." -ForegroundColor Yellow # Downloads the latest shared build of ffmpeg from GitHub -$filenamePattern = "*-full_build-shared.zip" -$releasesUri = "https://api.github.com/repos/GyanD/codexffmpeg/releases/latest" -$downloadUri = ((Invoke-RestMethod -Method GET -Uri $releasesUri).assets | Where-Object name -like $filenamePattern ).browser_download_url -$filename = ((Invoke-RestMethod -Method GET -Uri $releasesUri).assets | Where-Object name -like $filenamePattern ).name +# $filenamePattern = "*-full_build-shared.zip" +# $releasesUri = "https://api.github.com/repos/GyanD/codexffmpeg/releases/latest" +$downloadUri = "https://github.com/GyanD/codexffmpeg/releases/download/5.0.1/ffmpeg-5.0.1-full_build-shared.zip" # ((Invoke-RestMethod -Method GET -Uri $releasesUri).assets | Where-Object name -like $filenamePattern ).browser_download_url +$filename = "ffmpeg-5.0.1-full_build-shared.zip" # ((Invoke-RestMethod -Method GET -Uri $releasesUri).assets | Where-Object name -like $filenamePattern ).name $remove = ".zip" $foldername = $filename.Substring(0, ($filename.Length - $remove.Length)) diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 5c3cf0fb3..f2c039e33 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -49,6 +49,10 @@ jobs: if: steps.cache-prisma.outputs.cache-hit != 'true' run: cargo run -p prisma-cli --release -- generate + # This is do the proc-macro `tauri::generate_context!()` doesn't panic + - name: Create fake `dist` folder + run: mkdir ./apps/desktop/dist + - name: Run Clippy uses: actions-rs/clippy-check@v1 with: diff --git a/Cargo.lock b/Cargo.lock index e83b469b1..c143a58ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,237 +8,6 @@ version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -[[package]] -name = "actix" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f728064aca1c318585bf4bb04ffcfac9e75e508ab4e8b1bd9ba5dfe04e2cbed5" -dependencies = [ - "actix-rt", - "actix_derive", - "bitflags", - "bytes", - "crossbeam-channel", - "futures-core", - "futures-sink", - "futures-task", - "futures-util", - "log", - "once_cell", - "parking_lot 0.12.1", - "pin-project-lite", - "smallvec", - "tokio", - "tokio-util 0.7.3", -] - -[[package]] -name = "actix-codec" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a7559404a7f3573127aab53c08ce37a6c6a315c374a31070f3c91cd1b4a7fe" -dependencies = [ - "bitflags", - "bytes", - "futures-core", - "futures-sink", - "log", - "memchr", - "pin-project-lite", - "tokio", - "tokio-util 0.7.3", -] - -[[package]] -name = "actix-http" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f9ffb6db08c1c3a1f4aef540f1a63193adc73c4fbd40b75a95fc8c5258f6e51" -dependencies = [ - "actix-codec", - "actix-rt", - "actix-service", - "actix-utils", - "ahash", - "base64 0.13.0", - "bitflags", - "brotli", - "bytes", - "bytestring", - "derive_more", - "encoding_rs", - "flate2", - "futures-core", - "h2", - "http", - "httparse", - "httpdate", - "itoa 1.0.2", - "language-tags", - "local-channel", - "mime", - "percent-encoding", - "pin-project-lite", - "rand 0.8.5", - "sha1", - "smallvec", - "tracing", - "zstd", -] - -[[package]] -name = "actix-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "actix-router" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb60846b52c118f2f04a56cc90880a274271c489b2498623d58176f8ca21fa80" -dependencies = [ - "bytestring", - "firestorm", - "http", - "log", - "regex", - "serde", -] - -[[package]] -name = "actix-rt" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ea16c295198e958ef31930a6ef37d0fb64e9ca3b6116e6b93a8bdae96ee1000" -dependencies = [ - "futures-core", - "tokio", -] - -[[package]] -name = "actix-server" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da34f8e659ea1b077bb4637948b815cd3768ad5a188fdcd74ff4d84240cd824" -dependencies = [ - "actix-rt", - "actix-service", - "actix-utils", - "futures-core", - "futures-util", - "mio", - "num_cpus", - "socket2", - "tokio", - "tracing", -] - -[[package]] -name = "actix-service" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" -dependencies = [ - "futures-core", - "paste", - "pin-project-lite", -] - -[[package]] -name = "actix-utils" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e491cbaac2e7fc788dfff99ff48ef317e23b3cf63dbaf7aaab6418f40f92aa94" -dependencies = [ - "local-waker", - "pin-project-lite", -] - -[[package]] -name = "actix-web" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27e8fe9ba4ae613c21f677c2cfaf0696c3744030c6f485b34634e502d6bb379" -dependencies = [ - "actix-codec", - "actix-http", - "actix-macros", - "actix-router", - "actix-rt", - "actix-server", - "actix-service", - "actix-utils", - "actix-web-codegen", - "ahash", - "bytes", - "bytestring", - "cfg-if 1.0.0", - "cookie", - "derive_more", - "encoding_rs", - "futures-core", - "futures-util", - "itoa 1.0.2", - "language-tags", - "log", - "mime", - "once_cell", - "pin-project-lite", - "regex", - "serde", - "serde_json", - "serde_urlencoded", - "smallvec", - "socket2", - "time 0.3.11", - "url", -] - -[[package]] -name = "actix-web-actors" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31efe7896f3933ce03dd4710be560254272334bb321a18fd8ff62b1a557d9d19" -dependencies = [ - "actix", - "actix-codec", - "actix-http", - "actix-web", - "bytes", - "bytestring", - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "actix-web-codegen" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f270541caec49c15673b0af0e9a00143421ad4f118d2df7edcb68b627632f56" -dependencies = [ - "actix-router", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "actix_derive" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d44b8fee1ced9671ba043476deddef739dd0959bf77030b26b738cc591737a7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "addr2line" version = "0.17.0" @@ -334,6 +103,27 @@ dependencies = [ "url", ] +[[package]] +name = "async-stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" +dependencies = [ + "async-stream-impl", + "futures-core", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "async-trait" version = "0.1.56" @@ -417,6 +207,54 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "axum" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9496f0c1d1afb7a2af4338bbe1d969cddfead41d87a9fb3aaa6d0bbc7af648" +dependencies = [ + "async-trait", + "axum-core", + "base64 0.13.0", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa 1.0.2", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sha-1", + "sync_wrapper", + "tokio", + "tokio-tungstenite", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4f44a0e6200e9d11a1cdc989e4b358f6e3d354fbf48478f345a17f4e43f8635" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", +] + [[package]] name = "backtrace" version = "0.3.66" @@ -522,34 +360,13 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" -[[package]] -name = "block-buffer" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" -dependencies = [ - "block-padding", - "byte-tools", - "byteorder", - "generic-array 0.12.4", -] - [[package]] name = "block-buffer" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" dependencies = [ - "generic-array 0.14.5", -] - -[[package]] -name = "block-padding" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -dependencies = [ - "byte-tools", + "generic-array", ] [[package]] @@ -608,17 +425,11 @@ version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" - [[package]] name = "bytemuck" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c53dfa917ec274df8ed3c572698f381a24eef2efba9492d797301b72b6db408a" +checksum = "a5377c8865e74a160d21f29c2d40669f53286db6eab59b88540cbb12ffc8b835" [[package]] name = "byteorder" @@ -628,18 +439,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" - -[[package]] -name = "bytestring" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b6a75fd3048808ef06af5cd79712be8111960adaf89d90250974b38fc3928a" -dependencies = [ - "bytes", -] +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "cairo-rs" @@ -876,17 +678,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8" -[[package]] -name = "cookie" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05" -dependencies = [ - "percent-encoding", - "time 0.3.11", - "version_check", -] - [[package]] name = "core-derive" version = "0.1.0" @@ -956,48 +747,48 @@ dependencies = [ [[package]] name = "crossbeam" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae5588f6b3c3cb05239e90bd110f257254aecd01e4635400391aeae07497845" +checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c" dependencies = [ "cfg-if 1.0.0", "crossbeam-channel", "crossbeam-deque", "crossbeam-epoch", - "crossbeam-queue 0.3.5", - "crossbeam-utils 0.8.10", + "crossbeam-queue 0.3.6", + "crossbeam-utils 0.8.11", ] [[package]] name = "crossbeam-channel" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.10", + "crossbeam-utils 0.8.11", ] [[package]] name = "crossbeam-deque" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils 0.8.10", + "crossbeam-utils 0.8.11", ] [[package]] name = "crossbeam-epoch" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d" +checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" dependencies = [ "autocfg", "cfg-if 1.0.0", - "crossbeam-utils 0.8.10", + "crossbeam-utils 0.8.11", "memoffset", "once_cell", "scopeguard", @@ -1016,12 +807,12 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2" +checksum = "1cd42583b04998a5363558e5f9291ee5a5ff6b49944332103f251e7479a82aa7" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.10", + "crossbeam-utils 0.8.11", ] [[package]] @@ -1037,9 +828,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" dependencies = [ "cfg-if 1.0.0", "once_cell", @@ -1047,11 +838,11 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ccfd8c0ee4cce11e45b3fd6f9d5e69e0cc62912aa6a0cb1bf4617b0eba5a12f" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.5", + "generic-array", "typenum", ] @@ -1084,14 +875,24 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" +checksum = "cdffe87e1d521a10f9696f833fe502293ea446d7f256c06128293a4119bdf4cb" dependencies = [ "quote", "syn", ] +[[package]] +name = "ctrlc" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b37feaa84e6861e00a1f5e5aa8da3ee56d605c9992d33e082786754828e20865" +dependencies = [ + "nix", + "winapi", +] + [[package]] name = "cty" version = "0.2.2" @@ -1249,22 +1050,13 @@ dependencies = [ "thiserror", ] -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -dependencies = [ - "generic-array 0.12.4", -] - [[package]] name = "digest" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ - "block-buffer 0.10.2", + "block-buffer", "crypto-common", "subtle", ] @@ -1278,15 +1070,6 @@ dependencies = [ "dirs-sys", ] -[[package]] -name = "dirs" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" -dependencies = [ - "dirs-sys", -] - [[package]] name = "dirs-next" version = "2.0.0" @@ -1352,15 +1135,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "dotenvy" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e851a83c30366fd01d75b913588e95e74a1705c1ecc5d58b1f8e1a6d556525f" -dependencies = [ - "dirs", -] - [[package]] name = "dtoa" version = "0.4.8" @@ -1506,19 +1280,6 @@ dependencies = [ "syn", ] -[[package]] -name = "env_logger" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" -dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", -] - [[package]] name = "exr" version = "1.4.2" @@ -1535,12 +1296,6 @@ dependencies = [ "threadpool", ] -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" - [[package]] name = "fallible-iterator" version = "0.2.0" @@ -1555,9 +1310,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" dependencies = [ "instant", ] @@ -1605,16 +1360,10 @@ checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.13", + "redox_syscall 0.2.16", "windows-sys", ] -[[package]] -name = "firestorm" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c5f6c2c942da57e2aaaa84b8a521489486f14e75e7fa91dab70aba913975f98" - [[package]] name = "fixedbitset" version = "0.1.9" @@ -1634,15 +1383,15 @@ dependencies = [ [[package]] name = "flume" -version = "0.10.13" +version = "0.10.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ceeb589a3157cac0ab8cc585feb749bd2cea5cb55a6ee802ad72d9fd38303da" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" dependencies = [ "futures-core", "futures-sink", "nanorand", "pin-project", - "spin 0.9.3", + "spin 0.9.4", ] [[package]] @@ -1955,24 +1704,15 @@ dependencies = [ [[package]] name = "generator" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1d9279ca822891c1a4dae06d185612cf8fc6acfe5dff37781b41297811b12ee" +checksum = "cc184cace1cea8335047a471cc1da80f18acf8a76f3bab2028d499e328948ec7" dependencies = [ "cc", "libc", "log", "rustversion", - "winapi", -] - -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", + "windows 0.32.0", ] [[package]] @@ -2021,9 +1761,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" [[package]] name = "gio" @@ -2231,9 +1971,12 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "607c8a29735385251a339424dd462993c0fed8fa09d378f259377df08c126022" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] [[package]] name = "hashlink" @@ -2280,7 +2023,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.3", + "digest", ] [[package]] @@ -2346,6 +2089,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + [[package]] name = "httparse" version = "1.7.1" @@ -2358,12 +2107,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "hyper" version = "0.14.20" @@ -2434,7 +2177,7 @@ version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" dependencies = [ - "crossbeam-utils 0.8.10", + "crossbeam-utils 0.8.11", "globset", "lazy_static", "log", @@ -2459,9 +2202,9 @@ dependencies = [ [[package]] name = "image" -version = "0.24.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28edd9d7bc256be2502e325ac0628bde30b7001b9b52e0abe31a1a9dc2701212" +checksum = "7e30ca2ecf7666107ff827a8e481de6a132a9b687ed3bb20bb1c144a36c00964" dependencies = [ "bytemuck", "byteorder", @@ -2469,7 +2212,6 @@ dependencies = [ "exr", "gif", "jpeg-decoder", - "num-iter", "num-rational", "num-traits", "png 0.17.5", @@ -2504,7 +2246,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", - "hashbrown 0.12.2", + "hashbrown 0.12.3", "serde", ] @@ -2719,12 +2461,6 @@ dependencies = [ "selectors", ] -[[package]] -name = "language-tags" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" - [[package]] name = "lazy_static" version = "1.4.0" @@ -2887,24 +2623,6 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" -[[package]] -name = "local-channel" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" -dependencies = [ - "futures-core", - "futures-sink", - "futures-util", - "local-waker", -] - -[[package]] -name = "local-waker" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" - [[package]] name = "lock_api" version = "0.3.4" @@ -2950,11 +2668,11 @@ dependencies = [ [[package]] name = "lru" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c84e6fe5655adc6ce00787cf7dcaf8dc4f998a0565d23eafc207a8b08ca3349a" +checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a" dependencies = [ - "hashbrown 0.11.2", + "hashbrown 0.12.3", ] [[package]] @@ -2974,9 +2692,9 @@ checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" [[package]] name = "mac-notification-sys" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47a4acb83c904844ca12aafeac6fff6f781cf9e220a985c1db94fd94123993aa" +checksum = "fff231a88fe2e9985f9d159a2f02986fe46daa0f6af976a0d934be4870cc9d02" dependencies = [ "cc", "dirs-next", @@ -2994,12 +2712,6 @@ dependencies = [ "libc", ] -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - [[package]] name = "markup5ever" version = "0.10.1" @@ -3035,6 +2747,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +[[package]] +name = "matchit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" + [[package]] name = "maybe-uninit" version = "2.0.0" @@ -3047,7 +2765,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "658646b21e0b72f7866c7038ab086d3d5e1cd6271f060fd37defb241949d0582" dependencies = [ - "digest 0.10.3", + "digest", ] [[package]] @@ -3136,9 +2854,9 @@ dependencies = [ [[package]] name = "mongodb" -version = "2.2.2" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28f3943e379e9dcaaab9dc319c308a8caaf9e7ff083c6838dff740afbba59df7" +checksum = "b95afe97b0c799fdf69cd960272a2cb9662d077bd6efd84eb722bb9805d47554" dependencies = [ "async-trait", "base64 0.13.0", @@ -3163,7 +2881,7 @@ dependencies = [ "serde", "serde_bytes", "serde_with", - "sha-1 0.10.0", + "sha-1", "sha2", "socket2", "stringprep", @@ -3295,7 +3013,7 @@ dependencies = [ "saturating", "serde", "serde_json", - "sha-1 0.10.0", + "sha-1", "sha2", "smallvec", "subprocess", @@ -3304,6 +3022,15 @@ dependencies = [ "uuid 0.8.2", ] +[[package]] +name = "nanoid" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" +dependencies = [ + "rand 0.8.5", +] + [[package]] name = "nanorand" version = "0.7.0" @@ -3374,6 +3101,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +[[package]] +name = "nix" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "libc", +] + [[package]] name = "nodrop" version = "0.1.14" @@ -3567,17 +3305,11 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" - [[package]] name = "open" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360bcc8316bf6363aa3954c3ccc4de8add167b087e0259190a043c9514f910fe" +checksum = "f23a407004a1033f53e93f9b45580d14de23928faad187384f891507c9b0c045" dependencies = [ "pathdiff", "windows-sys", @@ -3737,17 +3469,6 @@ dependencies = [ "parking_lot_core 0.7.2", ] -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api 0.4.7", - "parking_lot_core 0.8.5", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -3772,20 +3493,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "parking_lot_core" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" -dependencies = [ - "cfg-if 1.0.0", - "instant", - "libc", - "redox_syscall 0.2.13", - "smallvec", - "winapi", -] - [[package]] name = "parking_lot_core" version = "0.9.3" @@ -3794,7 +3501,7 @@ checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.13", + "redox_syscall 0.2.16", "smallvec", "windows-sys", ] @@ -3828,7 +3535,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271779f35b581956db91a3e55737327a03aa051e90b1c47aeb189508533adfd7" dependencies = [ - "digest 0.10.3", + "digest", ] [[package]] @@ -3839,9 +3546,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "pem" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9a3b09a20e374558580a4914d3b7d89bd61b954a5a5e1dcbea98753addb1947" +checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4" dependencies = [ "base64 0.13.0", ] @@ -3854,18 +3561,19 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pest" -version = "2.1.3" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +checksum = "69486e2b8c2d2aeb9762db7b4e00b0331156393555cff467f4163ff06821eef8" dependencies = [ + "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.1.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +checksum = "b13570633aff33c6d22ce47dd566b10a3b9122c2fe9d8e7501895905be532b91" dependencies = [ "pest", "pest_generator", @@ -3873,9 +3581,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.1.3" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +checksum = "b3c567e5702efdc79fb18859ea74c3eb36e14c43da7b8c1f098a4ed6514ec7a0" dependencies = [ "pest", "pest_meta", @@ -3886,13 +3594,13 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.1.3" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +checksum = "5eb32be5ee3bbdafa8c7a18b0a8a8d962b66cfa2ceee4037f49267a50ee821fe" dependencies = [ - "maplit", + "once_cell", "pest", - "sha-1 0.8.2", + "sha-1", ] [[package]] @@ -4150,8 +3858,8 @@ dependencies = [ [[package]] name = "prisma-client-rust" -version = "0.5.2" -source = "git+https://github.com/Brendonovich/prisma-client-rust.git?tag=0.5.2#88ba860aedace6ebf0707fa989138fea87cd0d5e" +version = "0.6.0" +source = "git+https://github.com/Brendonovich/prisma-client-rust.git?branch=0.6.0#f6799c685662135a6fb6728e1fe188168cdbd219" dependencies = [ "bigdecimal 0.2.2", "chrono", @@ -4160,6 +3868,7 @@ dependencies = [ "prisma-models", "query-connector", "query-core", + "rspc", "serde", "serde-value", "serde_json", @@ -4170,8 +3879,26 @@ dependencies = [ [[package]] name = "prisma-client-rust-cli" -version = "0.4.1" -source = "git+https://github.com/Brendonovich/prisma-client-rust?tag=0.5.0#31a864ae09e835ff046469345bf6e413ae54a30f" +version = "0.6.0" +source = "git+https://github.com/Brendonovich/prisma-client-rust.git?branch=0.6.0#f6799c685662135a6fb6728e1fe188168cdbd219" +dependencies = [ + "datamodel", + "prisma-client-rust-sdk", + "prisma-models", + "proc-macro2", + "query-core", + "quote", + "request-handlers", + "serde", + "serde_json", + "serde_path_to_error", + "syn", +] + +[[package]] +name = "prisma-client-rust-sdk" +version = "0.6.0" +source = "git+https://github.com/Brendonovich/prisma-client-rust.git?branch=0.6.0#f6799c685662135a6fb6728e1fe188168cdbd219" dependencies = [ "convert_case 0.5.0", "datamodel", @@ -4179,6 +3906,7 @@ dependencies = [ "flate2", "http", "prisma-models", + "proc-macro2", "query-core", "quote", "regex", @@ -4289,9 +4017,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.40" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +checksum = "c278e965f1d8cf32d6e0e96de3d3e79712178ae67986d9cf9151f51e95aac89b" dependencies = [ "unicode-ident", ] @@ -4526,7 +4254,7 @@ checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ "crossbeam-channel", "crossbeam-deque", - "crossbeam-utils 0.8.10", + "crossbeam-utils 0.8.11", "num_cpus", ] @@ -4538,9 +4266,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] @@ -4552,7 +4280,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom 0.2.7", - "redox_syscall 0.2.13", + "redox_syscall 0.2.16", "thiserror", ] @@ -4737,6 +4465,24 @@ dependencies = [ "serde", ] +[[package]] +name = "rspc" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175b44c229ea6314902120dd1fa43181b3207a5eec18a7a3c9cbcc6ec6a780db" +dependencies = [ + "axum", + "futures", + "nanoid", + "serde", + "serde_json", + "specta", + "tauri", + "thiserror", + "tokio", + "tracing", +] + [[package]] name = "rusqlite" version = "0.25.4" @@ -4836,9 +4582,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf" +checksum = "24c8ad4f0c00e1eb5bc7614d236a7f1300e3dbd76b68cac8e06fb00b015ad8d8" [[package]] name = "ryu" @@ -4919,12 +4665,13 @@ dependencies = [ name = "sdcore" version = "0.1.0" dependencies = [ + "async-stream", "async-trait", "base64 0.13.0", "chrono", "core-derive", + "ctor", "data-encoding", - "env_logger", "ffmpeg-next", "fs_extra", "futures", @@ -4932,18 +4679,20 @@ dependencies = [ "image", "include_dir", "int-enum", - "log", + "once_cell", "prisma-client-rust", "ring 0.17.0-alpha.11", "rmp", "rmp-serde", + "rspc", "serde", "serde_json", "sysinfo", "thiserror", "tokio", - "ts-rs", - "uuid 0.8.2", + "tracing", + "tracing-subscriber", + "uuid 1.1.2", "walkdir", "webp", ] @@ -5035,9 +4784,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.139" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0171ebb889e45aa68b44aee0859b3eede84c6f5f5c228e6f140c0b2a0a46cad6" +checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03" dependencies = [ "serde_derive", ] @@ -5063,9 +4812,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.139" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1d3230c1de7932af58ad8ffbe1d784bd55efd5a9d84ac24f69c72d83543dfb" +checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da" dependencies = [ "proc-macro2", "quote", @@ -5164,13 +4913,11 @@ dependencies = [ name = "server" version = "0.1.0" dependencies = [ - "actix", - "actix-web", - "actix-web-actors", + "axum", + "ctrlc", "sdcore", - "serde", - "serde_json", "tokio", + "tracing", ] [[package]] @@ -5183,18 +4930,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "sha-1" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" -dependencies = [ - "block-buffer 0.7.3", - "digest 0.8.1", - "fake-simd", - "opaque-debug", -] - [[package]] name = "sha-1" version = "0.10.0" @@ -5203,18 +4938,7 @@ checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.3", -] - -[[package]] -name = "sha1" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77f4e7f65455545c2153c1253d25056825e77ee2533f0e41deb65a93a34852f" -dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", - "digest 0.10.3", + "digest", ] [[package]] @@ -5225,7 +4949,7 @@ checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.3", + "digest", ] [[package]] @@ -5279,9 +5003,12 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] [[package]] name = "smallvec" @@ -5331,18 +5058,43 @@ dependencies = [ name = "spacedrive" version = "0.1.0" dependencies = [ - "dotenvy", - "env_logger", - "futures", - "log", "sdcore", "swift-rs", "tauri", "tauri-build", "tokio", + "tracing", "window-shadows", ] +[[package]] +name = "specta" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dfbc46bfad9289e01928a228b82fdf615131212aded5497cce2d460b452e94" +dependencies = [ + "chrono", + "convert_case 0.5.0", + "indexmap", + "serde_json", + "specta-macros", + "termcolor", + "uuid 1.1.2", +] + +[[package]] +name = "specta-macros" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ad5db5dea9fa5816615d722b682781214fed875fe56c1bcd0a67ee9c477d84" +dependencies = [ + "Inflector", + "proc-macro2", + "quote", + "syn", + "termcolor", +] + [[package]] name = "spin" version = "0.5.2" @@ -5351,9 +5103,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spin" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c530c2b0d0bf8b69304b39fe2001993e267461948b890cd037d8ad4293fa1a0d" +checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" dependencies = [ "lock_api 0.4.7", ] @@ -5534,6 +5286,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" + [[package]] name = "sysinfo" version = "0.23.13" @@ -5583,9 +5341,9 @@ checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" [[package]] name = "tao" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a71c32c2fa7bba46b01becf9cf470f6a781573af7e376c5e317a313ecce27545" +checksum = "f6fd7725dc1e593e9ecabd9fe49c112a204c8c8694db4182e78b2a5af490b1ae" dependencies = [ "bitflags", "cairo-rs", @@ -5614,14 +5372,14 @@ dependencies = [ "ndk-sys", "objc", "once_cell", - "parking_lot 0.11.2", + "parking_lot 0.12.1", "paste", "png 0.17.5", "raw-window-handle", "scopeguard", "serde", "unicode-segmentation", - "uuid 0.8.2", + "uuid 1.1.2", "windows 0.37.0", "windows-implement", "x11-dl", @@ -5646,9 +5404,9 @@ dependencies = [ [[package]] name = "tauri" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61fc211e0bd2c04c0aecd202d2cd72dd797a89da02989a39e1b9691462386d6" +checksum = "e1a56a8b125069c2682bd31610109b4436c050c74447bee1078217a0325c1add" dependencies = [ "anyhow", "attohttpc", @@ -5699,9 +5457,9 @@ dependencies = [ [[package]] name = "tauri-build" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f2b32e551ec810ba4ab2ad735de5e3576e54bf0322ab0f4b7ce41244bc65ecf" +checksum = "acafb1c515c5d14234a294461bd43c723639a84891a45f6a250fd3441ad2e8ed" dependencies = [ "anyhow", "cargo_toml", @@ -5715,9 +5473,9 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f1f7928dd040fc03c94207adfad506c0cf5b152982fd1dc0a621f7fd777e22" +checksum = "16d62a3c8790d6cba686cea6e3f7f569d12c662c3274c2d165a4fd33e3871b72" dependencies = [ "base64 0.13.0", "brotli", @@ -5741,9 +5499,9 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e50b9f52871c088857360319a37472d59f4644f1ed004489599d62831a1b6996" +checksum = "7296fa17996629f43081e1c66d554703900187ed900c5bf46f97f0bcfb069278" dependencies = [ "heck 0.4.0", "proc-macro2", @@ -5829,7 +5587,7 @@ dependencies = [ "cfg-if 1.0.0", "fastrand", "libc", - "redox_syscall 0.2.13", + "redox_syscall 0.2.16", "remove_dir_all", "winapi", ] @@ -5932,9 +5690,9 @@ dependencies = [ [[package]] name = "tiff" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cfada0986f446a770eca461e8c6566cb879682f7d687c8348aa0c857bd52286" +checksum = "7259662e32d1e219321eb309d5f9d898b779769d81b76e762c07c8e5d38fcb65" dependencies = [ "flate2", "jpeg-decoder", @@ -5987,17 +5745,17 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.19.2" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" +checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" dependencies = [ + "autocfg", "bytes", "libc", "memchr", "mio", "num_cpus", "once_cell", - "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", "socket2", @@ -6068,6 +5826,18 @@ dependencies = [ "webpki", ] +[[package]] +name = "tokio-tungstenite" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.6.10" @@ -6106,6 +5876,47 @@ dependencies = [ "serde", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" + [[package]] name = "tower-service" version = "0.3.2" @@ -6114,9 +5925,9 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" dependencies = [ "cfg-if 1.0.0", "log", @@ -6138,9 +5949,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" dependencies = [ "once_cell", "valuable", @@ -6182,9 +5993,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a713421342a5a666b7577783721d3117f1b69a393df803ee17bb73b1e122a59" +checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" dependencies = [ "ansi_term", "matchers", @@ -6259,28 +6070,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] -name = "ts-rs" -version = "6.2.0" +name = "tungstenite" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc59f479df54269b400dd95bc3b7e81623b3e4b9c70c8ca7125ab8341eafa64e" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" dependencies = [ - "chrono", + "base64 0.13.0", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand 0.8.5", + "sha-1", "thiserror", - "ts-rs-macros", - "uuid 0.8.2", -] - -[[package]] -name = "ts-rs-macros" -version = "6.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f807fdb3151fee75df7485b901a89624358cd07a67a8fb1a5831bf5a07681ff" -dependencies = [ - "Inflector", - "proc-macro2", - "quote", - "syn", - "termcolor", + "url", + "utf-8", ] [[package]] @@ -6325,9 +6130,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" [[package]] name = "unicode-normalization" @@ -6419,6 +6224,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" dependencies = [ "getrandom 0.2.7", + "serde", ] [[package]] @@ -6797,6 +6603,19 @@ dependencies = [ "windows_x86_64_msvc 0.24.0", ] +[[package]] +name = "windows" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbedf6db9096bc2364adce0ae0aa636dcd89f3c3f2cd67947062aaf0ca2a10ec" +dependencies = [ + "windows_aarch64_msvc 0.32.0", + "windows_i686_gnu 0.32.0", + "windows_i686_msvc 0.32.0", + "windows_x86_64_gnu 0.32.0", + "windows_x86_64_msvc 0.32.0", +] + [[package]] name = "windows" version = "0.37.0" @@ -6856,6 +6675,12 @@ version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3263d25f1170419995b78ff10c06b949e8a986c35c208dc24333c64753a87169" +[[package]] +name = "windows_aarch64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" + [[package]] name = "windows_aarch64_msvc" version = "0.36.1" @@ -6874,6 +6699,12 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0866510a3eca9aed73a077490bbbf03e5eaac4e1fd70849d89539e5830501fd" +[[package]] +name = "windows_i686_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" + [[package]] name = "windows_i686_gnu" version = "0.36.1" @@ -6892,6 +6723,12 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf0ffed56b7e9369a29078d2ab3aaeceea48eb58999d2cff3aa2494a275b95c6" +[[package]] +name = "windows_i686_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" + [[package]] name = "windows_i686_msvc" version = "0.36.1" @@ -6910,6 +6747,12 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384a173630588044205a2993b6864a2f56e5a8c1e7668c07b93ec18cf4888dc4" +[[package]] +name = "windows_x86_64_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" + [[package]] name = "windows_x86_64_gnu" version = "0.36.1" @@ -6928,6 +6771,12 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bd8f062d8ca5446358159d79a90be12c543b3a965c847c8f3eedf14b321d399" +[[package]] +name = "windows_x86_64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" + [[package]] name = "windows_x86_64_msvc" version = "0.36.1" @@ -7063,32 +6912,3 @@ name = "xml-rs" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" - -[[package]] -name = "zstd" -version = "0.11.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "5.0.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" -dependencies = [ - "libc", - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.1+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" -dependencies = [ - "cc", - "libc", -] diff --git a/README.md b/README.md index 39333c6e5..36891b3e6 100644 --- a/README.md +++ b/README.md @@ -124,16 +124,16 @@ This project is using what I'm calling the **"PRRTT"** stack (Prisma, Rust, Reac ### Core: -- `core`: The [Rust](#) core, referred to internally as `sdcore`. Contains filesystem, database and networking logic. Can be deployed in a variety of host applications. +- `core`: The [Rust](https://www.rust-lang.org) core, referred to internally as `sdcore`. Contains filesystem, database and networking logic. Can be deployed in a variety of host applications. ### Packages: -- `client`: A [TypeScript](#) client library to handle dataflow via RPC between UI and the Rust core. -- `ui`: A [React](<[#](https://reactjs.org)>) Shared component library. +- `client`: A [TypeScript](https://www.typescriptlang.org/) client library to handle dataflow via RPC between UI and the Rust core. +- `ui`: A [React](https://reactjs.org) Shared component library. - `interface`: The complete user interface in React (used by apps `desktop`, `web` and `landing`) - `config`: `eslint` configurations (includes `eslint-config-next`, `eslint-config-prettier` and all `tsconfig.json` configs used throughout the monorepo. -- `macos`: A [Swift](#) Native binary for MacOS system extensions. -- `ios`: A [Swift](#) Native binary (planned). -- `windows`: A [C#](#) Native binary (planned). -- `android`: A [Kotlin](#) Native binary (planned). +- `macos`: A [Swift](https://developer.apple.com/swift/) Native binary for MacOS system extensions. +- `ios`: A [Swift](https://developer.apple.com/swift/) Native binary (planned). +- `windows`: A [C#](https://docs.microsoft.com/en-us/dotnet/csharp/) Native binary (planned). +- `android`: A [Kotlin](https://kotlinlang.org/) Native binary (planned). diff --git a/apps/desktop/dist/.placeholder b/apps/desktop/dist/.placeholder deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 7218ea08f..7b6dfae6f 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -11,31 +11,33 @@ "build": "tauri build" }, "dependencies": { + "@rspc/client": "^0.0.5", "@sd/client": "workspace:*", "@sd/core": "workspace:*", "@sd/interface": "workspace:*", "@sd/ui": "workspace:*", - "@tauri-apps/api": "1.0.0", - "react": "^18.1.0", - "react-dom": "^18.1.0" + "@tanstack/react-query": "^4.0.10", + "@tauri-apps/api": "1.0.2", + "react": "^18.2.0", + "react-dom": "^18.2.0" }, "devDependencies": { - "@tauri-apps/cli": "1.0.0", + "@tauri-apps/cli": "1.0.5", "@tauri-apps/tauricon": "github:tauri-apps/tauricon", "@types/babel-core": "^6.25.7", "@types/byte-size": "^8.1.0", - "@types/react": "^18.0.9", - "@types/react-dom": "^18.0.5", + "@types/react": "^18.0.15", + "@types/react-dom": "^18.0.6", "@types/react-router-dom": "^5.3.3", "@types/react-window": "^1.8.5", - "@types/tailwindcss": "^3.0.10", - "@vitejs/plugin-react": "^1.3.2", - "concurrently": "^7.2.1", - "prettier": "^2.6.2", - "sass": "^1.52.1", - "typescript": "^4.7.2", - "vite": "^2.9.9", + "@types/tailwindcss": "^3.1.0", + "@vitejs/plugin-react": "^2.0.0", + "concurrently": "^7.3.0", + "prettier": "^2.7.1", + "sass": "^1.54.0", + "typescript": "^4.7.4", + "vite": "^3.0.3", "vite-plugin-filter-replace": "^0.1.9", - "vite-plugin-svgr": "^2.1.0" + "vite-plugin-svgr": "^2.2.1" } } diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index ff2a9b3c2..edf6c13bb 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -9,27 +9,19 @@ default-run = "spacedrive" edition = "2021" build = "build.rs" -[build-dependencies] -tauri-build = { version = "1.0.0", features = [] } - [dependencies] -# Project dependencies -tauri = { version = "1.0.0", features = ["api-all", "macos-private-api"] } +tauri = { version = "1.0.4", features = ["api-all", "macos-private-api"] } sdcore = { path = "../../../core" } -# tauri-plugin-shadows = { git = "https://github.com/tauri-apps/tauri-plugin-shadows", features = ["tauri-impl"] } - -# Universal Dependencies tokio = { version = "1.17.0", features = ["sync"] } -futures = "^0.3" window-shadows = "0.1.2" -env_logger = "0.9.0" -dotenvy = "0.15.1" -log = { version = "0.4.17", features = ["max_level_trace"] } +tracing = "0.1.35" -# macOS system libs [target.'cfg(target_os = "macos")'.dependencies] swift-rs = { git = "https://github.com/Brendonovich/swift-rs.git", branch = "autorelease" } +[build-dependencies] +tauri-build = { version = "1.0.0", features = [] } + [target.'cfg(target_os = "macos")'.build-dependencies] swift-rs = { git = "https://github.com/Brendonovich/swift-rs.git", branch = "autorelease", features = ["build"] } diff --git a/apps/desktop/src-tauri/rustfmt.toml b/apps/desktop/src-tauri/rustfmt.toml index a231bfab7..054510e14 100644 --- a/apps/desktop/src-tauri/rustfmt.toml +++ b/apps/desktop/src-tauri/rustfmt.toml @@ -10,4 +10,3 @@ merge_derives = true use_try_shorthand = false use_field_init_shorthand = false force_explicit_abi = true -imports_granularity = "Crate" diff --git a/apps/desktop/src-tauri/src/main.rs b/apps/desktop/src-tauri/src/main.rs index 67cce98ad..5916fddf5 100644 --- a/apps/desktop/src-tauri/src/main.rs +++ b/apps/desktop/src-tauri/src/main.rs @@ -1,45 +1,12 @@ use std::path::PathBuf; -use std::time::{Duration, Instant}; -use dotenvy::dotenv; -use futures::executor::block_on; -use log::{debug, error, info}; -use sdcore::{ClientCommand, ClientQuery, CoreEvent, CoreResponse, Node, NodeController}; +use sdcore::Node; use tauri::{api::path, Manager, RunEvent}; -use tokio::sync::oneshot; - +use tracing::{debug, error}; #[cfg(target_os = "macos")] mod macos; mod menu; -#[tauri::command(async)] -async fn client_query_transport( - core: tauri::State<'_, NodeController>, - data: ClientQuery, -) -> Result { - match core.query(data).await { - Ok(response) => Ok(response), - Err(err) => { - error!("query error: {:?}", err); - Err(err.to_string()) - } - } -} - -#[tauri::command(async)] -async fn client_command_transport( - core: tauri::State<'_, NodeController>, - data: ClientCommand, -) -> Result { - match core.command(data).await { - Ok(response) => Ok(response), - Err(err) => { - println!("command error: {:?}", err); - Err(err.to_string()) - } - } -} - #[tauri::command(async)] async fn app_ready(app_handle: tauri::AppHandle) { let window = app_handle.get_window("main").unwrap(); @@ -47,44 +14,19 @@ async fn app_ready(app_handle: tauri::AppHandle) { window.show().unwrap(); } -struct ShutdownManager { - shutdown_tx: Option>, - shutdown_completion_rx: Option>, -} - -impl ShutdownManager { - fn shutdown(&mut self) { - if let Some(sender) = self.shutdown_tx.take() { - sender.send(()).unwrap(); - if let Some(receiver) = self.shutdown_completion_rx.take() { - block_on(receiver).expect("failed to receive shutdown completion signal"); - } - } - } -} - #[tokio::main] async fn main() { - dotenv().ok(); - env_logger::init(); - let data_dir = path::data_dir() .unwrap_or_else(|| PathBuf::from("./")) .join("spacedrive"); - // create an instance of the core - let (controller, mut event_receiver, node, shutdown_completion_rx) = Node::new(data_dir).await; - let (shutdown_tx, shutdown_rx) = oneshot::channel(); - let mut shutdown_manager = ShutdownManager { - shutdown_tx: Some(shutdown_tx), - shutdown_completion_rx: Some(shutdown_completion_rx), - }; + let (node, router) = Node::new(data_dir).await; - tokio::spawn(node.start(shutdown_rx)); - // create tauri app let app = tauri::Builder::default() - // pass controller to the tauri state manager - .manage(controller) + .plugin(sdcore::rspc::integrations::tauri::plugin(router, { + let node = node.clone(); + move || node.get_request_context() + })) .setup(|app| { let app = app.handle(); @@ -111,34 +53,10 @@ async fn main() { } }); - // core event transport - tokio::spawn(async move { - let mut last = Instant::now(); - // handle stream output - while let Some(event) = event_receiver.recv().await { - match event { - CoreEvent::InvalidateQueryDebounced(_) => { - let current = Instant::now(); - if current.duration_since(last) > Duration::from_millis(1000 / 60) { - last = current; - app.emit_all("core_event", &event).unwrap(); - } - } - event => { - app.emit_all("core_event", &event).unwrap(); - } - } - } - }); - Ok(()) }) .on_menu_event(menu::handle_menu_event) - .invoke_handler(tauri::generate_handler![ - client_query_transport, - client_command_transport, - app_ready, - ]) + .invoke_handler(tauri::generate_handler![app_ready,]) .menu(menu::get_menu()) .build(tauri::generate_context!()) .expect("error while building tauri application"); @@ -155,8 +73,8 @@ async fn main() { error!("failed to close window '{}': {:#?}", window_name, e); } }); - info!("Spacedrive shutting down..."); - shutdown_manager.shutdown(); + + node.shutdown(); app_handler.exit(0); } }) diff --git a/apps/desktop/src/index.tsx b/apps/desktop/src/index.tsx index 692f7149e..002eb506e 100644 --- a/apps/desktop/src/index.tsx +++ b/apps/desktop/src/index.tsx @@ -1,10 +1,7 @@ // import Spacedrive JS client -import { BaseTransport } from '@sd/client'; -// import types from Spacedrive core (TODO: re-export from client would be cleaner) -import { ClientCommand, ClientQuery, CoreEvent } from '@sd/core'; -// import Spacedrive interface +import { TauriTransport, createClient } from '@rspc/client'; +import { Operations, queryClient, rspc } from '@sd/client'; import SpacedriveInterface, { Platform } from '@sd/interface'; -// import tauri apis import { dialog, invoke, os, shell } from '@tauri-apps/api'; import { Event, listen } from '@tauri-apps/api/event'; import { convertFileSrc } from '@tauri-apps/api/tauri'; @@ -14,22 +11,9 @@ import { createRoot } from 'react-dom/client'; import '@sd/ui/style'; -// bind state to core via Tauri -class Transport extends BaseTransport { - constructor() { - super(); - - listen('core_event', (e: Event) => { - this.emit('core_event', e.payload); - }); - } - async query(query: ClientQuery) { - return await invoke('client_query_transport', { data: query }); - } - async command(query: ClientCommand) { - return await invoke('client_command_transport', { data: query }); - } -} +const client = createClient({ + transport: new TauriTransport() +}); function App() { function getPlatform(platform: string): Platform { @@ -64,23 +48,24 @@ function App() { }, []); return ( - { - return dialog.open(options); - }} - isFocused={focused} - onClose={() => appWindow.close()} - onFullscreen={() => appWindow.setFullscreen(true)} - onMinimize={() => appWindow.minimize()} - onOpen={(path: string) => shell.open(path)} - /> + + { + return dialog.open(options); + }} + isFocused={focused} + onClose={() => appWindow.close()} + onFullscreen={() => appWindow.setFullscreen(true)} + onMinimize={() => appWindow.minimize()} + onOpen={(path: string) => shell.open(path)} + /> + ); } diff --git a/apps/landing/package.json b/apps/landing/package.json index f1d1e2199..41103356e 100644 --- a/apps/landing/package.json +++ b/apps/landing/package.json @@ -1,69 +1,52 @@ { - "name": "@sd/landing", - "private": true, - "version": "0.0.0", - "scripts": { - "dev": "pnpm run server", - "prod": "pnpm run build && pnpm run server:prod", - "vercel-build": "./vercel/deploy.sh", - "build": "vite build && vite build --ssr && vite-plugin-ssr prerender", - "server": "ts-node ./server", - "server:prod": "cross-env NODE_ENV=production ts-node ./server" - }, - "dependencies": { - "@heroicons/react": "^1.0.6", - "@icons-pack/react-simple-icons": "^4.7.0", - "@sd/client": "workspace:*", - "@sd/core": "workspace:*", - "@sd/interface": "workspace:*", - "@sd/ui": "workspace:*", - "@tailwindcss/typography": "^0.5.2", - "@tryghost/content-api": "^1.11.0", - "@vitejs/plugin-react": "^1.2.0", - "clsx": "^1.1.1", - "compression": "^1.7.4", - "cross-env": "^7.0.3", - "express": "^4.17.3", - "phosphor-react": "^1.4.1", - "prismjs": "^1.28.0", - "react": "^18.0.2", - "react-canvas-confetti": "^1.3.0", - "react-device-detect": "^2.2.2", - "react-dom": "^18.0.2", - "react-helmet": "^6.1.0", - "react-hook-form": "^7.31.3", - "react-router-dom": "6.3.0", - "react-tsparticles": "^2.0.6", - "simple-icons": "^7.0.0", - "ts-node": "^10.5.0", - "tsparticles": "^2.0.6", - "typescript": "^4.5.5" - }, - "devDependencies": { - "@babel/preset-react": "^7.17.12", - "@tailwindcss/line-clamp": "^0.4.0", - "@types/compression": "^1.7.2", - "@types/express": "^4.17.13", - "@types/lodash": "^4.14.182", - "@types/node": "^17.0.36", - "@types/prismjs": "^1.26.0", - "@types/react": "^18.0.9", - "@types/react-dom": "^18.0.5", - "@types/react-helmet": "^6.1.5", - "@types/tryghost__content-api": "^1.3.10", - "@vitejs/plugin-react": "^1.3.2", - "autoprefixer": "^10.4.7", - "ncc": "^0.3.6", - "nodemon": "^2.0.16", - "postcss": "^8.4.14", - "sass": "^1.52.1", - "tailwind": "^4.0.0", - "ts-node": "^10.8.0", - "typescript": "^4.7.2", - "vite": "^2.9.9", - "vite-plugin-markdown": "^2.0.2", - "vite-plugin-md": "^0.13.1", - "vite-plugin-ssr": "^0.3.64", - "vite-plugin-svgr": "^2.1.0" - } + "name": "@sd/server", + "scripts": { + "dev": "npm run server", + "prod": "npm run build && npm run server:prod", + "build": "vite build", + "server": "ts-node ./server", + "server:prod": "cross-env NODE_ENV=production ts-node ./server" + }, + "dependencies": { + "@heroicons/react": "^1.0.6", + "@icons-pack/react-simple-icons": "^5.2.0", + "@sd/interface": "link:../../packages/interface", + "@sd/ui": "link:../../packages/ui", + "@tryghost/content-api": "^1.11.0", + "@types/compression": "^1.7.2", + "@types/express": "^4.17.13", + "@types/node": "^17.0.31", + "@types/react": "^18.0.8", + "@types/react-dom": "^18.0.3", + "@vitejs/plugin-react": "^1.3.2", + "clsx": "^1.2.1", + "compression": "^1.7.4", + "cross-env": "^7.0.3", + "express": "^4.18.1", + "phosphor-react": "^1.4.1", + "prismjs": "^1.28.0", + "react": "^18.1.0", + "react-dom": "^18.1.0", + "react-helmet": "^6.1.0", + "react-hook-form": "^7.33.1", + "react-tsparticles": "^2.1.3", + "sirv": "^2.0.2", + "ts-node": "^10.7.0", + "tsparticles": "^2.1.3", + "typescript": "^4.6.4", + "vite": "^2.9.14", + "vite-plugin-ssr": "^0.4.15" + }, + "devDependencies": { + "@tailwindcss/line-clamp": "^0.4.0", + "@tailwindcss/typography": "^0.5.4", + "@types/prismjs": "^1.26.0", + "@types/react-helmet": "^6.1.5", + "@types/tryghost__content-api": "^1.3.11", + "postcss": "^8.4.14", + "sass": "^1.54.0", + "tailwind": "^4.0.0", + "vite-plugin-markdown": "^2.0.2", + "vite-plugin-svgr": "^2.2.1" + } } diff --git a/apps/landing/server/index.ts b/apps/landing/server/index.ts index a4b603d5b..2e9db6514 100644 --- a/apps/landing/server/index.ts +++ b/apps/landing/server/index.ts @@ -1,46 +1,47 @@ -import compression from 'compression'; -import express from 'express'; -import { networkInterfaces } from 'os'; -import { createPageRenderer } from 'vite-plugin-ssr'; +import express from 'express' +import compression from 'compression' +import { renderPage } from 'vite-plugin-ssr' +import { networkInterfaces } from 'os' -const isProduction = process.env.NODE_ENV === 'production'; -const root = `${__dirname}/..`; +const isProduction = process.env.NODE_ENV === 'production' +const root = `${__dirname}/..` -startServer(); +startServer() async function startServer() { - const app = express(); + const app = express() - app.use(compression()); + app.use(compression()) - let viteDevServer; if (isProduction) { - app.use(express.static(`${root}/dist/client`)); + const sirv = require('sirv') + app.use(sirv(`${root}/dist/client`)) } else { - const vite = require('vite'); - viteDevServer = await vite.createServer({ - root, - server: { middlewareMode: 'ssr' } - }); - app.use(viteDevServer.middlewares); + const vite = require('vite') + const viteDevMiddleware = ( + await vite.createServer({ + root, + server: { middlewareMode: 'ssr' }, + }) + ).middlewares + app.use(viteDevMiddleware) } - const renderPage = createPageRenderer({ viteDevServer, isProduction, root }); app.get('*', async (req, res, next) => { - const url = req.originalUrl; + const url = req.originalUrl const pageContextInit = { - url - }; - const pageContext = await renderPage(pageContextInit); - const { httpResponse } = pageContext; - if (!httpResponse) return next(); - const { body, statusCode, contentType } = httpResponse; - res.status(statusCode).type(contentType).send(body); - }); + url, + } + const pageContext = await renderPage(pageContextInit) + const { httpResponse } = pageContext + if (!httpResponse) return next() + const { body, statusCode, contentType } = httpResponse + res.status(statusCode).type(contentType).send(body) + }) - const port = process.env.PORT || 8003; - app.listen(port); - console.log(`Server running at http://localhost:${port}`); + const port = process.env.PORT || 3000 + app.listen(port) + console.log(`Server running at http://localhost:${port}`) const nets = networkInterfaces(); diff --git a/apps/landing/server/tsconfig.json b/apps/landing/server/tsconfig.json deleted file mode 100644 index 566367f2a..000000000 --- a/apps/landing/server/tsconfig.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "compilerOptions": { - "lib": ["ESNext"], - "declaration": false, - "noEmit": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "inlineSources": false, - "isolatedModules": false, - "module": "CommonJS", - "target": "ES5", - "moduleResolution": "node", - "noUnusedLocals": false, - "noUnusedParameters": false, - "preserveWatchOutput": true, - "skipLibCheck": false, - "strict": true, - "allowSyntheticDefaultImports": true, - "resolveJsonModule": true - }, - "exclude": ["node_modules"] -} diff --git a/apps/landing/src/App.tsx b/apps/landing/src/App.tsx index 4c009cba9..7c3ede8fb 100644 --- a/apps/landing/src/App.tsx +++ b/apps/landing/src/App.tsx @@ -9,7 +9,7 @@ import NavBar from './components/NavBar'; import { PageContextProvider } from './renderer/usePageContext'; import './style.scss'; -export function App({ +export default function App({ children, pageContext }: { diff --git a/apps/landing/src/components/NavBar.tsx b/apps/landing/src/components/NavBar.tsx index 34d1908cd..11db4243d 100644 --- a/apps/landing/src/components/NavBar.tsx +++ b/apps/landing/src/components/NavBar.tsx @@ -1,13 +1,12 @@ import { BookOpenIcon, MapIcon, QuestionMarkCircleIcon, UsersIcon } from '@heroicons/react/solid'; -import { Discord, Github, Icon } from '@icons-pack/react-simple-icons'; +import { Discord, Github } from '@icons-pack/react-simple-icons'; import { Dropdown, DropdownItem } from '@sd/ui'; import clsx from 'clsx'; import { List } from 'phosphor-react'; import React, { useEffect, useState } from 'react'; -import { positions } from '../pages/careers.page'; - import AppLogo from '../assets/images/logo.png'; +import { positions } from '../pages/careers.page'; import { getWindow } from '../utils'; function NavLink(props: { link?: string; children: string }) { @@ -23,7 +22,7 @@ function NavLink(props: { link?: string; children: string }) { } function dropdownItem( - props: { name: string; icon: Icon } & ({ href: string } | { path: string }) + props: { name: string; icon: any } & ({ href: string } | { path: string }) ): DropdownItem[number] { if ('href' in props) { return { @@ -82,7 +81,12 @@ export default function NavBar() { Blog
Careers - {positions.length > 0 ? {positions.length} : null} + {positions.length > 0 ? ( + + {' '} + {positions.length}{' '} + + ) : null}
({ - url: `/blog/${post.slug}`, - pageContext: { pageProps: { post } } - })); - - const postListPage = { + return { url: '/blog', pageContext: { pageProps: { posts } } }; - - return [postListPage, ...postPages]; } diff --git a/apps/landing/src/pages/blog/index.page.tsx b/apps/landing/src/pages/blog/index.page.tsx index cd316f953..3a90b23c1 100644 --- a/apps/landing/src/pages/blog/index.page.tsx +++ b/apps/landing/src/pages/blog/index.page.tsx @@ -62,4 +62,4 @@ function Page({ posts }: { posts: PostOrPage[] }) { ); } -export default Page; +export { Page }; diff --git a/apps/landing/src/pages/blog/post.page.route.ts b/apps/landing/src/pages/blog/post.page.route.ts index 9ff84ad95..2698d0cee 100644 --- a/apps/landing/src/pages/blog/post.page.route.ts +++ b/apps/landing/src/pages/blog/post.page.route.ts @@ -1 +1 @@ -export default '/blog/:slug'; +export default '/blog/@slug'; diff --git a/apps/landing/src/pages/blog/post.page.server.ts b/apps/landing/src/pages/blog/post.page.server.ts index 2a6f65bfb..25d68151c 100644 --- a/apps/landing/src/pages/blog/post.page.server.ts +++ b/apps/landing/src/pages/blog/post.page.server.ts @@ -1,6 +1,6 @@ import { PageContextBuiltIn } from 'vite-plugin-ssr'; -import { getPost } from './api'; +import { getPost, getPosts } from './api'; export async function onBeforeRender(pageContext: PageContextBuiltIn) { const post = await getPost(pageContext.routeParams['slug']); @@ -13,3 +13,12 @@ export async function onBeforeRender(pageContext: PageContextBuiltIn) { } }; } + +export async function prerender() { + const posts = await getPosts(); + + return posts.map((post) => ({ + url: `/blog/${post.slug}`, + pageContext: { pageProps: { post } } + })); +} diff --git a/apps/landing/src/pages/blog/post.page.tsx b/apps/landing/src/pages/blog/post.page.tsx index e4a0f332f..560377514 100644 --- a/apps/landing/src/pages/blog/post.page.tsx +++ b/apps/landing/src/pages/blog/post.page.tsx @@ -1,7 +1,7 @@ import { PostOrPage, Tag } from '@tryghost/content-api'; import Prism from 'prismjs'; import 'prismjs/components/prism-rust'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { Helmet } from 'react-helmet'; import '../../atom-one.css'; @@ -72,4 +72,4 @@ function MarkdownPage({ post }: { post: PostOrPage }) { ); } -export default MarkdownPage; +export { MarkdownPage }; diff --git a/apps/landing/src/pages/careers.page.tsx b/apps/landing/src/pages/careers.page.tsx index 28e1a569c..6aa9fc227 100644 --- a/apps/landing/src/pages/careers.page.tsx +++ b/apps/landing/src/pages/careers.page.tsx @@ -10,10 +10,8 @@ import { TrendingUpIcon } from '@heroicons/react/outline'; import { Button } from '@sd/ui'; -import { Heartbeat } from 'phosphor-react'; import React from 'react'; import { Helmet } from 'react-helmet'; -import { ReactComponent as Content } from '~/docs/changelog/index.md'; interface PositionPosting { name: string; @@ -188,4 +186,4 @@ function Page() { ); } -export default Page; +export { Page }; diff --git a/apps/landing/src/pages/changelog.page.tsx b/apps/landing/src/pages/changelog.page.tsx index e71239908..4a27f8b7c 100644 --- a/apps/landing/src/pages/changelog.page.tsx +++ b/apps/landing/src/pages/changelog.page.tsx @@ -16,4 +16,4 @@ function Page() { ); } -export default Page; +export { Page }; diff --git a/apps/landing/src/pages/docs/architecture/distributed-data-sync.tsx b/apps/landing/src/pages/docs/architecture/distributed-data-sync.tsx index 709e8706a..c1549948e 100644 --- a/apps/landing/src/pages/docs/architecture/distributed-data-sync.tsx +++ b/apps/landing/src/pages/docs/architecture/distributed-data-sync.tsx @@ -19,4 +19,4 @@ function Page() { ); } -export default Page; +export { Page }; diff --git a/apps/landing/src/pages/faq.page.tsx b/apps/landing/src/pages/faq.page.tsx index e8c6da98c..9ddd284b3 100644 --- a/apps/landing/src/pages/faq.page.tsx +++ b/apps/landing/src/pages/faq.page.tsx @@ -16,4 +16,4 @@ function Page() { ); } -export default Page; +export { Page }; diff --git a/apps/landing/src/pages/index.page.tsx b/apps/landing/src/pages/index.page.tsx index 3b9d20cd5..d4c39fa7f 100644 --- a/apps/landing/src/pages/index.page.tsx +++ b/apps/landing/src/pages/index.page.tsx @@ -155,4 +155,4 @@ function Page() { ); } -export default Page; +export { Page }; diff --git a/apps/landing/src/pages/roadmap.page.tsx b/apps/landing/src/pages/roadmap.page.tsx index cd73bbd1e..f34dd76ff 100644 --- a/apps/landing/src/pages/roadmap.page.tsx +++ b/apps/landing/src/pages/roadmap.page.tsx @@ -20,4 +20,4 @@ function Page() { ); } -export default Page; +export { Page }; diff --git a/apps/landing/src/pages/team.page.tsx b/apps/landing/src/pages/team.page.tsx index 301816da0..8c22a8b27 100644 --- a/apps/landing/src/pages/team.page.tsx +++ b/apps/landing/src/pages/team.page.tsx @@ -253,4 +253,4 @@ function Page() { ); } -export default Page; +export { Page }; diff --git a/apps/landing/src/renderer/_default.page.client.tsx b/apps/landing/src/renderer/_default.page.client.tsx index 6f5a0acf1..4f9eaf0cf 100644 --- a/apps/landing/src/renderer/_default.page.client.tsx +++ b/apps/landing/src/renderer/_default.page.client.tsx @@ -1,43 +1,20 @@ -import React from 'react'; -import { Root, createRoot, hydrateRoot } from 'react-dom/client'; -import { useClientRouter } from 'vite-plugin-ssr/client/router'; -import type { PageContextBuiltInClient } from 'vite-plugin-ssr/client/router'; +import React from 'react' +import { hydrateRoot } from 'react-dom/client' +import App from '../App' +import type { PageContext } from './types' +import type { PageContextBuiltInClient } from 'vite-plugin-ssr/client' -import { App } from '../App'; -import type { PageContext } from './types'; +export { render } -let root: Root; -const { hydrationPromise } = useClientRouter({ - render(pageContext: PageContextBuiltInClient & PageContext) { - const { Page, pageProps } = pageContext; - const page = ( - - - - ); - const container = document.getElementById('page-view')!; - if (pageContext.isHydration) { - root = hydrateRoot(container, page); - } else { - if (!root) { - root = createRoot(container); - } - root.render(page); - } - } - // onTransitionStart, - // onTransitionEnd -}); +async function render(pageContext: PageContextBuiltInClient & PageContext) { + const { Page, pageProps } = pageContext + hydrateRoot( + document.getElementById('page-view')!, + + + , + ) +} -hydrationPromise.then(() => { - console.log('Hydration finished; page is now interactive.'); -}); +export const clientRouting = true -// function onTransitionStart() { -// console.log('Page transition start'); -// document.getElementById('page-view')!.classList.add('page-transition'); -// } -// function onTransitionEnd() { -// console.log('Page transition end'); -// document.getElementById('#page-content')!.classList.remove('page-transition'); -// } diff --git a/apps/landing/src/renderer/_default.page.server.tsx b/apps/landing/src/renderer/_default.page.server.tsx index f6dcbcea6..5042008fc 100644 --- a/apps/landing/src/renderer/_default.page.server.tsx +++ b/apps/landing/src/renderer/_default.page.server.tsx @@ -4,7 +4,7 @@ import { Helmet } from 'react-helmet'; import { dangerouslySkipEscape, escapeInject } from 'vite-plugin-ssr'; import type { PageContextBuiltIn } from 'vite-plugin-ssr'; -import { App } from '../App'; +import App from '../App'; import type { PageContext } from './types'; export { render }; diff --git a/apps/landing/tsconfig.json b/apps/landing/tsconfig.json index 7227c5eee..0b16233b2 100644 --- a/apps/landing/tsconfig.json +++ b/apps/landing/tsconfig.json @@ -1,9 +1,10 @@ { - "extends": "../../packages/config/interface.tsconfig.json", - "compilerOptions": { - "paths": { - "~/docs/*": ["../../docs/*"] - } - }, - "include": ["src"] + "extends": "../../packages/config/interface.tsconfig.json", + "include": ["src"], + "ts-node": { + "transpileOnly": true, + "compilerOptions": { + "module": "CommonJS" + } + } } diff --git a/apps/landing/vite.config.ts b/apps/landing/vite.config.ts index 2bc3b7830..19e24436e 100644 --- a/apps/landing/vite.config.ts +++ b/apps/landing/vite.config.ts @@ -1,13 +1,11 @@ -import react from '@vitejs/plugin-react'; -import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react' +import ssr from 'vite-plugin-ssr/plugin' +import { defineConfig } from 'vite' import md, { Mode } from 'vite-plugin-markdown'; -import ssr from 'vite-plugin-ssr/plugin'; import svg from 'vite-plugin-svgr'; -// https://vitejs.dev/config/ export default defineConfig({ - // @ts-ignore - plugins: [react(), ssr(), svg(), md({ mode: [Mode.REACT] })], + plugins: [react(), ssr({ prerender: true }), svg(), md({ mode: [Mode.REACT] })], resolve: { alias: { '~/docs': __dirname + '../../../docs' @@ -17,4 +15,4 @@ export default defineConfig({ port: 8003 }, publicDir: 'public' -}); +}) diff --git a/apps/server/Cargo.toml b/apps/server/Cargo.toml index 4a242d8c9..2a512ff97 100644 --- a/apps/server/Cargo.toml +++ b/apps/server/Cargo.toml @@ -4,10 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -actix = "0.13.0" -actix-web = "4.0.1" -actix-web-actors = "4.1.0" sdcore = { path = "../../core", features = [] } -serde = "1.0.136" -serde_json = "1.0.79" -tokio = { version = "1.17.0", features = ["sync", "rt"] } \ No newline at end of file +axum = "0.5.13" +tokio = { version = "1.17.0", features = ["sync", "rt-multi-thread", "signal"] } +tracing = "0.1.35" +ctrlc = "3.2.2" diff --git a/apps/server/src/main.rs b/apps/server/src/main.rs index 1bdef0dd6..3711f815b 100644 --- a/apps/server/src/main.rs +++ b/apps/server/src/main.rs @@ -1,282 +1,19 @@ -use sdcore::{ClientCommand, ClientQuery, CoreEvent, CoreResponse, Node, NodeController}; -use std::{ - collections::HashSet, - env, - path::Path, - sync::{Arc, RwLock}, - time::{Duration, Instant}, -}; +use std::{env, net::SocketAddr, path::Path}; -use actix::{ - Actor, ActorContext, Addr, AsyncContext, Context, ContextFutureSpawner, Handler, - Message, StreamHandler, WrapFuture, -}; -use actix_web::{ - get, http::StatusCode, web, App, Error, HttpRequest, HttpResponse, HttpServer, - Responder, -}; -use actix_web_actors::ws; -use serde::{Deserialize, Serialize}; +use axum::{handler::Handler, routing::get}; +use sdcore::Node; +use tracing::info; -use tokio::sync::{mpsc, oneshot}; +mod utils; -const DATA_DIR_ENV_VAR: &str = "DATA_DIR"; - -#[derive(Serialize)] -pub struct Event(CoreEvent); - -impl Message for Event { - type Result = (); -} - -struct EventServer { - clients: Arc>>>, -} - -impl Actor for EventServer { - type Context = Context; -} - -impl EventServer { - pub fn listen(mut event_receiver: mpsc::Receiver) -> Addr { - let server = Self { - clients: Arc::new(RwLock::new(HashSet::new())), - }; - let clients = server.clients.clone(); - tokio::spawn(async move { - let mut last = Instant::now(); - while let Some(event) = event_receiver.recv().await { - match event { - CoreEvent::InvalidateQueryDebounced(_) => { - let current = Instant::now(); - if current.duration_since(last) > Duration::from_millis(1000 / 60) - { - last = current; - for client in clients.read().unwrap().iter() { - client.do_send(Event(event.clone())); - } - } - }, - event => { - for client in clients.read().unwrap().iter() { - client.do_send(Event(event.clone())); - } - }, - } - } - }); - server.start() - } -} - -enum EventServerOperation { - Connect(Addr), - Disconnect(Addr), -} - -impl Message for EventServerOperation { - type Result = (); -} - -impl Handler for EventServer { - type Result = (); - - fn handle( - &mut self, - msg: EventServerOperation, - _: &mut Context, - ) -> Self::Result { - match msg { - EventServerOperation::Connect(addr) => { - self.clients.write().unwrap().insert(addr) - }, - EventServerOperation::Disconnect(addr) => { - self.clients.write().unwrap().remove(&addr) - }, - }; - } -} - -/// Define HTTP actor -struct Socket { - node_controller: web::Data, - event_server: web::Data>, -} - -impl Actor for Socket { - type Context = ws::WebsocketContext; -} - -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "camelCase", tag = "type", content = "data")] -enum SocketMessagePayload { - Command(ClientCommand), - Query(ClientQuery), -} - -#[derive(Serialize, Deserialize, Message)] -#[rtype(result = "()")] -#[serde(rename_all = "camelCase")] -struct SocketMessage { - id: String, - payload: SocketMessagePayload, -} - -impl StreamHandler> for Socket { - fn handle( - &mut self, - msg: Result, - ctx: &mut Self::Context, - ) { - // TODO: Add heartbeat and reconnect logic in the future. We can refer to https://github.com/actix/examples/blob/master/websockets/chat/src/session.rs for the heartbeat stuff. - - match msg { - Ok(ws::Message::Ping(msg)) => ctx.pong(&msg), - Ok(ws::Message::Text(text)) => { - let msg = serde_json::from_str::(&text); - - let msg = match msg { - Ok(msg) => msg, - Err(err) => { - println!("Error parsing message: {}", err); - return; - }, - }; - - let core = self.node_controller.clone(); - self.event_server - .do_send(EventServerOperation::Connect(ctx.address())); - - let recipient = ctx.address().recipient(); - let fut = async move { - match msg.payload { - SocketMessagePayload::Query(query) => { - match core.query(query).await { - Ok(response) => { - recipient.do_send(SocketResponse::Response { - id: msg.id.clone(), - payload: response, - }) - }, - Err(err) => { - println!("query error: {:?}", err); - // Err(err.to_string()) - }, - }; - }, - SocketMessagePayload::Command(command) => { - match core.command(command).await { - Ok(response) => { - recipient.do_send(SocketResponse::Response { - id: msg.id.clone(), - payload: response, - }) - }, - Err(err) => { - println!("command error: {:?}", err); - // Err(err.to_string()) - }, - }; - }, - } - }; - - fut.into_actor(self).spawn(ctx); - }, - _ => {}, - } - } - - fn finished(&mut self, ctx: &mut Self::Context) { - self.event_server - .do_send(EventServerOperation::Disconnect(ctx.address())); - ctx.stop(); - } -} - -impl Handler for Socket { - type Result = (); - - fn handle(&mut self, msg: Event, ctx: &mut Self::Context) { - ctx.text(serde_json::to_string(&SocketResponse::Event(msg.0)).unwrap()); - } -} - -#[derive(Message, Serialize)] -#[serde(rename_all = "camelCase", tag = "type", content = "data")] -#[rtype(result = "()")] -enum SocketResponse { - Response { id: String, payload: CoreResponse }, - Event(CoreEvent), -} - -impl Handler for Socket { - type Result = (); - - fn handle(&mut self, msg: SocketResponse, ctx: &mut Self::Context) { - ctx.text(serde_json::to_string(&msg).unwrap()); - } -} - -#[get("/")] -async fn index() -> impl Responder { - "Spacedrive Server!" -} - -#[get("/health")] -async fn healthcheck() -> impl Responder { - "OK" -} - -#[get("/ws")] -async fn ws_handler( - req: HttpRequest, - stream: web::Payload, - controller: web::Data, - server: web::Data>, -) -> Result { - ws::start( - Socket { - node_controller: controller, - event_server: server, - }, - &req, - stream, - ) -} - -async fn not_found() -> impl Responder { - HttpResponse::build(StatusCode::OK).body("We're past the event horizon...") -} - -#[actix_web::main] -async fn main() -> std::io::Result<()> { - let (event_receiver, controller) = setup().await; - - let server = web::Data::new(EventServer::listen(event_receiver)); - - println!("Listening http://localhost:8080"); - HttpServer::new(move || { - App::new() - .app_data(controller.clone()) - .app_data(server.clone()) - .service(index) - .service(healthcheck) - .service(ws_handler) - .default_service(web::route().to(not_found)) - }) - .bind(("0.0.0.0", 8080))? - .run() - .await -} - -async fn setup() -> (mpsc::Receiver, web::Data) { - let data_dir_path = match env::var(DATA_DIR_ENV_VAR) { +#[tokio::main] +async fn main() { + let data_dir = match env::var("DATA_DIR") { Ok(path) => Path::new(&path).to_path_buf(), Err(_e) => { #[cfg(not(debug_assertions))] { - panic!("${} is not set ({})", DATA_DIR_ENV_VAR, _e) + panic!("'$DATA_DIR' is not set ({})", _e) } std::env::current_dir() @@ -287,10 +24,38 @@ async fn setup() -> (mpsc::Receiver, web::Data) { }, }; - let (controller, event_receiver, node, _shutdown_completion_rx) = - Node::new(data_dir_path).await; - let (_shutdown_tx, shutdown_rx) = oneshot::channel(); - tokio::spawn(node.start(shutdown_rx)); + let port = env::var("PORT") + .map(|port| port.parse::().unwrap_or(8080)) + .unwrap_or(8080); - (event_receiver, web::Data::new(controller)) + let (node, router) = Node::new(data_dir).await; + + ctrlc::set_handler({ + let node = node.clone(); + move || { + node.shutdown(); + } + }) + .expect("Error setting Ctrl-C handler"); + + let app = axum::Router::new() + .route("/", get(|| async { "Spacedrive Server!" })) + .route("/health", get(|| async { "OK" })) + .route( + "/rspcws", + router.axum_ws_handler(move || node.get_request_context()), + ) + .fallback( + (|| async { "404 Not Found: We're past the event horizon..." }) + .into_service(), + ); + + let mut addr = "[::]:8080".parse::().unwrap(); // This listens on IPv6 and IPv4 + addr.set_port(port); + info!("Listening on http://localhost:{}", port); + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .with_graceful_shutdown(utils::axum_shutdown_signal()) + .await + .expect("Error with HTTP server!"); } diff --git a/apps/server/src/utils.rs b/apps/server/src/utils.rs new file mode 100644 index 000000000..f6437d365 --- /dev/null +++ b/apps/server/src/utils.rs @@ -0,0 +1,28 @@ +use tokio::signal; + +/// shutdown_signal will inform axum to gracefully shutdown when the process is asked to shutdown. +pub async fn axum_shutdown_signal() { + let ctrl_c = async { + signal::ctrl_c() + .await + .expect("failed to install Ctrl+C handler"); + }; + + #[cfg(unix)] + let terminate = async { + signal::unix::signal(signal::unix::SignalKind::terminate()) + .expect("failed to install signal handler") + .recv() + .await; + }; + + #[cfg(not(unix))] + let terminate = std::future::pending::<()>(); + + tokio::select! { + _ = ctrl_c => {}, + _ = terminate => {}, + } + + println!("signal received, starting graceful shutdown"); +} diff --git a/apps/web/package.json b/apps/web/package.json index 2bba515ce..04c98aa0e 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -8,24 +8,26 @@ "preview": "vite preview" }, "dependencies": { - "@fontsource/inter": "^4.5.10", + "@fontsource/inter": "^4.5.11", + "@rspc/client": "^0.0.5", "@sd/client": "workspace:*", "@sd/core": "workspace:*", "@sd/interface": "workspace:*", "@sd/ui": "workspace:*", - "react": "^18.1.0", - "react-dom": "^18.1.0" + "@tanstack/react-query": "^4.0.10", + "react": "^18.2.0", + "react-dom": "^18.2.0" }, "devDependencies": { - "@types/react": "^18.0.9", - "@types/react-dom": "^18.0.5", - "@vitejs/plugin-react": "^1.3.2", + "@types/react": "^18.0.15", + "@types/react-dom": "^18.0.6", + "@vitejs/plugin-react": "^2.0.0", "autoprefixer": "^10.4.7", "postcss": "^8.4.14", "tailwind": "^4.0.0", - "typescript": "^4.7.2", - "vite": "^2.9.9", - "vite-plugin-svgr": "^2.1.0", - "vite-plugin-tsconfig-paths": "^1.0.5" + "typescript": "^4.7.4", + "vite": "^3.0.3", + "vite-plugin-svgr": "^2.2.1", + "vite-plugin-tsconfig-paths": "^1.1.0" } } diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 7ae21f2f4..a01b6386a 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -1,121 +1,13 @@ -import { BaseTransport } from '@sd/client'; -import { ClientCommand, ClientQuery } from '@sd/core'; +import { WebsocketTransport, createClient } from '@rspc/client'; +import { Operations, queryClient, rspc } from '@sd/client'; import SpacedriveInterface from '@sd/interface'; import React, { useEffect } from 'react'; -const timeouts = [1000, 2000, 5000, 10000]; // In milliseconds - -const randomId = () => Math.random().toString(36).slice(2); - -// bind state to core via Tauri -class Transport extends BaseTransport { - websocket: WebSocket; - requestMap = new Map void>(); - - constructor() { - super(); - this.websocket = new WebSocket( - import.meta.env.VITE_SDSERVER_BASE_URL || 'ws://localhost:8080/ws' - ); - this.attachEventListeners(); - } - - async reconnect(timeoutIndex = 0) { - let timeout = - (timeouts[timeoutIndex] ?? timeouts[timeouts.length - 1]) + - (Math.floor(Math.random() * 5000 /* 5 Seconds */) + 1); - - setTimeout(() => { - let ws = new WebSocket(import.meta.env.VITE_SDSERVER_BASE_URL || 'ws://localhost:8080/ws'); - new Promise(function (resolve, reject) { - ws.addEventListener('open', () => resolve(null)); - ws.addEventListener('close', reject); - }) - .then(() => { - this.websocket = ws; - this.attachEventListeners(); - }) - .catch((err) => this.reconnect(timeoutIndex++)); - }, timeout); - } - - attachEventListeners() { - this.websocket.addEventListener('message', (event) => { - if (!event.data) return; - - const { type: msg_type, data: msg_data } = JSON.parse(event.data); - - if (msg_type === 'response') { - const id = msg_data.id; - if (this.requestMap.has(id)) { - this.requestMap.get(id)?.({ data: msg_data.payload.data }); - this.requestMap.delete(id); - } - } else if (msg_type === 'event') { - this.emit('core_event', msg_data); - } else { - console.error(`Received response message of type ${msg_type} which is not valid!`); - } - }); - - this.websocket.addEventListener('close', () => { - this.reconnect(); - }); - } - - async query(query: ClientQuery) { - if (this.websocket.readyState == 0) { - let resolve: () => void; - const promise = new Promise((res) => { - resolve = () => res(undefined); - }); - // @ts-ignore - this.websocket.addEventListener('open', resolve); - await promise; - } - - const id = randomId(); - let resolve: (data: any) => void; - - const promise = new Promise((res) => { - resolve = res; - }); - - // @ts-ignore - this.requestMap.set(id, resolve); - - this.websocket.send(JSON.stringify({ id, payload: { type: 'query', data: query } })); - - return await promise; - } - async command(command: ClientCommand) { - if (this.websocket.readyState == 0) { - let resolve: () => void; - const promise = new Promise((res) => { - resolve = () => res(undefined); - }); - // @ts-ignore - this.websocket.addEventListener('open', resolve); - await promise; - } - - const id = randomId(); - let resolve: (data: any) => void; - - const promise = new Promise((res) => { - resolve = res; - }); - - // @ts-ignore - this.requestMap.set(id, resolve); - - this.websocket.send(JSON.stringify({ id, payload: { type: 'command', data: command } })); - - return await promise; - } -} - -const transport = new Transport(); +const client = createClient({ + transport: new WebsocketTransport( + import.meta.env.VITE_SDSERVER_BASE_URL || 'ws://localhost:8080/rspcws' + ) +}); function App() { useEffect(() => { @@ -124,20 +16,20 @@ function App() { return (
- {/*
*/} - { - return Promise.resolve([]); - }} - /> + + { + return Promise.resolve([]); + }} + /> +
); } diff --git a/core/.rustfmt.toml b/core/.rustfmt.toml index 411a5f052..89f5d09e3 100644 --- a/core/.rustfmt.toml +++ b/core/.rustfmt.toml @@ -10,5 +10,4 @@ merge_derives = true use_try_shorthand = false use_field_init_shorthand = false force_explicit_abi = true -# normalize_comments = true -normalize_doc_attributes = true \ No newline at end of file +# normalize_comments = true \ No newline at end of file diff --git a/core/Cargo.toml b/core/Cargo.toml index bc6968e0b..3eddc564e 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -16,7 +16,7 @@ hostname = "0.3.1" # Universal Dependencies base64 = "0.13.0" serde = { version = "1.0", features = ["derive"] } -chrono = { version = "0.4.0", features = ["serde"] } +chrono = { version = "0.4.19", features = ["serde"] } serde_json = "1.0" futures = "0.3" data-encoding = "2.3.2" @@ -26,20 +26,23 @@ rmp = "^0.8.11" rmp-serde = "^1.1.0" # Project dependencies -ts-rs = { version = "6.2", features = ["chrono-impl", "uuid-impl", "serde-compat"] } -prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust.git", tag = "0.5.2" } +prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust.git", branch = "0.6.0", features = ["rspc"] } +rspc = { version = "0.0.4", features = ["axum", "tauri", "uuid", "chrono", "tracing"] } walkdir = "^2.3.2" -uuid = { version = "^0.8.2", features = ["v4", "serde"]} +uuid = { version = "1.1.2", features = ["v4", "serde"] } sysinfo = "0.23.9" thiserror = "1.0.30" core-derive = { path = "./derive" } -tokio = { version = "^1.17.0", features = ["sync", "rt"] } +tokio = { version = "1.17.0", features = ["sync", "rt-multi-thread"] } include_dir = { version = "0.7.2", features = ["glob"] } async-trait = "^0.1.52" image = "0.24.1" webp = "0.2.2" ffmpeg-next = "5.0.3" fs_extra = "1.2.0" -log = { version = "0.4.17", features = ["max_level_trace"] } -env_logger = "0.9.0" +tracing = "0.1.35" +tracing-subscriber = "0.3.14" +async-stream = "0.3.3" +once_cell = "1.13.0" +ctor = "0.1.22" diff --git a/core/bindings/Client.ts b/core/bindings/Client.ts deleted file mode 100644 index 4660f7e8d..000000000 --- a/core/bindings/Client.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { Platform } from './Platform'; - -export interface Client { - uuid: string; - name: string; - platform: Platform; - tcp_address: string; - last_seen: string; - last_synchronized: string; -} diff --git a/core/bindings/ClientCommand.ts b/core/bindings/ClientCommand.ts deleted file mode 100644 index fd9b1f0a1..000000000 --- a/core/bindings/ClientCommand.ts +++ /dev/null @@ -1,4 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { LibraryCommand } from "./LibraryCommand"; - -export type ClientCommand = { key: "CreateLibrary", params: { name: string, } } | { key: "EditLibrary", params: { id: string, name: string | null, description: string | null, } } | { key: "DeleteLibrary", params: { id: string, } } | { key: "LibraryCommand", params: { library_id: string, command: LibraryCommand, } }; \ No newline at end of file diff --git a/core/bindings/ClientQuery.ts b/core/bindings/ClientQuery.ts deleted file mode 100644 index d48b1859d..000000000 --- a/core/bindings/ClientQuery.ts +++ /dev/null @@ -1,4 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { LibraryQuery } from "./LibraryQuery"; - -export type ClientQuery = { key: "GetLibraries" } | { key: "GetNode" } | { key: "GetVolumes" } | { key: "GetNodes" } | { key: "LibraryQuery", params: { library_id: string, query: LibraryQuery, } }; \ No newline at end of file diff --git a/core/bindings/ClientState.ts b/core/bindings/ClientState.ts deleted file mode 100644 index ce5c2d7af..000000000 --- a/core/bindings/ClientState.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { LibraryState } from './LibraryState'; - -export interface ClientState { - client_uuid: string; - client_id: number; - client_name: string; - data_path: string; - tcp_port: number; - libraries: Array; - current_library_uuid: string; -} diff --git a/core/bindings/ConfigMetadata.ts b/core/bindings/ConfigMetadata.ts deleted file mode 100644 index d12aaa575..000000000 --- a/core/bindings/ConfigMetadata.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export interface ConfigMetadata { version: string | null, } \ No newline at end of file diff --git a/core/bindings/CoreEvent.ts b/core/bindings/CoreEvent.ts deleted file mode 100644 index 91a556227..000000000 --- a/core/bindings/CoreEvent.ts +++ /dev/null @@ -1,5 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ClientQuery } from "./ClientQuery"; -import type { CoreResource } from "./CoreResource"; - -export type CoreEvent = { key: "InvalidateQuery", data: ClientQuery } | { key: "InvalidateQueryDebounced", data: ClientQuery } | { key: "InvalidateResource", data: CoreResource } | { key: "NewThumbnail", data: { cas_id: string, } } | { key: "Log", data: { message: string, } } | { key: "DatabaseDisconnected", data: { reason: string | null, } }; \ No newline at end of file diff --git a/core/bindings/CoreResource.ts b/core/bindings/CoreResource.ts deleted file mode 100644 index 7ca5999ca..000000000 --- a/core/bindings/CoreResource.ts +++ /dev/null @@ -1,7 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { File } from "./File"; -import type { JobReport } from "./JobReport"; -import type { LocationResource } from "./LocationResource"; -import type { Tag } from "./Tag"; - -export type CoreResource = { key: "Client" } | { key: "Library" } | { key: "Location", data: LocationResource } | { key: "File", data: File } | { key: "Job", data: JobReport } | { key: "Tag", data: Tag }; \ No newline at end of file diff --git a/core/bindings/CoreResponse.ts b/core/bindings/CoreResponse.ts deleted file mode 100644 index f9ee981c7..000000000 --- a/core/bindings/CoreResponse.ts +++ /dev/null @@ -1,12 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { DirectoryWithContents } from "./DirectoryWithContents"; -import type { JobReport } from "./JobReport"; -import type { LibraryConfigWrapped } from "./LibraryConfigWrapped"; -import type { LocationResource } from "./LocationResource"; -import type { NodeState } from "./NodeState"; -import type { Statistics } from "./Statistics"; -import type { Tag } from "./Tag"; -import type { TagWithFiles } from "./TagWithFiles"; -import type { Volume } from "./Volume"; - -export type CoreResponse = { key: "Success", data: null } | { key: "Error", data: string } | { key: "GetLibraries", data: Array } | { key: "GetVolumes", data: Array } | { key: "TagCreateResponse", data: Tag } | { key: "GetTag", data: Tag | null } | { key: "GetTags", data: Array } | { key: "GetLocation", data: LocationResource } | { key: "GetLocations", data: Array } | { key: "GetExplorerDir", data: DirectoryWithContents } | { key: "GetNode", data: NodeState } | { key: "LocCreate", data: LocationResource } | { key: "OpenTag", data: Array } | { key: "GetRunningJobs", data: Array } | { key: "GetJobHistory", data: Array } | { key: "GetLibraryStatistics", data: Statistics }; \ No newline at end of file diff --git a/core/bindings/DirectoryWithContents.ts b/core/bindings/DirectoryWithContents.ts deleted file mode 100644 index c41944db8..000000000 --- a/core/bindings/DirectoryWithContents.ts +++ /dev/null @@ -1,4 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { FilePath } from "./FilePath"; - -export interface DirectoryWithContents { directory: FilePath, contents: Array, } \ No newline at end of file diff --git a/core/bindings/EncryptionAlgorithm.ts b/core/bindings/EncryptionAlgorithm.ts deleted file mode 100644 index d5b7d9889..000000000 --- a/core/bindings/EncryptionAlgorithm.ts +++ /dev/null @@ -1,2 +0,0 @@ - -export type EncryptionAlgorithm = "None" | "AES128" | "AES192" | "AES256"; \ No newline at end of file diff --git a/core/bindings/File.ts b/core/bindings/File.ts deleted file mode 100644 index 8f8dd6aa6..000000000 --- a/core/bindings/File.ts +++ /dev/null @@ -1,5 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { FileKind } from "./FileKind"; -import type { FilePath } from "./FilePath"; - -export interface File { id: number, cas_id: string, integrity_checksum: string | null, size_in_bytes: string, kind: FileKind, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, paths: Array, } \ No newline at end of file diff --git a/core/bindings/FileKind.ts b/core/bindings/FileKind.ts deleted file mode 100644 index 2b4f51fc7..000000000 --- a/core/bindings/FileKind.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type FileKind = "Unknown" | "Directory" | "Package" | "Archive" | "Image" | "Video" | "Audio" | "Plaintext" | "Alias"; \ No newline at end of file diff --git a/core/bindings/FilePath.ts b/core/bindings/FilePath.ts deleted file mode 100644 index 6d45b049e..000000000 --- a/core/bindings/FilePath.ts +++ /dev/null @@ -1,4 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { File } from "./File"; - -export interface FilePath { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, file_id: number | null, parent_id: number | null, date_created: string, date_modified: string, date_indexed: string, file: File | null, } \ No newline at end of file diff --git a/core/bindings/JobReport.ts b/core/bindings/JobReport.ts deleted file mode 100644 index d6f964bee..000000000 --- a/core/bindings/JobReport.ts +++ /dev/null @@ -1,4 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { JobStatus } from "./JobStatus"; - -export interface JobReport { id: string, name: string, data: Array | null, date_created: string, date_modified: string, status: JobStatus, task_count: number, completed_task_count: number, message: string, seconds_elapsed: string, } \ No newline at end of file diff --git a/core/bindings/JobStatus.ts b/core/bindings/JobStatus.ts deleted file mode 100644 index 88aa16ae5..000000000 --- a/core/bindings/JobStatus.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused"; \ No newline at end of file diff --git a/core/bindings/LibraryCommand.ts b/core/bindings/LibraryCommand.ts deleted file mode 100644 index 3438a4085..000000000 --- a/core/bindings/LibraryCommand.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type LibraryCommand = { key: "FileReadMetaData", params: { id: number, } } | { key: "FileSetNote", params: { id: number, note: string | null, } } | { key: "FileSetFavorite", params: { id: number, favorite: boolean, } } | { key: "FileDelete", params: { id: number, } } | { key: "TagCreate", params: { name: string, color: string, } } | { key: "TagUpdate", params: { id: number, name: string | null, color: string | null, } } | { key: "TagAssign", params: { file_id: number, tag_id: number, } } | { key: "TagDelete", params: { id: number, } } | { key: "LocCreate", params: { path: string, } } | { key: "LocUpdate", params: { id: number, name: string | null, } } | { key: "LocDelete", params: { id: number, } } | { key: "LocFullRescan", params: { id: number, } } | { key: "LocQuickRescan", params: { id: number, } } | { key: "VolUnmount", params: { id: number, } } | { key: "GenerateThumbsForLocation", params: { id: number, path: string, } } | { key: "IdentifyUniqueFiles", params: { id: number, path: string, } }; \ No newline at end of file diff --git a/core/bindings/LibraryConfig.ts b/core/bindings/LibraryConfig.ts deleted file mode 100644 index 8a371014b..000000000 --- a/core/bindings/LibraryConfig.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export interface LibraryConfig { version: string | null, name: string, description: string, } \ No newline at end of file diff --git a/core/bindings/LibraryConfigWrapped.ts b/core/bindings/LibraryConfigWrapped.ts deleted file mode 100644 index ee5b5ccfe..000000000 --- a/core/bindings/LibraryConfigWrapped.ts +++ /dev/null @@ -1,4 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { LibraryConfig } from "./LibraryConfig"; - -export interface LibraryConfigWrapped { uuid: string, config: LibraryConfig, } \ No newline at end of file diff --git a/core/bindings/LibraryNode.ts b/core/bindings/LibraryNode.ts deleted file mode 100644 index f5c82dd96..000000000 --- a/core/bindings/LibraryNode.ts +++ /dev/null @@ -1,4 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { Platform } from "./Platform"; - -export interface LibraryNode { uuid: string, name: string, platform: Platform, last_seen: string, } \ No newline at end of file diff --git a/core/bindings/LibraryQuery.ts b/core/bindings/LibraryQuery.ts deleted file mode 100644 index d90a2fefc..000000000 --- a/core/bindings/LibraryQuery.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type LibraryQuery = { key: "GetJobHistory" } | { key: "GetLocations" } | { key: "GetLocation", params: { id: number, } } | { key: "GetRunningJobs" } | { key: "GetExplorerDir", params: { location_id: number, path: string, limit: number, } } | { key: "GetLibraryStatistics" } | { key: "GetTags" } | { key: "GetFilesTagged", params: { tag_id: number, } }; \ No newline at end of file diff --git a/core/bindings/LibraryState.ts b/core/bindings/LibraryState.ts deleted file mode 100644 index 9e77b1d4e..000000000 --- a/core/bindings/LibraryState.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export interface LibraryState { library_uuid: string, library_id: number, library_path: string, offline: boolean, } \ No newline at end of file diff --git a/core/bindings/LocationResource.ts b/core/bindings/LocationResource.ts deleted file mode 100644 index c9fbfa335..000000000 --- a/core/bindings/LocationResource.ts +++ /dev/null @@ -1,4 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { LibraryNode } from "./LibraryNode"; - -export interface LocationResource { id: number, name: string | null, path: string | null, total_capacity: number | null, available_capacity: number | null, is_removable: boolean | null, node: LibraryNode | null, is_online: boolean, date_created: string, } \ No newline at end of file diff --git a/core/bindings/NodeConfig.ts b/core/bindings/NodeConfig.ts deleted file mode 100644 index 512f0202c..000000000 --- a/core/bindings/NodeConfig.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export interface NodeConfig { version: string | null, id: string, name: string, p2p_port: number | null, } \ No newline at end of file diff --git a/core/bindings/NodeState.ts b/core/bindings/NodeState.ts deleted file mode 100644 index 978fb3103..000000000 --- a/core/bindings/NodeState.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string, } \ No newline at end of file diff --git a/core/bindings/Platform.ts b/core/bindings/Platform.ts deleted file mode 100644 index 30f10773a..000000000 --- a/core/bindings/Platform.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type Platform = "Unknown" | "Windows" | "MacOS" | "Linux" | "IOS" | "Android"; \ No newline at end of file diff --git a/core/bindings/Statistics.ts b/core/bindings/Statistics.ts deleted file mode 100644 index 503cd208c..000000000 --- a/core/bindings/Statistics.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export interface Statistics { total_file_count: number, total_bytes_used: string, total_bytes_capacity: string, total_bytes_free: string, total_unique_bytes: string, preview_media_bytes: string, library_db_size: string, } \ No newline at end of file diff --git a/core/bindings/Tag.ts b/core/bindings/Tag.ts deleted file mode 100644 index 60683d4b0..000000000 --- a/core/bindings/Tag.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export interface Tag { id: number, pub_id: string, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string, } \ No newline at end of file diff --git a/core/bindings/TagOnFile.ts b/core/bindings/TagOnFile.ts deleted file mode 100644 index 97723b51a..000000000 --- a/core/bindings/TagOnFile.ts +++ /dev/null @@ -1,5 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { File } from "./File"; -import type { Tag } from "./Tag"; - -export interface TagOnFile { tag_id: number, tag: Tag | null, file_id: number, file: File | null, date_created: string, } \ No newline at end of file diff --git a/core/bindings/TagWithFiles.ts b/core/bindings/TagWithFiles.ts deleted file mode 100644 index 6cf36acae..000000000 --- a/core/bindings/TagWithFiles.ts +++ /dev/null @@ -1,5 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { Tag } from "./Tag"; -import type { TagOnFile } from "./TagOnFile"; - -export interface TagWithFiles { tag: Tag, files_with_tag: Array, } \ No newline at end of file diff --git a/core/bindings/Volume.ts b/core/bindings/Volume.ts deleted file mode 100644 index e40b28748..000000000 --- a/core/bindings/Volume.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export interface Volume { name: string, mount_point: string, total_capacity: bigint, available_capacity: bigint, is_removable: boolean, disk_type: string | null, file_system: string | null, is_root_filesystem: boolean, } \ No newline at end of file diff --git a/core/index.ts b/core/index.ts index 22ef6909e..c70c78aea 100644 --- a/core/index.ts +++ b/core/index.ts @@ -1,30 +1,118 @@ -export * from './bindings/Client'; -export * from './bindings/ClientCommand'; -export * from './bindings/ClientQuery'; -export * from './bindings/ClientState'; -export * from './bindings/ConfigMetadata'; -export * from './bindings/CoreEvent'; -export * from './bindings/CoreResource'; -export * from './bindings/CoreResponse'; -export * from './bindings/DirectoryWithContents'; -export * from './bindings/EncryptionAlgorithm'; -export * from './bindings/File'; -export * from './bindings/FileKind'; -export * from './bindings/FilePath'; -export * from './bindings/JobReport'; -export * from './bindings/JobStatus'; -export * from './bindings/LibraryCommand'; -export * from './bindings/LibraryConfig'; -export * from './bindings/LibraryConfigWrapped'; -export * from './bindings/LibraryNode'; -export * from './bindings/LibraryQuery'; -export * from './bindings/LibraryState'; -export * from './bindings/LocationResource'; -export * from './bindings/NodeConfig'; -export * from './bindings/NodeState'; -export * from './bindings/Platform'; -export * from './bindings/Statistics'; -export * from './bindings/Tag'; -export * from './bindings/TagOnFile'; -export * from './bindings/TagWithFiles'; -export * from './bindings/Volume'; +/* eslint-disable */ +// This file was generated by [rspc](https://github.com/oscartbeaumont/rspc). Do not edit this file manually. + +export type Operations = { + queries: + { key: ["getNode"], result: NodeState } | + { key: ["version"], result: string } | + { key: ["tags.getFilesForTag", LibraryArgs], result: Tag | null } | + { key: ["locations.getById", LibraryArgs], result: Location | null } | + { key: ["files.readMetadata", LibraryArgs], result: null } | + { key: ["locations.get", LibraryArgs], result: Array } | + { key: ["jobs.getRunning", LibraryArgs], result: Array } | + { key: ["library.get"], result: Array } | + { key: ["locations.getExplorerDir", LibraryArgs], result: DirectoryWithContents } | + { key: ["tags.get", LibraryArgs], result: Array } | + { key: ["jobs.getHistory", LibraryArgs], result: Array } | + { key: ["library.getStatistics", LibraryArgs], result: Statistics } | + { key: ["volumes.get"], result: Array }, + mutations: + { key: ["files.setFavorite", LibraryArgs], result: null } | + { key: ["tags.assign", LibraryArgs], result: null } | + { key: ["locations.update", LibraryArgs], result: null } | + { key: ["locations.delete", LibraryArgs], result: null } | + { key: ["locations.fullRescan", LibraryArgs], result: null } | + { key: ["tags.delete", LibraryArgs], result: null } | + { key: ["jobs.identifyUniqueFiles", LibraryArgs], result: null } | + { key: ["library.delete", string], result: null } | + { key: ["files.delete", LibraryArgs], result: null } | + { key: ["locations.quickRescan", LibraryArgs], result: null } | + { key: ["locations.create", LibraryArgs], result: Location } | + { key: ["files.setNote", LibraryArgs], result: null } | + { key: ["library.edit", EditLibraryArgs], result: null } | + { key: ["tags.create", LibraryArgs], result: Tag } | + { key: ["library.create", string], result: null } | + { key: ["jobs.generateThumbsForLocation", LibraryArgs], result: null } | + { key: ["tags.update", LibraryArgs], result: null }, + subscriptions: + { key: ["jobs.newThumbnail", LibraryArgs], result: string } | + { key: ["invalidateQuery"], result: InvalidateOperationEvent } +}; + +export interface Tag { id: number, pub_id: Array, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string, tag_files: Array | null } + +export interface SetFavoriteArgs { id: number, favorite: boolean } + +export interface LibraryConfig { version: string | null, name: string, description: string } + +export interface FileInAlbum { date_created: string, album_id: number, album: Album | null, file_id: number, file: File | null } + +export interface Statistics { id: number, date_captured: string, total_file_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string } + +export interface GetExplorerDirArgs { location_id: number, path: string, limit: number } + +export interface JobReport { id: string, name: string, data: Array | null, date_created: string, date_modified: string, status: JobStatus, task_count: number, completed_task_count: number, message: string, seconds_elapsed: number } + +export interface Album { id: number, pub_id: Array, name: string, is_hidden: boolean, date_created: string, date_modified: string, files: Array | null } + +export interface LabelOnFile { date_created: string, label_id: number, label: Label | null, file_id: number, file: File | null } + +export interface Location { id: number, pub_id: Array, node_id: number | null, name: string | null, local_path: string | null, total_capacity: number | null, available_capacity: number | null, filesystem: string | null, disk_type: number | null, is_removable: boolean | null, is_online: boolean, date_created: string, node: Node | null | null, file_paths: Array | null } + +export interface Label { id: number, pub_id: Array, name: string | null, date_created: string, date_modified: string, label_files: Array | null } + +export interface InvalidateOperationEvent { key: string, arg: any } + +export interface LibraryArgs { library_id: string, arg: T } + +export interface MediaData { id: number, pixel_width: number | null, pixel_height: number | null, longitude: number | null, latitude: number | null, fps: number | null, capture_device_make: string | null, capture_device_model: string | null, capture_device_software: string | null, duration_seconds: number | null, codecs: string | null, streams: number | null, files: File | null | null } + +export interface GenerateThumbsForLocationArgs { id: number, path: string } + +export interface Volume { name: string, mount_point: string, total_capacity: bigint, available_capacity: bigint, is_removable: boolean, disk_type: string | null, file_system: string | null, is_root_filesystem: boolean } + +export interface DirectoryWithContents { directory: FilePath, contents: Array } + +export interface FileInSpace { date_created: string, space_id: number, space: Space | null, file_id: number, file: File | null } + +export interface TagOnFile { date_created: string, tag_id: number, tag: Tag | null, file_id: number, file: File | null } + +export interface File { id: number, cas_id: string, integrity_checksum: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, tags: Array | null, labels: Array | null, albums: Array | null, spaces: Array | null, paths: Array | null, comments: Array | null, media_data: MediaData | null | null, key: Key | null | null } + +export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string } + +export interface SetNoteArgs { id: number, note: string | null } + +export interface IdentifyUniqueFilesArgs { id: number, path: string } + +export interface Space { id: number, pub_id: Array, name: string | null, description: string | null, date_created: string, date_modified: string, files: Array | null } + +export interface LocationUpdateArgs { id: number, name: string | null } + +export interface Job { id: Array, name: string, node_id: number, action: number, status: number, data: Array | null, task_count: number, completed_task_count: number, date_created: string, date_modified: string, seconds_elapsed: number, nodes: Node | null } + +export interface TagAssignArgs { file_id: number, tag_id: number } + +export interface FilePath { id: number, is_dir: boolean, location_id: number | null, materialized_path: string, name: string, extension: string | null, file_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, file: File | null | null, location: Location | null | null, key: Key | null | null } + +export interface TagCreateArgs { name: string, color: string } + +export interface EditLibraryArgs { id: string, name: string | null, description: string | null } + +export interface TagUpdateArgs { id: number, name: string | null, color: string | null } + +export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused" + +export interface NodeConfig { version: string | null, id: string, name: string, p2p_port: number | null } + +export interface Comment { id: number, pub_id: Array, content: string, date_created: string, date_modified: string, file_id: number | null, file: File | null | null } + +export interface LibraryConfigWrapped { uuid: string, config: LibraryConfig } + +export interface SyncEvent { id: number, node_id: number, timestamp: string, record_id: Array, kind: number, column: string | null, value: string, node: Node | null } + +export interface ConfigMetadata { version: string | null } + +export interface Node { id: number, pub_id: Array, name: string, platform: number, version: string | null, last_seen: string, timezone: string | null, date_created: string, sync_events: Array | null, jobs: Array | null, Location: Array | null } + +export interface Key { id: number, checksum: string, name: string | null, date_created: string | null, algorithm: number | null, files: Array | null, file_paths: Array | null } diff --git a/core/package.json b/core/package.json index 433da6483..c05d412fe 100644 --- a/core/package.json +++ b/core/package.json @@ -4,15 +4,14 @@ "main": "index.js", "license": "GPL-3.0-only", "scripts": { - "codegen": "cargo test && ts-node ./scripts/bindingsIndex.ts", + "codegen": "cargo test", "build": "cargo build", "test": "cargo test", "test:log": "cargo test -- --nocapture", "prisma": "cargo prisma" }, "devDependencies": { - "@types/node": "^17.0.36", - "ts-node": "^10.8.0", - "typescript": "^4.7.2" + "@types/node": "^18.6.1", + "typescript": "^4.7.4" } } diff --git a/core/prisma/Cargo.toml b/core/prisma/Cargo.toml index 9ed9a2062..d9b60cd5a 100644 --- a/core/prisma/Cargo.toml +++ b/core/prisma/Cargo.toml @@ -4,4 +4,4 @@ version = "0.1.0" edition = "2021" [dependencies] -prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client-rust", tag = "0.5.0" } \ No newline at end of file +prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client-rust.git", branch = "0.6.0", features = ["rspc"] } diff --git a/core/src/api/files.rs b/core/src/api/files.rs new file mode 100644 index 000000000..bb33d9acd --- /dev/null +++ b/core/src/api/files.rs @@ -0,0 +1,105 @@ +use rspc::Type; +use serde::Deserialize; + +use crate::{api::locations::GetExplorerDirArgs, invalidate_query, prisma::file}; + +use super::{LibraryArgs, RouterBuilder}; + +#[derive(Type, Deserialize)] +pub struct SetNoteArgs { + pub id: i32, + pub note: Option, +} + +#[derive(Type, Deserialize)] +pub struct SetFavoriteArgs { + pub id: i32, + pub favorite: bool, +} + +pub(crate) fn mount() -> RouterBuilder { + ::new() + .query("readMetadata", |_ctx, _id: LibraryArgs| todo!()) + .mutation("setNote", |ctx, arg: LibraryArgs| async move { + let (args, library) = arg.get_library(&ctx).await?; + + library + .db + .file() + .find_unique(file::id::equals(args.id)) + .update(vec![file::note::set(args.note)]) + .exec() + .await?; + + invalidate_query!( + library, + "locations.getExplorerDir": LibraryArgs, + LibraryArgs { + library_id: library.id, + arg: GetExplorerDirArgs { + location_id: 0, // TODO: This should be the correct location_id + path: "".into(), + limit: 0, + } + } + ); + + Ok(()) + }) + .mutation( + "setFavorite", + |ctx, arg: LibraryArgs| async move { + let (args, library) = arg.get_library(&ctx).await?; + + library + .db + .file() + .find_unique(file::id::equals(args.id)) + .update(vec![file::favorite::set(args.favorite)]) + .exec() + .await?; + + invalidate_query!( + library, + "locations.getExplorerDir": LibraryArgs, + LibraryArgs { + library_id: library.id, + arg: GetExplorerDirArgs { + // TODO: Set these arguments to the correct type + location_id: 0, + path: "".into(), + limit: 0, + } + } + ); + + Ok(()) + }, + ) + .mutation("delete", |ctx, arg: LibraryArgs| async move { + let (id, library) = arg.get_library(&ctx).await?; + + library + .db + .file() + .find_unique(file::id::equals(id)) + .delete() + .exec() + .await?; + + invalidate_query!( + library, + "locations.getExplorerDir": LibraryArgs, + LibraryArgs { + library_id: library.id, + arg: GetExplorerDirArgs { + // TODO: Set these arguments to the correct type + location_id: 0, + path: "".into(), + limit: 0, + } + } + ); + Ok(()) + }) +} diff --git a/core/src/api/jobs.rs b/core/src/api/jobs.rs new file mode 100644 index 000000000..675a7a686 --- /dev/null +++ b/core/src/api/jobs.rs @@ -0,0 +1,88 @@ +use std::path::PathBuf; + +use rspc::Type; +use serde::Deserialize; + +use crate::{ + encode::{ThumbnailJob, ThumbnailJobInit}, + file::cas::{FileIdentifierJob, FileIdentifierJobInit}, + job::{Job, JobManager}, +}; + +use super::{CoreEvent, LibraryArgs, RouterBuilder}; + +#[derive(Type, Deserialize)] +pub struct GenerateThumbsForLocationArgs { + pub id: i32, + pub path: PathBuf, +} + +#[derive(Type, Deserialize)] +pub struct IdentifyUniqueFilesArgs { + pub id: i32, + pub path: PathBuf, +} + +pub(crate) fn mount() -> RouterBuilder { + ::new() + .query("getRunning", |ctx, arg: LibraryArgs<()>| async move { + let (_, _) = arg.get_library(&ctx).await?; + + Ok(ctx.jobs.get_running().await) + }) + .query("getHistory", |ctx, arg: LibraryArgs<()>| async move { + let (_, library) = arg.get_library(&ctx).await?; + + Ok(JobManager::get_history(&library).await?) + }) + .mutation( + "generateThumbsForLocation", + |ctx, arg: LibraryArgs| async move { + let (args, library) = arg.get_library(&ctx).await?; + + library + .spawn_job(Job::new( + ThumbnailJobInit { + location_id: args.id, + path: args.path, + background: false, // fix + }, + Box::new(ThumbnailJob {}), + )) + .await; + + Ok(()) + }, + ) + .mutation( + "identifyUniqueFiles", + |ctx, arg: LibraryArgs| async move { + let (args, library) = arg.get_library(&ctx).await?; + + library + .spawn_job(Job::new( + FileIdentifierJobInit { + location_id: args.id, + path: args.path, + }, + Box::new(FileIdentifierJob {}), + )) + .await; + + Ok(()) + }, + ) + .subscription("newThumbnail", |ctx, _: LibraryArgs<()>| { + // TODO: Only return event for the library that was subscribed to + + let mut event_bus_rx = ctx.event_bus.subscribe(); + async_stream::stream! { + while let Ok(event) = event_bus_rx.recv().await { + match event { + CoreEvent::NewThumbnail { cas_id } => yield cas_id, + _ => {} + } + } + } + }) +} diff --git a/core/src/api/libraries.rs b/core/src/api/libraries.rs new file mode 100644 index 000000000..f2135b1fa --- /dev/null +++ b/core/src/api/libraries.rs @@ -0,0 +1,101 @@ +use chrono::Utc; +use fs_extra::dir::get_size; +use rspc::Type; +use serde::Deserialize; +use tokio::fs; +use uuid::Uuid; + +use crate::{ + library::LibraryConfig, + prisma::statistics, + sys::{get_volumes, save_volume}, +}; + +use super::{LibraryArgs, RouterBuilder}; + +#[derive(Type, Deserialize)] +pub struct EditLibraryArgs { + pub id: Uuid, + pub name: Option, + pub description: Option, +} + +pub(crate) fn mount() -> RouterBuilder { + ::new() + .query("get", |ctx, _: ()| async move { + ctx.library_manager.get_all_libraries_config().await + }) + .query("getStatistics", |ctx, arg: LibraryArgs<()>| async move { + let (_, library) = arg.get_library(&ctx).await?; + + let _statistics = library + .db + .statistics() + .find_unique(statistics::id::equals(library.node_local_id)) + .exec() + .await?; + + // TODO: get from database, not sys + let volumes = get_volumes(); + save_volume(&library).await?; + + let mut available_capacity: u64 = 0; + let mut total_capacity: u64 = 0; + if volumes.is_ok() { + for volume in volumes? { + total_capacity += volume.total_capacity; + available_capacity += volume.available_capacity; + } + } + + let library_db_size = match fs::metadata(library.config().data_directory()).await { + Ok(metadata) => metadata.len(), + Err(_) => 0, + }; + + let thumbnail_folder_size = + get_size(library.config().data_directory().join("thumbnails")); + + use statistics::*; + let params = vec![ + id::set(1), // Each library is a database so only one of these ever exists + date_captured::set(Utc::now().into()), + total_file_count::set(0), + library_db_size::set(library_db_size.to_string()), + total_bytes_used::set(0.to_string()), + total_bytes_capacity::set(total_capacity.to_string()), + total_unique_bytes::set(0.to_string()), + total_bytes_free::set(available_capacity.to_string()), + preview_media_bytes::set(thumbnail_folder_size.unwrap_or(0).to_string()), + ]; + + Ok(library + .db + .statistics() + .upsert( + statistics::id::equals(1), // Each library is a database so only one of these ever exists + params.clone(), + params, + ) + .exec() + .await?) + }) + .mutation("create", |ctx, name: String| async move { + Ok(ctx + .library_manager + .create(LibraryConfig { + name: name.to_string(), + ..Default::default() + }) + .await?) + }) + .mutation("edit", |ctx, args: EditLibraryArgs| async move { + Ok(ctx + .library_manager + .edit(args.id, args.name, args.description) + .await?) + }) + .mutation("delete", |ctx, id: Uuid| async move { + Ok(ctx.library_manager.delete_library(id).await?) + }) +} diff --git a/core/src/api/locations.rs b/core/src/api/locations.rs new file mode 100644 index 000000000..666afcc48 --- /dev/null +++ b/core/src/api/locations.rs @@ -0,0 +1,184 @@ +use std::path::PathBuf; + +use rspc::{Error, ErrorCode, Type}; +use serde::{Deserialize, Serialize}; +use tracing::info; + +use crate::{ + encode::THUMBNAIL_CACHE_DIR_NAME, + invalidate_query, + prisma::{file_path, location}, + sys::{self, create_location, scan_location}, +}; + +use super::{LibraryArgs, RouterBuilder}; + +#[derive(Serialize, Deserialize, Type, Debug)] +pub struct DirectoryWithContents { + pub directory: file_path::Data, + pub contents: Vec, +} + +#[derive(Type, Deserialize)] +pub struct LocationUpdateArgs { + pub id: i32, + pub name: Option, +} + +#[derive(Clone, Serialize, Deserialize, Type)] +pub struct GetExplorerDirArgs { + pub location_id: i32, + pub path: String, + pub limit: i32, +} + +pub(crate) fn mount() -> RouterBuilder { + ::new() + .query("get", |ctx, arg: LibraryArgs<()>| async move { + let (_, library) = arg.get_library(&ctx).await?; + + let locations = library + .db + .location() + .find_many(vec![]) + .with(location::node::fetch()) + .exec() + .await?; + + Ok(locations) + }) + .query("getById", |ctx, arg: LibraryArgs| async move { + let (location_id, library) = arg.get_library(&ctx).await?; + + Ok(library + .db + .location() + .find_unique(location::id::equals(location_id)) + .exec() + .await?) + }) + .query( + "getExplorerDir", + |ctx, arg: LibraryArgs| async move { + let (args, library) = arg.get_library(&ctx).await?; + + let location = library + .db + .location() + .find_unique(location::id::equals(args.location_id)) + .exec() + .await? + .unwrap(); + + let directory = library + .db + .file_path() + .find_first(vec![ + file_path::location_id::equals(Some(location.id)), + file_path::materialized_path::equals(args.path), + file_path::is_dir::equals(true), + ]) + .exec() + .await? + .ok_or_else(|| Error::new(ErrorCode::NotFound, "Directory not found".into()))?; + + let file_paths = library + .db + .file_path() + .find_many(vec![ + file_path::location_id::equals(Some(location.id)), + file_path::parent_id::equals(Some(directory.id)), + ]) + .with(file_path::file::fetch()) + .exec() + .await?; + + Ok(DirectoryWithContents { + directory, + contents: file_paths + .into_iter() + .map(|mut file_path| { + if let Some(file) = &mut file_path.file.as_mut().unwrap_or_else( + || /* Prisma relationship was not fetched */ unreachable!(), + ) { + // TODO: Use helper function to build this url as as the Rust file loading layer + let thumb_path = library + .config() + .data_directory() + .join(THUMBNAIL_CACHE_DIR_NAME) + .join(location.id.to_string()) + .join(&file.cas_id) + .with_extension("webp"); + + file.has_thumbnail = thumb_path.exists(); + } + + file_path + }) + .collect(), + }) + }, + ) + .mutation("create", |ctx, arg: LibraryArgs| async move { + let (path, library) = arg.get_library(&ctx).await?; + let location = create_location(&library, &path).await?; + scan_location(&library, location.id, path).await; + Ok(location) + }) + .mutation( + "update", + |ctx, arg: LibraryArgs| async move { + let (args, library) = arg.get_library(&ctx).await?; + + library + .db + .location() + .find_unique(location::id::equals(args.id)) + .update(vec![location::name::set(args.name)]) + .exec() + .await?; + + Ok(()) + }, + ) + .mutation("delete", |ctx, arg: LibraryArgs| async move { + let (location_id, library) = arg.get_library(&ctx).await?; + + library + .db + .file_path() + .find_many(vec![file_path::location_id::equals(Some(location_id))]) + .delete() + .exec() + .await?; + + library + .db + .location() + .find_unique(location::id::equals(location_id)) + .delete() + .exec() + .await?; + + invalidate_query!( + library, + "locations.get": LibraryArgs<()>, + LibraryArgs { + library_id: library.id, + arg: () + } + ); + + info!("Location {} deleted", location_id); + + Ok(()) + }) + .mutation("fullRescan", |ctx, arg: LibraryArgs| async move { + let (id, library) = arg.get_library(&ctx).await?; + + sys::scan_location(&library, id, String::new()).await; + + Ok(()) + }) + .mutation("quickRescan", |_, _: LibraryArgs<()>| todo!()) +} diff --git a/core/src/api/mod.rs b/core/src/api/mod.rs new file mode 100644 index 000000000..ad1c649f1 --- /dev/null +++ b/core/src/api/mod.rs @@ -0,0 +1,137 @@ +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; + +use rspc::{Config, ErrorCode, Type}; +use serde::{Deserialize, Serialize}; +use tokio::sync::broadcast; +use uuid::Uuid; + +use crate::{ + job::JobManager, + library::{LibraryContext, LibraryManager}, + node::{NodeConfig, NodeConfigManager}, +}; + +use utils::{InvalidRequests, InvalidateOperationEvent}; + +pub(crate) type Router = rspc::Router; +pub(crate) type RouterBuilder = rspc::RouterBuilder; + +/// Represents an internal core event, these are exposed to client via a rspc subscription. +#[derive(Debug, Clone, Serialize, Type)] +pub enum CoreEvent { + NewThumbnail { cas_id: String }, + InvalidateOperation(InvalidateOperationEvent), + InvalidateOperationDebounced(InvalidateOperationEvent), +} + +/// Is provided when executing the router from the request. +pub struct Ctx { + pub library_manager: Arc, + pub config: Arc, + pub jobs: Arc, + pub event_bus: broadcast::Sender, +} + +/// Can wrap a query argument to require it to contain a `library_id` and provide helpers for working with libraries. +#[derive(Clone, Serialize, Deserialize, Type)] +pub struct LibraryArgs { + // If you want to make these public, your doing it wrong. + pub library_id: Uuid, + pub arg: T, +} + +impl LibraryArgs { + pub async fn get_library(self, ctx: &Ctx) -> Result<(T, LibraryContext), rspc::Error> { + match ctx.library_manager.get_ctx(self.library_id).await { + Some(library) => Ok((self.arg, library)), + None => Err(rspc::Error::new( + ErrorCode::BadRequest, + "You must specify a valid library to use this operation.".to_string(), + )), + } + } +} + +mod files; +mod jobs; +mod libraries; +mod locations; +mod tags; +pub mod utils; +mod volumes; + +pub use files::*; +pub use jobs::*; +pub use libraries::*; +pub use tags::*; + +#[derive(Serialize, Deserialize, Debug, Type)] +struct NodeState { + #[serde(flatten)] + config: NodeConfig, + data_path: String, +} + +pub(crate) fn mount() -> Arc { + let r = ::new() + .config( + Config::new() + // TODO: This messes with Tauri's hot reload so we can't use it until their is a solution upstream. https://github.com/tauri-apps/tauri/issues/4617 + // .export_ts_bindings(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("./index.ts")), + .set_ts_bindings_header("/* eslint-disable */"), + ) + .query("version", |_, _: ()| env!("CARGO_PKG_VERSION")) + .query("getNode", |ctx, _: ()| async move { + Ok(NodeState { + config: ctx.config.get().await, + // We are taking the assumption here that this value is only used on the frontend for display purposes + data_path: ctx.config.data_directory().to_string_lossy().into_owned(), + }) + }) + .merge("library.", libraries::mount()) + .merge("volumes.", volumes::mount()) + .merge("tags.", tags::mount()) + .merge("locations.", locations::mount()) + .merge("files.", files::mount()) + .merge("jobs.", jobs::mount()) + // TODO: Scope the invalidate queries to a specific library (filtered server side) + .subscription("invalidateQuery", |ctx, _: ()| { + let mut event_bus_rx = ctx.event_bus.subscribe(); + let mut last = Instant::now(); + async_stream::stream! { + while let Ok(event) = event_bus_rx.recv().await { + match event { + CoreEvent::InvalidateOperation(op) => yield op, + CoreEvent::InvalidateOperationDebounced(op) => { + let current = Instant::now(); + if current.duration_since(last) > Duration::from_millis(1000 / 60) { + last = current; + yield op; + } + }, + _ => {} + } + } + } + }) + .build() + .arced(); + InvalidRequests::validate(r.clone()); // This validates all invalidation calls. + r +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + /// This test will ensure the rspc router and all calls to `invalidate_query` are valid and also export an updated version of the Typescript bindings. + #[test] + fn test_and_export_rspc_bindings() { + let r = super::mount(); + r.export_ts(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("./index.ts")) + .expect("Error exporting rspc Typescript bindings!"); + } +} diff --git a/core/src/api/tags.rs b/core/src/api/tags.rs new file mode 100644 index 000000000..c8a75bc9d --- /dev/null +++ b/core/src/api/tags.rs @@ -0,0 +1,139 @@ +use rspc::Type; +use serde::Deserialize; +use uuid::Uuid; + +use crate::{ + invalidate_query, + prisma::{file, tag}, +}; + +use super::{LibraryArgs, RouterBuilder}; + +#[derive(Type, Deserialize)] +pub struct TagCreateArgs { + pub name: String, + pub color: String, +} + +#[derive(Type, Deserialize)] +pub struct TagAssignArgs { + pub file_id: i32, + pub tag_id: i32, +} + +#[derive(Type, Deserialize)] +pub struct TagUpdateArgs { + pub id: i32, + pub name: Option, + pub color: Option, +} + +pub(crate) fn mount() -> RouterBuilder { + RouterBuilder::new() + .query("get", |ctx, arg: LibraryArgs<()>| async move { + let (_, library) = arg.get_library(&ctx).await?; + + Ok(library.db.tag().find_many(vec![]).exec().await?) + }) + .query("getFilesForTag", |ctx, arg: LibraryArgs| async move { + let (tag_id, library) = arg.get_library(&ctx).await?; + + Ok(library + .db + .tag() + .find_unique(tag::id::equals(tag_id)) + .exec() + .await?) + }) + .mutation( + "create", + |ctx, arg: LibraryArgs| async move { + let (args, library) = arg.get_library(&ctx).await?; + + let created_tag = library + .db + .tag() + .create( + Uuid::new_v4().as_bytes().to_vec(), + vec![ + tag::name::set(Some(args.name)), + tag::color::set(Some(args.color)), + ], + ) + .exec() + .await?; + + invalidate_query!( + library, + "tags.get": LibraryArgs<()>, + LibraryArgs { + library_id: library.id, + arg: () + } + ); + + Ok(created_tag) + }, + ) + .mutation( + "assign", + |ctx, arg: LibraryArgs| async move { + let (args, library) = arg.get_library(&ctx).await?; + + library.db.tag_on_file().create( + tag::id::equals(args.tag_id), + file::id::equals(args.file_id), + vec![], + ); + + Ok(()) + }, + ) + .mutation( + "update", + |ctx, arg: LibraryArgs| async move { + let (args, library) = arg.get_library(&ctx).await?; + + library + .db + .tag() + .find_unique(tag::id::equals(args.id)) + .update(vec![tag::name::set(args.name), tag::color::set(args.color)]) + .exec() + .await?; + + invalidate_query!( + library, + "tags.get": LibraryArgs<()>, + LibraryArgs { + library_id: library.id, + arg: () + } + ); + + Ok(()) + }, + ) + .mutation("delete", |ctx, arg: LibraryArgs| async move { + let (id, library) = arg.get_library(&ctx).await?; + + library + .db + .tag() + .find_unique(tag::id::equals(id)) + .delete() + .exec() + .await?; + + invalidate_query!( + library, + "tags.get": LibraryArgs<()>, + LibraryArgs { + library_id: library.id, + arg: () + } + ); + + Ok(()) + }) +} diff --git a/core/src/api/utils/invalidate.rs b/core/src/api/utils/invalidate.rs new file mode 100644 index 000000000..a8b491c15 --- /dev/null +++ b/core/src/api/utils/invalidate.rs @@ -0,0 +1,119 @@ +use std::sync::{Arc, Mutex}; + +use once_cell::sync::OnceCell; +use rspc::{internal::specta::DataType, Type}; +use serde::Serialize; +use serde_json::Value; + +use crate::api::Router; + +/// holds information about all invalidation queries done with the [invalidate_query!] macro so we can check they are valid when building the router. +#[cfg(debug_assertions)] +pub(crate) static INVALIDATION_REQUESTS: OnceCell> = OnceCell::new(); + +#[derive(Debug, Clone, Serialize, Type)] +pub struct InvalidateOperationEvent { + /// This fields are intentionally private. + key: &'static str, + arg: Value, +} + +impl InvalidateOperationEvent { + /// If you are using this function, your doing it wrong. + pub fn dangerously_create(key: &'static str, arg: Value) -> Self { + Self { key, arg } + } +} + +/// a request to invalidate a specific resource +#[derive(Debug)] +pub(crate) struct InvalidationRequest { + pub key: &'static str, + pub arg_ty: DataType, + pub macro_src: &'static str, +} + +/// invalidation request for a specific resource +#[derive(Debug, Default)] +pub(crate) struct InvalidRequests { + pub queries: Vec, +} + +impl InvalidRequests { + #[allow(unused_variables)] + pub(crate) fn validate(r: Arc) { + #[cfg(debug_assertions)] + { + let invalidate_requests = crate::api::utils::INVALIDATION_REQUESTS + .get_or_init(Default::default) + .lock() + .unwrap(); + + let queries = r.queries(); + for req in &invalidate_requests.queries { + if let Some(query_ty) = queries.get(req.key) { + if query_ty.ty.arg_ty != req.arg_ty { + panic!( + "Error at '{}': Attempted to invalid query '{}' but the argument type does not match the type defined on the router.", + req.macro_src, req.key + ); + } + } else { + panic!( + "Error at '{}': Attempted to invalid query '{}' which was not found in the router", + req.macro_src, req.key + ); + } + } + } + } +} + +/// invalidate_query is a macro which stores a list of all of it's invocations so it can ensure all of the queries match the queries attached to the router. +/// This allows invalidate to the type safe even when the router keys are stringly typed. +/// ```ignore +/// invalidate_query!( +/// library, // crate::library::LibraryContext +/// "version": (), // Name of the query and the type of it +/// () // The arguments +/// ); +/// ``` +#[macro_export] +#[allow(clippy::crate_in_macro_def)] +macro_rules! invalidate_query { + ($ctx:expr, $key:literal: $arg_ty:ty, $arg:expr) => {{ + let _: $arg_ty = $arg; // Assert the type the user provided is correct + let ctx: &crate::library::LibraryContext = &$ctx; // Assert the context is the correct type + + #[cfg(debug_assertions)] + { + #[ctor::ctor] + fn invalidate() { + crate::api::utils::INVALIDATION_REQUESTS + .get_or_init(|| Default::default()) + .lock() + .unwrap() + .queries + .push(crate::api::utils::InvalidationRequest { + key: $key, + arg_ty: <$arg_ty as rspc::internal::specta::Type>::reference(rspc::internal::specta::DefOpts { + parent_inline: false, + type_map: &mut rspc::internal::specta::TypeDefs::new(), + }, &[]), + macro_src: concat!(file!(), ":", line!()), + }) + } + } + + // The error are ignored here because they aren't mission critical. If they fail the UI might be outdated for a bit. + let _ = serde_json::to_value($arg) + .map(|v| + ctx.emit(crate::api::CoreEvent::InvalidateOperation( + crate::api::utils::InvalidateOperationEvent::dangerously_create($key, v), + )) + ) + .map_err(|_| { + tracing::warn!("Failed to serialize invalidate query event!"); + }); + }}; +} diff --git a/core/src/api/utils/mod.rs b/core/src/api/utils/mod.rs new file mode 100644 index 000000000..bd98fe854 --- /dev/null +++ b/core/src/api/utils/mod.rs @@ -0,0 +1,3 @@ +mod invalidate; + +pub use invalidate::*; diff --git a/core/src/api/volumes.rs b/core/src/api/volumes.rs new file mode 100644 index 000000000..63b8bb742 --- /dev/null +++ b/core/src/api/volumes.rs @@ -0,0 +1,7 @@ +use crate::sys::get_volumes; + +use super::{Router, RouterBuilder}; + +pub(crate) fn mount() -> RouterBuilder { + ::new().query("get", |_, _: ()| Ok(get_volumes()?)) +} diff --git a/core/src/encode/thumb.rs b/core/src/encode/thumb.rs index bde57e343..6d47e009f 100644 --- a/core/src/encode/thumb.rs +++ b/core/src/encode/thumb.rs @@ -1,11 +1,10 @@ use crate::{ + api::CoreEvent, job::{JobError, JobReportUpdate, JobResult, JobState, StatefulJob, WorkerContext}, library::LibraryContext, - prisma::file_path, - sys, CoreEvent, + prisma::{file_path, location}, }; use image::{self, imageops, DynamicImage, GenericImageView}; -use log::{error, info, trace}; use serde::{Deserialize, Serialize}; use std::{ error::Error, @@ -13,6 +12,7 @@ use std::{ path::{Path, PathBuf}, }; use tokio::{fs, task::block_in_place}; +use tracing::{error, info, trace, warn}; use webp::Encoder; static THUMBNAIL_SIZE_FACTOR: f32 = 0.2; @@ -57,7 +57,13 @@ impl StatefulJob for ThumbnailJob { .join(THUMBNAIL_CACHE_DIR_NAME) .join(state.init.location_id.to_string()); - let location = sys::get_location(&library_ctx, state.init.location_id).await?; + let location = library_ctx + .db + .location() + .find_unique(location::id::equals(state.init.location_id)) + .exec() + .await? + .unwrap(); info!( "Searching for images in location {} at path {:#?}", @@ -66,7 +72,7 @@ impl StatefulJob for ThumbnailJob { // create all necessary directories if they don't exist fs::create_dir_all(&thumbnail_dir).await?; - let root_path = location.path.unwrap(); + let root_path = location.local_path.map(PathBuf::from).unwrap(); // query database for all files in this location that need thumbnails let image_files = @@ -113,7 +119,7 @@ impl StatefulJob for ThumbnailJob { if let Some(f) = file { f.cas_id.clone() } else { - info!( + warn!( "skipping thumbnail generation for {}", step.materialized_path ); @@ -138,9 +144,7 @@ impl StatefulJob for ThumbnailJob { } if !state.init.background { - ctx.library_ctx() - .emit(CoreEvent::NewThumbnail { cas_id }) - .await; + ctx.library_ctx().emit(CoreEvent::NewThumbnail { cas_id }); }; } else { info!("Thumb exists, skipping... {}", output_path.display()); @@ -219,7 +223,7 @@ pub async fn get_images( ]), ]; - let path_str = path.as_ref().to_string_lossy().to_string(); + let path_str = path.as_ref().as_os_str().to_str().unwrap().to_string(); if !path_str.is_empty() { params.push(file_path::materialized_path::starts_with(path_str)) diff --git a/core/src/file/cas/identifier.rs b/core/src/file/cas/identifier.rs index c8bef854b..8cbe5bbcc 100644 --- a/core/src/file/cas/identifier.rs +++ b/core/src/file/cas/identifier.rs @@ -1,15 +1,11 @@ use super::checksum::generate_cas_id; use crate::{ - file::FileError, job::{JobError, JobReportUpdate, JobResult, JobState, StatefulJob, WorkerContext}, library::LibraryContext, - prisma::{file, file_path}, - sys::get_location, - sys::LocationResource, + prisma::{self, file, file_path, location}, }; use chrono::{DateTime, FixedOffset}; -use log::{error, info}; use prisma_client_rust::{prisma_models::PrismaValue, raw, raw::Raw, Direction}; use serde::{Deserialize, Serialize}; use std::{ @@ -17,6 +13,7 @@ use std::{ path::{Path, PathBuf}, }; use tokio::{fs, io}; +use tracing::{error, info}; // we break this job into chunks of 100 to improve performance static CHUNK_SIZE: usize = 100; @@ -37,7 +34,7 @@ pub struct FileIdentifierJobInit { pub struct FileIdentifierJobState { total_count: usize, task_count: usize, - location: LocationResource, + location: location::Data, location_path: PathBuf, cursor: i32, } @@ -59,14 +56,23 @@ impl StatefulJob for FileIdentifierJob { ) -> JobResult { info!("Identifying orphan file paths..."); - let location = get_location(&ctx.library_ctx(), state.init.location_id).await?; - let location_path = if let Some(ref path) = location.path { - path.clone() - } else { - PathBuf::new() - }; + let library = ctx.library_ctx(); - let total_count = count_orphan_file_paths(&ctx.library_ctx(), location.id.into()).await?; + let location = library + .db + .location() + .find_unique(location::id::equals(state.init.location_id)) + .exec() + .await? + .unwrap(); + + let location_path = location + .local_path + .as_ref() + .map(PathBuf::from) + .unwrap_or_default(); + + let total_count = count_orphan_file_paths(&library, location.id.into()).await?; info!("Found {} orphan file paths", total_count); let task_count = (total_count as f64 / CHUNK_SIZE as f64).ceil() as usize; @@ -149,16 +155,17 @@ impl StatefulJob for FileIdentifierJob { // link those existing files to their file paths // Had to put the file_path in a variable outside of the closure, to satisfy the borrow checker let library_ctx = ctx.library_ctx(); - let prisma_file_path = library_ctx.db.file_path(); for existing_file in &existing_files { - if let Err(e) = update_file_id_by_cas_id( - &prisma_file_path, - &cas_lookup, - &existing_file.cas_id, - existing_file.id, - ) - .await + if let Err(e) = library_ctx + .db + .file_path() + .find_unique(file_path::id::equals( + *cas_lookup.get(&existing_file.cas_id).unwrap(), + )) + .update(vec![file_path::file_id::set(Some(existing_file.id))]) + .exec() + .await { info!("Error updating file_id: {:#?}", e); } @@ -208,13 +215,16 @@ impl StatefulJob for FileIdentifierJob { // associate newly created files with their respective file_paths // TODO: this is potentially bottle necking the chunk system, individually linking file_path to file, 100 queries per chunk // - insert many could work, but I couldn't find a good way to do this in a single SQL query - if let Err(e) = update_file_id_by_cas_id( - &prisma_file_path, - &cas_lookup, - &created_file.cas_id, - created_file.id, - ) - .await + if let Err(e) = ctx + .library_ctx() + .db + .file_path() + .find_unique(file_path::id::equals( + *cas_lookup.get(&created_file.cas_id).unwrap(), + )) + .update(vec![file_path::file_id::set(Some(created_file.id))]) + .exec() + .await { info!("Error updating file_id: {:#?}", e); } @@ -267,7 +277,7 @@ struct CountRes { pub async fn count_orphan_file_paths( ctx: &LibraryContext, location_id: i64, -) -> Result { +) -> Result { let files_count = ctx.db ._query_raw::(raw!( "SELECT COUNT(*) AS count FROM file_paths WHERE file_id IS NULL AND is_dir IS FALSE AND location_id = {}", @@ -280,7 +290,7 @@ pub async fn count_orphan_file_paths( pub async fn get_orphan_file_paths( ctx: &LibraryContext, cursor: i32, -) -> Result, FileError> { +) -> Result, prisma::QueryError> { info!( "discovering {} orphan file paths at cursor: {:?}", CHUNK_SIZE, cursor @@ -296,7 +306,6 @@ pub async fn get_orphan_file_paths( .take(CHUNK_SIZE as i64) .exec() .await - .map_err(Into::into) } #[derive(Deserialize, Serialize, Debug)] @@ -342,16 +351,3 @@ pub async fn prepare_file( date_created: file_path.date_created, }) } - -async fn update_file_id_by_cas_id( - prisma_file_path: &file_path::Actions<'_>, - cas_lookup: &HashMap, - file_cas_id: &str, - file_id: i32, -) -> prisma_client_rust::Result> { - prisma_file_path - .find_unique(file_path::id::equals(*cas_lookup.get(file_cas_id).unwrap())) - .update(vec![file_path::file_id::set(Some(file_id))]) - .exec() - .await -} diff --git a/core/src/file/explorer/mod.rs b/core/src/file/explorer/mod.rs deleted file mode 100644 index dd44d4a91..000000000 --- a/core/src/file/explorer/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod open; - -pub use open::*; diff --git a/core/src/file/indexer/mod.rs b/core/src/file/indexer.rs similarity index 97% rename from core/src/file/indexer/mod.rs rename to core/src/file/indexer.rs index 46969ad94..42a41a2bf 100644 --- a/core/src/file/indexer/mod.rs +++ b/core/src/file/indexer.rs @@ -1,9 +1,9 @@ use crate::{ job::{JobReportUpdate, JobResult, JobState, StatefulJob, WorkerContext}, - sys::{create_location, LocationResource}, + prisma::location, + sys::create_location, }; use chrono::{DateTime, Utc}; -use log::{error, info}; use prisma_client_rust::{raw, raw::Raw, PrismaValue}; use serde::{Deserialize, Serialize}; use std::{ @@ -13,6 +13,7 @@ use std::{ time::Duration, }; use tokio::{fs, time::Instant}; +use tracing::{error, info}; use walkdir::{DirEntry, WalkDir}; static BATCH_SIZE: usize = 100; @@ -34,7 +35,7 @@ pub struct IndexerJobInit { #[derive(Serialize, Deserialize)] pub struct IndexerJobData { - location: LocationResource, + location: location::Data, db_write_start: DateTime, scan_read_time: Duration, total_paths: usize, @@ -286,14 +287,14 @@ impl StatefulJob for IndexerJob { async fn prepare_values( file_path: impl AsRef, id: i32, - location: &LocationResource, + location: &location::Data, parent_id: &Option, is_dir: bool, ) -> Result<[PrismaValue; 8], std::io::Error> { let file_path = file_path.as_ref(); let metadata = fs::metadata(file_path).await?; - let location_path = location.path.as_ref().unwrap(); + let location_path = location.local_path.as_ref().map(PathBuf::from).unwrap(); // let size = metadata.len(); let name; let extension; diff --git a/core/src/file/mod.rs b/core/src/file/mod.rs index a642eb7c8..fc2fd30f5 100644 --- a/core/src/file/mod.rs +++ b/core/src/file/mod.rs @@ -1,201 +1,2 @@ -use crate::{ - library::LibraryContext, - prisma::{self, file, file_path}, - sys::SysError, - ClientQuery, CoreError, CoreEvent, CoreResponse, LibraryQuery, -}; - -use chrono::{DateTime, Utc}; -use int_enum::IntEnum; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; -use thiserror::Error; -use ts_rs::TS; - pub mod cas; -pub mod explorer; pub mod indexer; - -// A unique file -#[derive(Debug, Clone, Serialize, Deserialize, TS)] -#[ts(export)] -pub struct File { - pub id: i32, - pub cas_id: String, - pub integrity_checksum: Option, - pub size_in_bytes: String, - pub kind: FileKind, - - pub hidden: bool, - pub favorite: bool, - pub important: bool, - pub has_thumbnail: bool, - pub has_thumbstrip: bool, - pub has_video_preview: bool, - // pub encryption: EncryptionAlgorithm, - pub ipfs_id: Option, - pub note: Option, - - pub date_created: DateTime, - pub date_modified: DateTime, - pub date_indexed: DateTime, - - pub paths: Vec, - // pub media_data: Option, - // pub tags: Vec, - // pub label: Vec