diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..3550a30f --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..a3763810 --- /dev/null +++ b/flake.lock @@ -0,0 +1,81 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1726560853, + "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1720535198, + "narHash": "sha256-zwVvxrdIzralnSbcpghA92tWu2DV2lwv89xZc8MTrbg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "205fd4226592cc83fd4c0885a3e4c9c400efabb5", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-23.11", + "type": "indirect" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1727144949, + "narHash": "sha256-uMZMjoCS2nf40TAE1686SJl3OXWfdfM+BDEfRdr+uLc=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "2e19799819104b46019d339e78d21c14372d3666", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..abe896f9 --- /dev/null +++ b/flake.nix @@ -0,0 +1,88 @@ +{ + description = "tauri-todos"; + + inputs = { + nixpkgs.url = "nixpkgs/nixos-23.11"; + # fixes https://github.com/NixOS/nixpkgs/issues/298285 + # using nixpkgs from that branch until it's merged + # nixpkgs-androidenv.url = + # "github:hadilq/nixpkgs/androidenv-fix-ndk-toolchains"; + flake-utils.url = "github:numtide/flake-utils"; + + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.flake-utils.follows = "flake-utils"; + }; + }; + + outputs = { self, nixpkgs, flake-utils, rust-overlay, }: + flake-utils.lib.eachDefaultSystem (system: + let + overlays = [ (import rust-overlay) ]; + pkgs = import nixpkgs { + inherit system overlays; + # config.android_sdk.accept_license = true; + config.allowUnfree = true; + }; + + # androidenvPkgs = import nixpkgs-androidenv { + # inherit system overlays; + # config.android_sdk.accept_license = true; + # config.allowUnfree = true; + # }; + + nightly-rustfmt = pkgs.rust-bin.nightly.latest.rustfmt; + + # androidComposition = androidenvPkgs.androidenv.composeAndroidPackages { + # platformVersions = [ "33" "32" ]; + # buildToolsVersions = [ "30.0.3" ]; + # includeEmulator = false; # haven't figured it out yet... + # includeNDK = true; + # # may need to wait for https://github.com/NixOS/nixpkgs/pull/300386 to land + # ndkVersion = "26.1.10909125"; + # }; + in { + devShells.default = pkgs.mkShell rec { + name = "tauri-todos"; + nativeBuildInputs = with pkgs; [ + nightly-rustfmt + direnv + corepack # includes pnpm + pkg-config + # c libraries needed for tauri on linux desktop + openssl + glib.dev + pango.dev + libsoup.dev + webkitgtk.dev + # needed for rust android compilation (pnpm tauri android dev) + # llvmPackages_13.libcxx + # libxml2 + # jdk17 + # android development tools + # androidComposition.androidsdk + # ] ++ lib.optionals stdenv.isDarwin [ + # darwin.apple_sdk.frameworks.Security + # darwin.apple_sdk.frameworks.CoreFoundation + # darwin.apple_sdk.frameworks.Foundation + ]; + + # env variables so tauri picks up our android sdk install + # ANDROID_SDK_ROOT = + # "${androidComposition.androidsdk}/libexec/android-sdk"; + # ANDROID_NDK_ROOT = "${ANDROID_SDK_ROOT}/ndk-bundle"; + # ANDROID_HOME = "${ANDROID_SDK_ROOT}"; + # NDK_HOME = "${ANDROID_NDK_ROOT}"; + + # For some reason that's needed for the android NDK's clang setup to work + # LD_LIBRARY_PATH = "${pkgs.libxml2.out}/lib"; + + # Needed for `tauri android dev` to pick up the jdk + # JAVA_HOME = "${pkgs.jdk17}/lib/openjdk"; + + # Fixes an empty window bug for me https://github.com/tauri-apps/tauri/issues/8254 + WEBKIT_DISABLE_COMPOSITING_MODE = 1; + }; + }); +} diff --git a/tauri-todos/.gitignore b/tauri-todos/.gitignore index d10566fd..3b5b9df6 100644 --- a/tauri-todos/.gitignore +++ b/tauri-todos/.gitignore @@ -13,6 +13,8 @@ dist dist-ssr *.local +.data-dir* + # Editor directories and files .vscode/* !.vscode/extensions.json diff --git a/tauri-todos/src-tauri/.test-sth/blobs/blobs.db b/tauri-todos/src-tauri/.test-sth/blobs/blobs.db new file mode 100644 index 00000000..0b9e386d Binary files /dev/null and b/tauri-todos/src-tauri/.test-sth/blobs/blobs.db differ diff --git a/tauri-todos/src-tauri/.test-sth/default-author b/tauri-todos/src-tauri/.test-sth/default-author new file mode 100644 index 00000000..d7262da0 --- /dev/null +++ b/tauri-todos/src-tauri/.test-sth/default-author @@ -0,0 +1 @@ +5fu4waoqtgdihnnhyywcvuejknfhh2srkwqett5ryyxbreanwfdq \ No newline at end of file diff --git a/tauri-todos/src-tauri/.test-sth/docs.redb b/tauri-todos/src-tauri/.test-sth/docs.redb new file mode 100644 index 00000000..693308ba Binary files /dev/null and b/tauri-todos/src-tauri/.test-sth/docs.redb differ diff --git a/tauri-todos/src-tauri/.test-sth/keypair b/tauri-todos/src-tauri/.test-sth/keypair new file mode 100644 index 00000000..7c1897f6 --- /dev/null +++ b/tauri-todos/src-tauri/.test-sth/keypair @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDg2+xLBDmcbLBE0NJyhBGHrMG5IhoPlYHRtrNB8W1JgAAAAIg9Eb6/PRG+ +vwAAAAtzc2gtZWQyNTUxOQAAACDg2+xLBDmcbLBE0NJyhBGHrMG5IhoPlYHRtrNB8W1JgA +AAAEAIWZEjmnwAUz5iAgE5zH6GOh8iX0nuj4MsL88B90QxleDb7EsEOZxssETQ0nKEEYes +wbkiGg+VgdG2s0HxbUmAAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/tauri-todos/src-tauri/.test-sth/peers.postcard b/tauri-todos/src-tauri/.test-sth/peers.postcard new file mode 100644 index 00000000..74b7d7c0 Binary files /dev/null and b/tauri-todos/src-tauri/.test-sth/peers.postcard differ diff --git a/tauri-todos/src-tauri/.test-sth/spaces.redb b/tauri-todos/src-tauri/.test-sth/spaces.redb new file mode 100644 index 00000000..bb459c05 Binary files /dev/null and b/tauri-todos/src-tauri/.test-sth/spaces.redb differ diff --git a/tauri-todos/src-tauri/Cargo.lock b/tauri-todos/src-tauri/Cargo.lock index ddb7fb41..8d3bab13 100644 --- a/tauri-todos/src-tauri/Cargo.lock +++ b/tauri-todos/src-tauri/Cargo.lock @@ -111,15 +111,20 @@ version = "0.1.0" dependencies = [ "anyhow", "bytes", + "data-encoding", + "derive_more 1.0.0-beta.7", "futures-lite 2.3.0", + "futures-util", "iroh", - "num_cpus", + "postcard", "serde", "serde_json", "tauri", "tauri-build", "tokio", + "tokio-stream", "tokio-util", + "tracing-subscriber", ] [[package]] @@ -155,9 +160,9 @@ checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "asn1-rs" -version = "0.5.2" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" dependencies = [ "asn1-rs-derive", "asn1-rs-impl", @@ -171,25 +176,25 @@ dependencies = [ [[package]] name = "asn1-rs-derive" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.72", "synstructure", ] [[package]] name = "asn1-rs-impl" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.72", ] [[package]] @@ -360,6 +365,15 @@ dependencies = [ "system-deps 6.2.2", ] +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -495,6 +509,19 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake3" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9ec96fe9a81b5e365f9db71fe00edc4fe4ca2cc7dcb7861f0603012a7caa210" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + [[package]] name = "block" version = "0.1.6" @@ -809,26 +836,6 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "const_format" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" -dependencies = [ - "const_format_proc_macros", -] - -[[package]] -name = "const_format_proc_macros" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - [[package]] name = "constant_time_eq" version = "0.3.0" @@ -927,6 +934,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "critical-section" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f64009896348fc5af4222e9cf7d7d82a95a256c634ebcf61c53e4ea461422242" + [[package]] name = "crossbeam-channel" version = "0.5.13" @@ -1064,7 +1077,9 @@ dependencies = [ "curve25519-dalek-derive", "digest", "fiat-crypto", + "rand_core 0.6.4", "rustc_version", + "serde", "subtle", "zeroize", ] @@ -1147,9 +1162,9 @@ dependencies = [ [[package]] name = "der-parser" -version = "8.2.0" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" dependencies = [ "asn1-rs", "displaydoc", @@ -1401,6 +1416,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "elliptic-curve" version = "0.13.8" @@ -1446,6 +1467,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + [[package]] name = "encoding_rs" version = "0.8.34" @@ -2294,6 +2321,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -2319,6 +2355,20 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.3.3" @@ -2382,15 +2432,41 @@ dependencies = [ "url", ] +[[package]] +name = "hickory-proto" +version = "0.25.0-alpha.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8270a1857fb962b9914aafd46a89a187a4e63d0eb4190c327e7c7b8256a2d055" +dependencies = [ + "async-recursion", + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna 0.5.0", + "ipnet", + "once_cell", + "rand 0.8.5", + "thiserror", + "time", + "tinyvec", + "tokio", + "tracing", + "url", +] + [[package]] name = "hickory-resolver" -version = "0.24.1" +version = "0.25.0-alpha.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28757f23aa75c98f254cf0405e6d8c25b831b32921b050a66692427679b1f243" +checksum = "46c110355b5703070d9e29c344d79818a7cde3de9c27fc35750defea6074b0ad" dependencies = [ "cfg-if", "futures-util", - "hickory-proto", + "hickory-proto 0.25.0-alpha.2", "ipconfig", "lru-cache", "once_cell", @@ -2596,12 +2672,12 @@ dependencies = [ "http 1.1.0", "hyper 1.4.1", "hyper-util", - "rustls 0.23.12", + "rustls", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls", "tower-service", - "webpki-roots 0.26.3", + "webpki-roots", ] [[package]] @@ -2834,9 +2910,7 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "iroh" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24dfb8e552d56a5a4adae0226542a0d2c6d9645cf527708f37090ef3e877015a" +version = "0.24.0" dependencies = [ "anyhow", "async-channel", @@ -2856,6 +2930,7 @@ dependencies = [ "iroh-metrics", "iroh-net", "iroh-quinn", + "iroh-willow", "nested_enum_utils", "num_cpus", "parking_lot", @@ -2879,9 +2954,7 @@ dependencies = [ [[package]] name = "iroh-base" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8d60492b6099a5e94b674d0f4c564b3fa63a211836a090bc34c14d422721c19" +version = "0.24.0" dependencies = [ "aead", "anyhow", @@ -2921,9 +2994,7 @@ dependencies = [ [[package]] name = "iroh-blobs" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f44c07ea8e480ffe6b8d40dc029a73b68a312a6d021290d1afaaad8e332cc76b" +version = "0.24.0" dependencies = [ "anyhow", "async-channel", @@ -2940,6 +3011,7 @@ dependencies = [ "iroh-io", "iroh-metrics", "iroh-net", + "iroh-quinn", "num_cpus", "oneshot", "parking_lot", @@ -2963,9 +3035,7 @@ dependencies = [ [[package]] name = "iroh-docs" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f2515aabce975ef0fe2f93faa93ccf782d8294a8f61aeb1bcd517995b769ab4" +version = "0.24.0" dependencies = [ "anyhow", "async-channel", @@ -3002,9 +3072,7 @@ dependencies = [ [[package]] name = "iroh-gossip" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11551a79338d0d3aac1cff957a8801a2c3ffccce59fa1f3d60ca183aa785edab" +version = "0.24.0" dependencies = [ "anyhow", "async-channel", @@ -3043,9 +3111,7 @@ dependencies = [ [[package]] name = "iroh-metrics" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0be681eb052594a43afa8d2d2cc093361917c7348c779df3fe92a7169bd2f9c5" +version = "0.24.0" dependencies = [ "anyhow", "erased_set", @@ -3064,9 +3130,7 @@ dependencies = [ [[package]] name = "iroh-net" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b18cd649ce3a342d8480038abee8170e483bc3bec8f111bf57ca9679c5075ce" +version = "0.24.0" dependencies = [ "anyhow", "backoff", @@ -3083,7 +3147,7 @@ dependencies = [ "genawaiter", "governor", "hex", - "hickory-proto", + "hickory-proto 0.25.0-alpha.2", "hickory-resolver", "hostname", "http 1.1.0", @@ -3108,13 +3172,12 @@ dependencies = [ "pkarr", "postcard", "rand 0.8.5", - "rand_core 0.6.4", "rcgen", "reqwest 0.12.5", "ring", "rtnetlink", - "rustls 0.21.12", - "rustls-webpki 0.101.7", + "rustls", + "rustls-webpki", "serde", "smallvec", "socket2", @@ -3125,7 +3188,7 @@ dependencies = [ "thiserror", "time", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls", "tokio-stream", "tokio-tungstenite", "tokio-tungstenite-wasm", @@ -3134,7 +3197,7 @@ dependencies = [ "tungstenite", "url", "watchable", - "webpki-roots 0.25.4", + "webpki-roots", "windows 0.51.1", "wmi", "x509-parser", @@ -3143,16 +3206,17 @@ dependencies = [ [[package]] name = "iroh-quinn" -version = "0.10.5" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "906875956feb75d3d41d708ddaffeb11fdb10cd05f23efbcb17600037e411779" +checksum = "4fd590a39a14cfc168efa4d894de5039d65641e62d8da4a80733018ababe3c33" dependencies = [ "bytes", "iroh-quinn-proto", "iroh-quinn-udp", "pin-project-lite", - "rustc-hash 1.1.0", - "rustls 0.21.12", + "rustc-hash", + "rustls", + "socket2", "thiserror", "tokio", "tracing", @@ -3160,16 +3224,16 @@ dependencies = [ [[package]] name = "iroh-quinn-proto" -version = "0.10.8" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6bf92478805e67f2320459285496e1137edf5171411001a0d4d85f9bbafb792" +checksum = "5fd0538ff12efe3d61ea1deda2d7913f4270873a519d43e6995c6e87a1558538" dependencies = [ "bytes", "rand 0.8.5", "ring", - "rustc-hash 1.1.0", - "rustls 0.21.12", - "rustls-native-certs", + "rustc-hash", + "rustls", + "rustls-platform-verifier", "slab", "thiserror", "tinyvec", @@ -3178,15 +3242,69 @@ dependencies = [ [[package]] name = "iroh-quinn-udp" -version = "0.4.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc7915b3a31f08ee0bc02f73f4d61a5d5be146a1081ef7f70622a11627fd314" +checksum = "d0619b59471fdd393ac8a6c047f640171119c1c8b41f7d2927db91776dcdbc5f" dependencies = [ - "bytes", "libc", + "once_cell", "socket2", "tracing", - "windows-sys 0.48.0", + "windows-sys 0.59.0", +] + +[[package]] +name = "iroh-willow" +version = "0.24.0" +dependencies = [ + "anyhow", + "bytes", + "curve25519-dalek", + "derive_more 1.0.0-beta.7", + "ed25519-dalek", + "either", + "futures-buffered", + "futures-concurrency", + "futures-lite 2.3.0", + "futures-util", + "genawaiter", + "hex", + "iroh-base", + "iroh-blake3", + "iroh-blobs", + "iroh-io", + "iroh-metrics", + "iroh-net", + "meadowcap", + "postcard", + "rand 0.8.5", + "rand_core 0.6.4", + "redb 2.1.1", + "self_cell", + "serde", + "sha2", + "strum 0.26.3", + "syncify", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "ufotofu", + "willow-data-model", + "willow-encoding", + "willow-store", + "zerocopy", + "zerocopy-derive", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", ] [[package]] @@ -3224,6 +3342,20 @@ dependencies = [ "system-deps 5.0.0", ] +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + [[package]] name = "jni" version = "0.20.0" @@ -3474,12 +3606,31 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +[[package]] +name = "maybe-std" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4958ec1997b05011d5c786bf4093cd48578bd9be2737350ab38659694083ddde" + [[package]] name = "md5" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" +[[package]] +name = "meadowcap" +version = "0.1.0" +source = "git+https://github.com/n0-computer/willow-rs.git?branch=main#1e9943e39d08f9ec8b79fc6a5805be449a19f5d0" +dependencies = [ + "either", + "signature", + "syncify", + "ufotofu", + "willow-data-model", + "willow-encoding", +] + [[package]] name = "memalloc" version = "0.1.0" @@ -4056,9 +4207,9 @@ dependencies = [ [[package]] name = "oid-registry" -version = "0.6.1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" dependencies = [ "asn1-rs", ] @@ -4698,22 +4849,23 @@ dependencies = [ [[package]] name = "postcard" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55c51ee6c0db07e68448e336cf8ea4131a620edefebf9893e759b2d793420f8" +checksum = "5f7f0a8d620d71c457dd1d47df76bb18960378da56af4527aaa10f515eee732e" dependencies = [ "cobs", - "const_format", - "embedded-io", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless", "postcard-derive", "serde", ] [[package]] name = "postcard-derive" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4b01218787dd4420daf63875163a787a78294ad48a24e9f6fa8c6507759a79" +checksum = "0239fa9c1d225d4b7eb69925c25c5e082307a141e470573fbbe3a817ce6a7a37" dependencies = [ "proc-macro2", "quote", @@ -4908,9 +5060,9 @@ dependencies = [ [[package]] name = "quic-rpc" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "110f0fbbf7c4a694902e11d890157245801d89a18d8e9b8d9d2afd91358a6a7c" +checksum = "87cb85690ab1688eade9a5de4d94545a9ceef60639b3370f5e1a28f525eb5589" dependencies = [ "anyhow", "bincode", @@ -4924,6 +5076,7 @@ dependencies = [ "iroh-quinn", "pin-project", "serde", + "slab", "tokio", "tokio-serde", "tokio-util", @@ -4932,9 +5085,9 @@ dependencies = [ [[package]] name = "quic-rpc-derive" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b02c2d3637609d07aa2560f178588dce09cf2f4f5cdcc9b0caac0063a188a898" +checksum = "6150a9fd3cf6c34d25730fe55a247b99d1c6e4fad6e7b7843f729a431a57e919" dependencies = [ "proc-macro2", "quic-rpc", @@ -4985,8 +5138,8 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.0.0", - "rustls 0.23.12", + "rustc-hash", + "rustls", "socket2", "thiserror", "tokio", @@ -5002,8 +5155,8 @@ dependencies = [ "bytes", "rand 0.8.5", "ring", - "rustc-hash 2.0.0", - "rustls 0.23.12", + "rustc-hash", + "rustls", "slab", "thiserror", "tinyvec", @@ -5362,7 +5515,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.12", + "rustls", "rustls-pemfile 2.1.3", "rustls-pki-types", "serde", @@ -5370,13 +5523,13 @@ dependencies = [ "serde_urlencoded", "sync_wrapper 1.0.1", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.26.3", + "webpki-roots", "winreg 0.52.0", ] @@ -5484,12 +5637,6 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc-hash" version = "2.0.0" @@ -5527,18 +5674,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", -] - [[package]] name = "rustls" version = "0.23.12" @@ -5549,19 +5684,20 @@ dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.102.6", + "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-native-certs" -version = "0.6.3" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" dependencies = [ "openssl-probe", - "rustls-pemfile 1.0.4", + "rustls-pemfile 2.1.3", + "rustls-pki-types", "schannel", "security-framework", ] @@ -5592,15 +5728,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] -name = "rustls-webpki" -version = "0.101.7" +name = "rustls-platform-verifier" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +checksum = "afbb878bdfdf63a336a5e63561b1835e7a8c91524f51621db870169eac84b490" dependencies = [ - "ring", - "untrusted", + "core-foundation", + "core-foundation-sys", + "jni 0.19.0", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-roots", + "winapi", ] +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.102.6" @@ -5663,16 +5816,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "sec1" version = "0.7.3" @@ -5697,6 +5840,7 @@ dependencies = [ "core-foundation", "core-foundation-sys", "libc", + "num-bigint", "security-framework-sys", ] @@ -6309,7 +6453,7 @@ checksum = "39769914108ae68e261d85ceac7bce7095947130f79c29d4535e9b31fc702a40" dependencies = [ "acto", "anyhow", - "hickory-proto", + "hickory-proto 0.24.1", "rand 0.8.5", "socket2", "tokio", @@ -6361,16 +6505,26 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +[[package]] +name = "syncify" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e2f83220c0c5abf77ec9f4910c6590f75f1bf1405c7f2762bf35fb1bd11c5e7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "synstructure" -version = "0.12.6" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", - "unicode-xid", + "syn 2.0.72", ] [[package]] @@ -6479,7 +6633,7 @@ dependencies = [ "gtk", "image 0.24.9", "instant", - "jni", + "jni 0.20.0", "lazy_static", "libc", "log", @@ -6901,23 +7055,13 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.12", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.12", + "rustls", "rustls-pki-types", "tokio", ] @@ -6981,13 +7125,15 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", "futures-sink", + "futures-util", + "hashbrown 0.14.5", "pin-project-lite", "slab", "tokio", @@ -7251,6 +7397,23 @@ dependencies = [ "winapi", ] +[[package]] +name = "ufotofu" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "530b5dd047cf0fbc98b5138a17419d9f914de9b98971b6ac8a1b9e2050b711ef" +dependencies = [ + "either", + "ufotofu_queues", + "wrapper", +] + +[[package]] +name = "ufotofu_queues" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d903d5bc0e14d24559dac3b9690d004ad3fb08d66f93d87d28f5cb3466b5b55b" + [[package]] name = "unicode-bidi" version = "0.3.15" @@ -7309,10 +7472,10 @@ dependencies = [ "base64 0.22.1", "log", "once_cell", - "rustls 0.23.12", + "rustls", "rustls-pki-types", "url", - "webpki-roots 0.26.3", + "webpki-roots", ] [[package]] @@ -7650,12 +7813,6 @@ dependencies = [ "system-deps 6.2.2", ] -[[package]] -name = "webpki-roots" -version = "0.25.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" - [[package]] name = "webpki-roots" version = "0.26.3" @@ -7715,6 +7872,46 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" +[[package]] +name = "willow-data-model" +version = "0.1.0" +source = "git+https://github.com/n0-computer/willow-rs.git?branch=main#1e9943e39d08f9ec8b79fc6a5805be449a19f5d0" +dependencies = [ + "bytes", + "either", + "syncify", + "ufotofu", + "willow-encoding", +] + +[[package]] +name = "willow-encoding" +version = "0.1.0" +source = "git+https://github.com/n0-computer/willow-rs.git?branch=main#1e9943e39d08f9ec8b79fc6a5805be449a19f5d0" +dependencies = [ + "either", + "syncify", + "ufotofu", +] + +[[package]] +name = "willow-store" +version = "0.1.0" +source = "git+https://github.com/n0-computer/willow-store.git?branch=matheus23/redb-ref#3a29c592fe071ff87fa763bd4650bfed789a53e6" +dependencies = [ + "anyhow", + "blake3", + "genawaiter", + "hex", + "itertools", + "redb 2.1.1", + "ref-cast", + "self_cell", + "smallvec", + "tracing", + "zerocopy", +] + [[package]] name = "winapi" version = "0.3.9" @@ -8363,6 +8560,15 @@ dependencies = [ "windows 0.52.0", ] +[[package]] +name = "wrapper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2bbc2d8fbd2f17a297df6d6492526a91b947c04ec80536c0877fd35df1c889d" +dependencies = [ + "maybe-std", +] + [[package]] name = "wry" version = "0.24.10" @@ -8450,9 +8656,9 @@ checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" [[package]] name = "x509-parser" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7069fba5b66b9193bd2c5d3d4ff12b839118f6bcbef5328efafafb5395cf63da" +checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" dependencies = [ "asn1-rs", "data-encoding", diff --git a/tauri-todos/src-tauri/Cargo.toml b/tauri-todos/src-tauri/Cargo.toml index 6b2444bf..c11c3c08 100644 --- a/tauri-todos/src-tauri/Cargo.toml +++ b/tauri-todos/src-tauri/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" description = "Todos with Iroh" license = "MIT OR Apache-2.0" authors = ["dignifiedquire ", "n0 team"] -repository = "https://github.com/n0-computer/iroh-example-todos" +repository = "https://github.com/n0-computer/iroh-examples" default-run = "app" edition = "2021" rust-version = "1.65" @@ -18,11 +18,17 @@ serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } tauri = { version = "1.6.1", features = ["api-all"] } tokio = { version = "1" } -iroh = "0.23" +# iroh = { git = "https://github.com/n0-computer/iroh.git", branch = "Frando/willow-proptest" } +iroh = { path = "../../../iroh-willow/iroh" } bytes = "1" -num_cpus = { version = "1.15.0" } tokio-util = { version = "0.7" } futures-lite = "2.3.0" +derive_more = "=1.0.0-beta.7" +postcard = "1.0.10" +data-encoding = "2.6.0" +tokio-stream = "0.1.15" +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } +futures-util = "0.3.30" [features] # by default Tauri runs in production mode @@ -31,3 +37,11 @@ default = [ "custom-protocol" ] # this feature is used used for production builds where `devPath` points to the filesystem # DO NOT remove this custom-protocol = [ "tauri/custom-protocol" ] + +[patch.crates-io] +# willow-data-model = { path = "../willow-rs/data-model" } +# willow-encoding = { path = "../willow-rs/encoding" } +# meadowcap = { path = "../willow-rs/meadowcap" } +willow-data-model = { git = "https://github.com/n0-computer/willow-rs.git", branch = "main" } +willow-encoding = { git = "https://github.com/n0-computer/willow-rs.git", branch = "main" } +meadowcap = { git = "https://github.com/n0-computer/willow-rs.git", branch = "main" } diff --git a/tauri-todos/src-tauri/src/main.rs b/tauri-todos/src-tauri/src/main.rs index fc38f0c7..3e3605c4 100644 --- a/tauri-todos/src-tauri/src/main.rs +++ b/tauri-todos/src-tauri/src/main.rs @@ -4,12 +4,11 @@ )] mod todos; -use anyhow::{anyhow, Result}; +use anyhow::Result; use futures_lite::StreamExt; -use iroh::client::docs::LiveEvent; use iroh::client::Iroh; -use iroh::docs::ContentStatus; use tauri::Manager; +use todos::CapExchangeProtocol; use tokio::sync::Mutex; use self::todos::{Todo, Todos}; @@ -19,19 +18,50 @@ type IrohNode = iroh::node::Node; // setup an iroh node async fn setup(handle: tauri::AppHandle) -> Result<()> { - // get the applicaiton data root, join with "iroh_data" to get the data root for the iroh node - let data_root = handle - .path_resolver() - .app_data_dir() - .ok_or_else(|| anyhow!("can't get application data directory"))? - .join("iroh_data"); + let iroh_data_dir = std::env::var("IROH_DATA_DIR") + .ok() + .map(std::path::PathBuf::from); + + // get the application data root, join with "iroh_data" to get the data root for the iroh node + let data_root = iroh_data_dir.map(anyhow::Ok).unwrap_or_else(|| { + anyhow::Ok( + handle + .path_resolver() + .app_data_dir() + .ok_or_else(|| anyhow::anyhow!("can't get application data directory"))? + .join("iroh_data"), + ) + })?; // create the iroh node let node = iroh::node::Node::persistent(data_root) .await? + .build() + .await? + .accept( + CapExchangeProtocol::ALPN, + CapExchangeProtocol::new({ + let handle = handle.clone(); + move |user_id| { + Box::pin({ + let handle = handle.clone(); + async move { + let state: tauri::State<'_, AppState> = + handle.try_state().ok_or(anyhow::anyhow!("Missing state"))?; + let Some((todos, _)) = &mut *state.todos.lock().await else { + anyhow::bail!("Not initialized"); + }; + let ticket = todos.share_ticket(user_id).await?; + Ok(ticket) + } + }) + } + }), + ) .spawn() .await?; handle.manage(AppState::new(node)); + println!("CALLED MANAGE"); Ok(()) } @@ -40,6 +70,7 @@ struct AppState { todos: Mutex)>>, iroh: IrohNode, } + impl AppState { fn new(iroh: IrohNode) -> Self { AppState { @@ -52,6 +83,10 @@ impl AppState { self.iroh.client().clone() } + fn endpoint(&self) -> iroh::net::Endpoint { + self.iroh.endpoint().clone() + } + async fn init_todos( &self, app_handle: tauri::AppHandle, @@ -60,25 +95,17 @@ impl AppState { let mut events = todos.doc_subscribe().await?; let events_handle = tokio::spawn(async move { while let Some(Ok(event)) = events.next().await { - match event { - LiveEvent::InsertRemote { content_status, .. } => { - // Only update if the we already have the content. Likely to happen when a remote user toggles "done". - if content_status == ContentStatus::Complete { - app_handle.emit_all("update-all", ()).ok(); - } - } - LiveEvent::InsertLocal { .. } | LiveEvent::ContentReady { .. } => { - app_handle.emit_all("update-all", ()).ok(); - } - _ => {} - } + println!("Got an event {event:?}"); + app_handle.emit_all("update-all", ()).ok(); } }); let mut t = self.todos.lock().await; if let Some((_t, handle)) = t.take() { + println!("Aborting existing state"); handle.abort(); } + println!("Initializing state"); *t = Some((todos, events_handle)); Ok(()) @@ -86,6 +113,8 @@ impl AppState { } fn main() { + tracing_subscriber::fmt::init(); + tauri::Builder::default() .setup(|app| { let handle = app.handle(); @@ -112,7 +141,7 @@ fn main() { toggle_done, update_todo, delete, - set_ticket, + join_list, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); @@ -132,7 +161,7 @@ async fn new_list( app_handle: tauri::AppHandle, state: tauri::State<'_, AppState>, ) -> Result<(), String> { - let todos = Todos::new(None, state.iroh()) + let todos = Todos::new(None, state.iroh(), state.endpoint()) .await .map_err(|e| e.to_string())?; @@ -170,29 +199,29 @@ async fn update_todo(todo: Todo, state: tauri::State<'_, AppState>) -> Result<() #[tauri::command] async fn toggle_done(id: String, state: tauri::State<'_, AppState>) -> Result { - if let Some((todos, _)) = &mut *state.todos.lock().await { - todos.toggle_done(id).await.map_err(|e| e.to_string())?; - return Ok(true); - } - Err("not initialized".to_string()) + let Some((todos, _)) = &mut *state.todos.lock().await else { + return Err("not initialized".to_string()); + }; + todos.toggle_done(id).await.map_err(|e| e.to_string())?; + Ok(true) } #[tauri::command] async fn delete(id: String, state: tauri::State<'_, AppState>) -> Result { - if let Some((todos, _)) = &mut *state.todos.lock().await { - todos.delete(id).await.map_err(|e| e.to_string())?; - return Ok(true); - } - Err("not initialized".to_string()) + let Some((todos, _)) = &mut *state.todos.lock().await else { + return Err("not initialized".to_string()); + }; + todos.delete(id).await.map_err(|e| e.to_string())?; + Ok(true) } #[tauri::command] -async fn set_ticket( +async fn join_list( app_handle: tauri::AppHandle, ticket: String, state: tauri::State<'_, AppState>, ) -> Result<(), String> { - let todos = Todos::new(Some(ticket), state.iroh()) + let todos = Todos::new(Some(ticket), state.iroh(), state.endpoint()) .await .map_err(|e| e.to_string())?; @@ -206,8 +235,8 @@ async fn set_ticket( #[tauri::command] async fn get_ticket(state: tauri::State<'_, AppState>) -> Result { - if let Some((todos, _)) = &mut *state.todos.lock().await { - return Ok(todos.ticket()); - } - Err("not initialized".to_string()) + let Some((todos, _)) = &mut *state.todos.lock().await else { + return Err("not initialized".to_string()); + }; + todos.ticket().await.map_err(|e| e.to_string()) } diff --git a/tauri-todos/src-tauri/src/todos.rs b/tauri-todos/src-tauri/src/todos.rs index 66f57f91..16df7ce2 100644 --- a/tauri-todos/src-tauri/src/todos.rs +++ b/tauri-todos/src-tauri/src/todos.rs @@ -1,12 +1,26 @@ -use anyhow::{bail, ensure, Context, Result}; +use anyhow::{anyhow, bail, ensure, Context, Result}; use bytes::Bytes; -use futures_lite::{Stream, StreamExt}; -use iroh::client::docs::{Entry, LiveEvent, ShareMode}; -use iroh::client::{docs::Doc, Iroh}; -use iroh::docs::{AuthorId, DocTicket}; -use std::str::FromStr; -// use iroh::ticket::DocTicket; +use futures_lite::future::Boxed; +use futures_lite::Stream; +use futures_util::{StreamExt, TryStreamExt as _}; +use iroh::client::spaces::{EntryForm, Space, SpaceTicket}; +use iroh::client::Iroh; +use iroh::net::ticket::NodeTicket; +use iroh::net::NodeAddr; +use iroh::node::ProtocolHandler; +use iroh::spaces::form::{AuthForm, SubspaceForm, TimestampForm}; +use iroh::spaces::interest::{CapabilityPack, RestrictArea}; +use iroh::spaces::proto::data_model::{Entry, Path, PathExt, SubspaceId}; +use iroh::spaces::proto::grouping::{Area, Range, Range3d}; +use iroh::spaces::proto::keys::{NamespaceKind, UserId}; +use iroh::spaces::proto::meadowcap::AccessMode; +use iroh::spaces::session::SessionMode; +use iroh::spaces::store::traits::StoreEvent; use serde::{Deserialize, Serialize}; +use std::collections::{btree_map, BTreeMap}; +use std::str::FromStr; +use std::sync::Arc; +use tokio::task::JoinSet; /// Todo in a list of todos. #[derive(Clone, Debug, Serialize, Deserialize)] @@ -54,39 +68,75 @@ const MAX_LABEL_LEN: usize = 2 * 1000; /// List of todos, including completed todos that have not been archived pub struct Todos { node: Iroh, - doc: Doc, - ticket: DocTicket, - author: AuthorId, + space: Space, + subspace_id: SubspaceId, + user: UserId, + #[allow(unused)] + tasks: JoinSet>, } impl Todos { - pub async fn new(ticket: Option, node: Iroh) -> anyhow::Result { - let author = node.authors().create().await?; - - let doc = match ticket { - None => node.docs().create().await?, - Some(ticket) => { - let ticket = DocTicket::from_str(&ticket)?; - node.docs().import(ticket).await? + pub async fn new( + node_ticket: Option, + node: Iroh, + endpoint: iroh::net::Endpoint, + ) -> anyhow::Result { + let user = node.spaces().create_user().await?; + + let (space, subspace_id, tasks) = match node_ticket { + None => ( + node.spaces().create(NamespaceKind::Owned, user).await?, + user, // owner == subspace ID + JoinSet::new(), + ), + Some(node_ticket) => { + let node_addr = NodeTicket::from_str(&node_ticket)?.node_addr().clone(); + let ticket = CapExchangeProtocol::request(&endpoint, node_addr, user).await?; + anyhow::ensure!(ticket.caps.len() >= 1, "at least one capability delegated"); + let subspace_id = *progenitor(&ticket.caps[0]); + println!("Importing & Syncing"); + let (space, mut sync) = node + .spaces() + .import_and_sync(ticket, SessionMode::Continuous) + .await?; + let mut tasks = JoinSet::new(); + tasks.spawn(async move { + while let Some(ev) = sync.next().await { + println!("=== SYNC EVENT === {ev:?}"); + } + anyhow::Ok(()) + }); + (space, subspace_id, tasks) } }; - let ticket = doc.share(ShareMode::Write, Default::default()).await?; + println!("Using subspace ID {subspace_id}"); Ok(Todos { node, - author, - doc, - ticket, + user, + subspace_id, + space, + tasks, }) } - pub fn ticket(&self) -> String { - self.ticket.to_string() + pub async fn ticket(&self) -> Result { + Ok(NodeTicket::new(self.node.net().node_addr().await?)?.to_string()) } - pub async fn doc_subscribe(&self) -> Result>> { - self.doc.subscribe().await + pub async fn share_ticket(&self, user: UserId) -> Result { + self.space + .share(user, AccessMode::Write, RestrictArea::None) + .await + } + + pub async fn doc_subscribe(&self) -> Result>> { + let stream = self + .space + .subscribe_area(Area::new_full(), Default::default()) + .await?; + Ok(stream) } pub async fn add(&mut self, id: String, label: String) -> anyhow::Result<()> { @@ -129,26 +179,27 @@ impl Todos { } pub async fn get_todos(&self) -> anyhow::Result> { - let mut entries = self - .doc - .get_many(iroh::docs::store::Query::single_latest_per_key()) - .await?; + let entries = self.get_latest(Range3d::new_full()).await?; - let mut todos = Vec::new(); - while let Some(entry) = entries.next().await { - let entry = entry?; - let todo = self.todo_from_entry(&entry).await?; - if !todo.is_delete { - todos.push(todo); - } - } - todos.sort_by_key(|t| t.created); + let mut todos = futures_lite::stream::iter(entries.into_iter()) + .then(|entry| async move { self.todo_from_entry(&entry).await }) + .try_collect::>() + .await?; + todos.sort_by_key(|todo| todo.created); Ok(todos) } async fn insert_bytes(&self, key: impl AsRef<[u8]>, content: Bytes) -> anyhow::Result<()> { - self.doc - .set_bytes(self.author, key.as_ref().to_vec(), content) + self.space + .insert_bytes( + EntryForm { + auth: AuthForm::Any(self.user), + subspace_id: SubspaceForm::Exact(self.subspace_id), + path: Self::to_willow_path(key)?, + timestamp: TimestampForm::Now, + }, + content, + ) .await?; Ok(()) } @@ -159,22 +210,145 @@ impl Todos { } async fn get_todo(&self, id: String) -> anyhow::Result { - let entry = self - .doc - .get_many(iroh::docs::store::Query::single_latest_per_key().key_exact(id)) - .await? - .next() - .await - .ok_or_else(|| anyhow::anyhow!("no todo found"))??; + let path = Self::to_willow_path(&id)?; + let path_dumb = Self::to_willow_path(&format!("{id} "))?; + let entries = self + .get_latest(Range3d::new( + Range::full(), + // Create a closed range, the interval end is simply the path plus another space, to make it lexicographically bigger. + // This is a stupid workaround for the fact that `Range` doesn't have a way to be exactly one thing. + Range::new_closed(path, path_dumb).expect("we did the dumb thing right"), + Range::full(), + )) + .await?; + + let entry = entries.last().ok_or_else(|| anyhow!("no todo found"))?; self.todo_from_entry(&entry).await } + async fn get_latest(&self, range: Range3d) -> anyhow::Result> { + let mut entries = self + .space + .get_many(range) + .await? + .map_ok(|e| e.into_parts().0); + + let mut deduplicated = BTreeMap::::new(); + while let Some(entry) = entries.try_next().await? { + match deduplicated.entry(entry.path().clone()) { + btree_map::Entry::Occupied(mut curr) => { + if !curr.get().is_newer_than(&entry) { + *curr.get_mut() = entry; + } + } + btree_map::Entry::Vacant(space) => { + space.insert(entry); + } + } + } + + let mut collected = deduplicated.into_values().collect::>(); + collected.sort_by_key(Entry::timestamp); + Ok(collected) + } + async fn todo_from_entry(&self, entry: &Entry) -> anyhow::Result { - let id = String::from_utf8(entry.key().to_owned()).context("invalid key")?; - match self.node.blobs().read_to_bytes(entry.content_hash()).await { + let id = Self::from_willow_path(entry.path())?; + let digest = entry.payload_digest(); + match self.node.blobs().read_to_bytes(digest.0).await { Ok(b) => Todo::from_bytes(b), Err(_) => Ok(Todo::missing_todo(id)), } } + + fn to_willow_path(key: impl AsRef<[u8]>) -> anyhow::Result { + Ok(Path::from_bytes(&[key.as_ref()])?) + } + + fn from_willow_path(path: &Path) -> anyhow::Result { + let key_component = path + .get_component(0) + .ok_or_else(|| anyhow::anyhow!("path component missing"))?; + let id = String::from_utf8(key_component.to_vec()).context("invalid key")?; + Ok(id) + } +} + +type GetTicketFn = Arc Boxed> + Send + Sync + 'static>; + +#[derive(derive_more::Debug)] +pub struct CapExchangeProtocol { + #[debug(skip)] + build_ticket: GetTicketFn, +} + +impl CapExchangeProtocol { + pub const ALPN: &'static [u8] = b"iroh-tauri-todos/cap-request/0"; + + pub fn new( + build_ticket: impl Fn(UserId) -> Boxed> + Send + Sync + 'static, + ) -> Arc { + Arc::new(Self { + build_ticket: Arc::new(build_ticket), + }) + } + + pub async fn request( + endpoint: &iroh::net::Endpoint, + node_addr: NodeAddr, + user: UserId, + ) -> Result { + println!("Requesting caps from {}", node_addr.node_id.fmt_short()); + let conn = endpoint + .connect(node_addr, CapExchangeProtocol::ALPN) + .await?; + let (mut send, mut recv) = conn.open_bi().await?; + send.write_all(user.as_bytes()).await?; + send.finish()?; + println!("Sent request"); + let ticket_bytes = recv.read_to_end(1024 * 10).await?; + let ticket: SpaceTicket = postcard::from_bytes(&ticket_bytes)?; + println!("Received ticket"); + conn.close(0u32.into(), b"thanks for the ticket!"); + Ok(ticket) + } + + pub async fn respond(self: Arc, conn: iroh::net::endpoint::Connecting) -> Result<()> { + let conn = conn.await?; + + let (mut send, mut recv) = conn.accept_bi().await?; + + let bytes: [u8; 32] = recv + .read_to_end(1000) + .await? + .try_into() + .map_err(|v: Vec| anyhow::anyhow!("Expected 32 bytes, but got {}", v.len()))?; + let user_id = UserId::from(bytes); + println!("Got incoming cap request from {user_id}"); + + let ticket = (self.build_ticket)(user_id).await?; + let ticket_bytes = postcard::to_allocvec(&ticket)?; + send.write_all(&ticket_bytes).await?; + send.finish()?; + println!("Sent ticket"); + + let close = conn.closed().await; + println!("conn closed: {close:?}"); + + Ok(()) + } +} + +impl ProtocolHandler for CapExchangeProtocol { + fn accept(self: Arc, conn: iroh::net::endpoint::Connecting) -> Boxed> { + Box::pin(self.respond(conn)) + } +} + +fn progenitor(cap: &CapabilityPack) -> &UserId { + match cap { + CapabilityPack::Read(read) => read.read_cap().progenitor(), + CapabilityPack::Write(write) => write.progenitor(), + } } diff --git a/tauri-todos/src/App.tsx b/tauri-todos/src/App.tsx index d7664001..68ff0d99 100644 --- a/tauri-todos/src/App.tsx +++ b/tauri-todos/src/App.tsx @@ -15,40 +15,36 @@ function App() { useEffect(() => { listen('update-all', (event) => { console.log("updating", event) - getTodos() + getTodos() }) }, []) - function createList() { + async function createList() { console.log("create list"); - invoke('new_list').then(() => { - console.log("in new_list and then"); - getTodos(); - setShowOpenList(false); - }) + await invoke('new_list') + console.log("in new_list and then") + await getTodos() + setShowOpenList(false) } - function joinList(ticket:string) { + async function joinList(ticket: string) { console.log("join list"); - setTicket(ticket); - getTodos(); + await joinListInv(ticket); + await getTodos(); setShowOpenList(false); } - function setTicket(ticket: string) { + async function joinListInv(ticket: string) { // this is the effect for the modal // otherwise just get-todos - invoke('set_ticket', {ticket}).then((res) => { - getTodos() - }) + await invoke('join_list', { ticket }) + await getTodos() } - function getTodos() { - invoke('get_todos').then((res) => { - setAllTodos(res) - }).then(()=> { - setShowOpenList(false) - }) + async function getTodos() { + const res = await invoke('get_todos') + setAllTodos(res) + setShowOpenList(false) } return ( diff --git a/tauri-todos/src/component/OpenList.tsx b/tauri-todos/src/component/OpenList.tsx index 83ff2d51..3ab68b17 100644 --- a/tauri-todos/src/component/OpenList.tsx +++ b/tauri-todos/src/component/OpenList.tsx @@ -6,7 +6,7 @@ import { Todo } from '../types/todo' import TodoItem from './TodoItem' import { invoke } from '@tauri-apps/api' -const OpenList: React.FC<{ createList: () => void, joinList: (ticket: string) => void }> = ({ createList, joinList}) => { +const OpenList: React.FC<{ createList: () => Promise, joinList: (ticket: string) => Promise }> = ({ createList, joinList}) => { const [ticket, setTicket] = useState(''); return ( <> @@ -35,7 +35,7 @@ const OpenList: React.FC<{ createList: () => void, joinList: (ticket: string) => type="text" placeholder='input a ticket to join a list' /> - joinList(ticket)}style={{width: 110, cursor: "pointer", color:"#b83f45"}}>Join Using Ticket ⇨ + joinList(ticket)} style={{width: 110, cursor: "pointer", color:"#b83f45"}}>Join Using Ticket ⇨ diff --git a/tauri-todos/src/component/TodoList.tsx b/tauri-todos/src/component/TodoList.tsx index 3174da06..c2580f21 100644 --- a/tauri-todos/src/component/TodoList.tsx +++ b/tauri-todos/src/component/TodoList.tsx @@ -20,6 +20,9 @@ const TodoList: React.FC<{ todos: Todo[] }> = ({ todos }) => { console.log("get ticket"); console.log(res); setTicket(res) + }).catch(err => { + console.log("get ticket err"); + console.log(err); }) }, [])