From defa4bb8270c214710ee37348abc684574803ba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Thu, 29 Aug 2024 13:15:12 +0200 Subject: [PATCH 01/17] Write flake.nix file --- tauri-todos/.envrc | 1 + tauri-todos/flake.lock | 81 ++++++++++++++++++++++++++++++++++++++++ tauri-todos/flake.nix | 85 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+) create mode 100644 tauri-todos/.envrc create mode 100644 tauri-todos/flake.lock create mode 100644 tauri-todos/flake.nix diff --git a/tauri-todos/.envrc b/tauri-todos/.envrc new file mode 100644 index 00000000..3550a30f --- /dev/null +++ b/tauri-todos/.envrc @@ -0,0 +1 @@ +use flake diff --git a/tauri-todos/flake.lock b/tauri-todos/flake.lock new file mode 100644 index 00000000..cc92af7c --- /dev/null +++ b/tauri-todos/flake.lock @@ -0,0 +1,81 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "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": 1724898214, + "narHash": "sha256-4yMO9+Lsr3zqTf4clAGGag/bfNTmc/ITOXbJQcOEok4=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "0bc2c784e3a6ce30a2ab1b9f47325ccbed13039f", + "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/tauri-todos/flake.nix b/tauri-todos/flake.nix new file mode 100644 index 00000000..fcd7587b --- /dev/null +++ b/tauri-todos/flake.nix @@ -0,0 +1,85 @@ +{ + 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"; + }; + }); +} From da8774b9f2bc7cb0a10ce0bafb25bf7d116d4161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Thu, 29 Aug 2024 13:15:34 +0200 Subject: [PATCH 02/17] Enable using an `IROH_DATA_DIR` env variable --- tauri-todos/.gitignore | 2 ++ tauri-todos/src-tauri/src/main.rs | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) 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/src/main.rs b/tauri-todos/src-tauri/src/main.rs index fc38f0c7..9ead8d5f 100644 --- a/tauri-todos/src-tauri/src/main.rs +++ b/tauri-todos/src-tauri/src/main.rs @@ -19,12 +19,20 @@ type IrohNode = iroh::node::Node; // setup an iroh node async fn setup(handle: tauri::AppHandle) -> Result<()> { + let iroh_data_dir = std::env::var("IROH_DATA_DIR") + .ok() + .map(std::path::PathBuf::from); + // 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 data_root = iroh_data_dir.map(anyhow::Ok).unwrap_or_else(|| { + anyhow::Ok( + handle + .path_resolver() + .app_data_dir() + .ok_or_else(|| anyhow!("can't get application data directory"))? + .join("iroh_data"), + ) + })?; // create the iroh node let node = iroh::node::Node::persistent(data_root) From d06c8d384fa9d2de868b6198f6c58a42bc7295b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Thu, 29 Aug 2024 13:18:59 +0200 Subject: [PATCH 03/17] Move flake files to top level --- tauri-todos/.envrc => .envrc | 0 tauri-todos/flake.lock => flake.lock | 0 tauri-todos/flake.nix => flake.nix | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename tauri-todos/.envrc => .envrc (100%) rename tauri-todos/flake.lock => flake.lock (100%) rename tauri-todos/flake.nix => flake.nix (100%) diff --git a/tauri-todos/.envrc b/.envrc similarity index 100% rename from tauri-todos/.envrc rename to .envrc diff --git a/tauri-todos/flake.lock b/flake.lock similarity index 100% rename from tauri-todos/flake.lock rename to flake.lock diff --git a/tauri-todos/flake.nix b/flake.nix similarity index 100% rename from tauri-todos/flake.nix rename to flake.nix From f3430fc9574a17f24f558c1b8bcb1c5b32809b7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Fri, 30 Aug 2024 14:22:11 +0200 Subject: [PATCH 04/17] Write a version of traditional iroh docs with a cap exchange protocol --- tauri-todos/src-tauri/Cargo.lock | 1 + tauri-todos/src-tauri/Cargo.toml | 1 + tauri-todos/src-tauri/src/main.rs | 60 +++++++++++++++------ tauri-todos/src-tauri/src/todos.rs | 85 +++++++++++++++++++++++++----- tauri-todos/src/App.tsx | 6 +-- 5 files changed, 121 insertions(+), 32 deletions(-) diff --git a/tauri-todos/src-tauri/Cargo.lock b/tauri-todos/src-tauri/Cargo.lock index ddb7fb41..2fd48cdb 100644 --- a/tauri-todos/src-tauri/Cargo.lock +++ b/tauri-todos/src-tauri/Cargo.lock @@ -111,6 +111,7 @@ version = "0.1.0" dependencies = [ "anyhow", "bytes", + "derive_more 1.0.0-beta.7", "futures-lite 2.3.0", "iroh", "num_cpus", diff --git a/tauri-todos/src-tauri/Cargo.toml b/tauri-todos/src-tauri/Cargo.toml index 6b2444bf..b0529988 100644 --- a/tauri-todos/src-tauri/Cargo.toml +++ b/tauri-todos/src-tauri/Cargo.toml @@ -23,6 +23,7 @@ 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" [features] # by default Tauri runs in production mode diff --git a/tauri-todos/src-tauri/src/main.rs b/tauri-todos/src-tauri/src/main.rs index 9ead8d5f..2da15804 100644 --- a/tauri-todos/src-tauri/src/main.rs +++ b/tauri-todos/src-tauri/src/main.rs @@ -10,6 +10,7 @@ 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}; @@ -37,6 +38,28 @@ async fn setup(handle: tauri::AppHandle) -> Result<()> { // 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 || { + 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().await?; + Ok(ticket) + } + }) + } + }), + ) .spawn() .await?; handle.manage(AppState::new(node)); @@ -48,6 +71,7 @@ struct AppState { todos: Mutex)>>, iroh: IrohNode, } + impl AppState { fn new(iroh: IrohNode) -> Self { AppState { @@ -60,6 +84,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, @@ -140,7 +168,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())?; @@ -178,20 +206,20 @@ 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] @@ -200,7 +228,7 @@ async fn set_ticket( 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())?; @@ -214,8 +242,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..73ab784e 100644 --- a/tauri-todos/src-tauri/src/todos.rs +++ b/tauri-todos/src-tauri/src/todos.rs @@ -1,10 +1,16 @@ use anyhow::{bail, ensure, Context, Result}; use bytes::Bytes; +use futures_lite::future::Boxed; use futures_lite::{Stream, StreamExt}; +use iroh::base::ticket::Ticket; use iroh::client::docs::{Entry, LiveEvent, ShareMode}; use iroh::client::{docs::Doc, Iroh}; use iroh::docs::{AuthorId, DocTicket}; +use iroh::net::ticket::NodeTicket; +use iroh::net::NodeId; +use iroh::node::ProtocolHandler; use std::str::FromStr; +use std::sync::Arc; // use iroh::ticket::DocTicket; use serde::{Deserialize, Serialize}; @@ -55,34 +61,87 @@ const MAX_LABEL_LEN: usize = 2 * 1000; pub struct Todos { node: Iroh, doc: Doc, - ticket: DocTicket, author: AuthorId, } +type GetTicketFn = Arc Boxed> + Send + Sync + 'static>; + +#[derive(derive_more::Debug)] +pub struct CapExchangeProtocol { + #[debug(skip)] + get_ticket: GetTicketFn, +} + +impl CapExchangeProtocol { + pub const ALPN: &'static [u8] = b"iroh-tauri-todos/cap-request/0"; + + pub fn new( + get_ticket: impl Fn() -> Boxed> + Send + Sync + 'static, + ) -> Arc { + Arc::new(Self { + get_ticket: Arc::new(get_ticket), + }) + } +} + +impl ProtocolHandler for CapExchangeProtocol { + fn accept(self: Arc, conn: iroh::net::endpoint::Connecting) -> Boxed> { + Box::pin(async move { + let conn = conn.await?; + + let (mut send, mut recv) = conn.accept_bi().await?; + + let node_id = NodeId::from_bytes(&recv.read_to_end(1000).await?.try_into().map_err( + |v: Vec| anyhow::anyhow!("Expected 32 bytes, but got {}", v.len()), + )?)?; + println!("Got incoming cap request from {node_id}"); + + let ticket = (self.get_ticket)().await?; + send.write_all(&ticket.to_bytes()).await?; + send.finish().await?; + + let close = conn.closed().await; + println!("conn closed: {close:?}"); + + Ok(()) + }) + } +} + impl Todos { - pub async fn new(ticket: Option, node: Iroh) -> anyhow::Result { + pub async fn new( + ticket: Option, + node: Iroh, + endpoint: iroh::net::Endpoint, + ) -> 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)?; + Some(node_ticket) => { + let node_addr = NodeTicket::from_str(&node_ticket)?.node_addr().clone(); + let conn = endpoint + .connect(node_addr, CapExchangeProtocol::ALPN) + .await?; + let (mut send, mut recv) = conn.open_bi().await?; + send.write_all(node.net().node_id().await?.as_ref()).await?; + send.finish().await?; + let ticket_bytes = recv.read_to_end(1024 * 10).await?; + let ticket = DocTicket::from_bytes(&ticket_bytes)?; + conn.close(0u32.into(), b"bye!"); node.docs().import(ticket).await? } }; - let ticket = doc.share(ShareMode::Write, Default::default()).await?; + Ok(Todos { node, author, doc }) + } - Ok(Todos { - node, - author, - doc, - ticket, - }) + pub async fn ticket(&self) -> Result { + Ok(NodeTicket::new(self.node.net().node_addr().await?)?.to_string()) } - pub fn ticket(&self) -> String { - self.ticket.to_string() + pub async fn share_ticket(&self) -> Result { + self.doc.share(ShareMode::Write, Default::default()).await } pub async fn doc_subscribe(&self) -> Result>> { diff --git a/tauri-todos/src/App.tsx b/tauri-todos/src/App.tsx index d7664001..626f66ca 100644 --- a/tauri-todos/src/App.tsx +++ b/tauri-todos/src/App.tsx @@ -23,12 +23,12 @@ function App() { console.log("create list"); invoke('new_list').then(() => { console.log("in new_list and then"); - getTodos(); - setShowOpenList(false); + getTodos(); + setShowOpenList(false); }) } - function joinList(ticket:string) { + function joinList(ticket: string) { console.log("join list"); setTicket(ticket); getTodos(); From 270b4f6c58506e11c563c04d080fb004311da213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Fri, 30 Aug 2024 14:29:59 +0200 Subject: [PATCH 05/17] Update to new iroh --- tauri-todos/src-tauri/Cargo.lock | 494 ++++++++++++++++++++--------- tauri-todos/src-tauri/Cargo.toml | 15 +- tauri-todos/src-tauri/src/todos.rs | 6 +- 3 files changed, 354 insertions(+), 161 deletions(-) diff --git a/tauri-todos/src-tauri/Cargo.lock b/tauri-todos/src-tauri/Cargo.lock index 2fd48cdb..61331b20 100644 --- a/tauri-todos/src-tauri/Cargo.lock +++ b/tauri-todos/src-tauri/Cargo.lock @@ -51,7 +51,7 @@ dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -111,10 +111,11 @@ version = "0.1.0" dependencies = [ "anyhow", "bytes", + "data-encoding", "derive_more 1.0.0-beta.7", "futures-lite 2.3.0", "iroh", - "num_cpus", + "postcard", "serde", "serde_json", "tauri", @@ -156,9 +157,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", @@ -172,25 +173,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]] @@ -361,6 +362,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" @@ -810,26 +820,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" @@ -928,6 +918,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" @@ -1065,7 +1061,9 @@ dependencies = [ "curve25519-dalek-derive", "digest", "fiat-crypto", + "rand_core 0.6.4", "rustc_version", + "serde", "subtle", "zeroize", ] @@ -1148,9 +1146,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", @@ -1402,6 +1400,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" @@ -1447,6 +1451,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" @@ -2295,6 +2305,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" @@ -2320,6 +2339,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" @@ -2383,15 +2416,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", @@ -2597,12 +2656,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]] @@ -2836,8 +2895,7 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "iroh" version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24dfb8e552d56a5a4adae0226542a0d2c6d9645cf527708f37090ef3e877015a" +source = "git+https://github.com/n0-computer/iroh.git?branch=willow#9216fd64ec8f5d802bea25a9605f33af1c6cfc91" dependencies = [ "anyhow", "async-channel", @@ -2857,6 +2915,7 @@ dependencies = [ "iroh-metrics", "iroh-net", "iroh-quinn", + "iroh-willow", "nested_enum_utils", "num_cpus", "parking_lot", @@ -2881,8 +2940,7 @@ dependencies = [ [[package]] name = "iroh-base" version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8d60492b6099a5e94b674d0f4c564b3fa63a211836a090bc34c14d422721c19" +source = "git+https://github.com/n0-computer/iroh.git?branch=willow#9216fd64ec8f5d802bea25a9605f33af1c6cfc91" dependencies = [ "aead", "anyhow", @@ -2923,8 +2981,7 @@ dependencies = [ [[package]] name = "iroh-blobs" version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f44c07ea8e480ffe6b8d40dc029a73b68a312a6d021290d1afaaad8e332cc76b" +source = "git+https://github.com/n0-computer/iroh.git?branch=willow#9216fd64ec8f5d802bea25a9605f33af1c6cfc91" dependencies = [ "anyhow", "async-channel", @@ -2941,6 +2998,7 @@ dependencies = [ "iroh-io", "iroh-metrics", "iroh-net", + "iroh-quinn", "num_cpus", "oneshot", "parking_lot", @@ -2965,8 +3023,7 @@ dependencies = [ [[package]] name = "iroh-docs" version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f2515aabce975ef0fe2f93faa93ccf782d8294a8f61aeb1bcd517995b769ab4" +source = "git+https://github.com/n0-computer/iroh.git?branch=willow#9216fd64ec8f5d802bea25a9605f33af1c6cfc91" dependencies = [ "anyhow", "async-channel", @@ -3004,8 +3061,7 @@ dependencies = [ [[package]] name = "iroh-gossip" version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11551a79338d0d3aac1cff957a8801a2c3ffccce59fa1f3d60ca183aa785edab" +source = "git+https://github.com/n0-computer/iroh.git?branch=willow#9216fd64ec8f5d802bea25a9605f33af1c6cfc91" dependencies = [ "anyhow", "async-channel", @@ -3045,8 +3101,7 @@ dependencies = [ [[package]] name = "iroh-metrics" version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0be681eb052594a43afa8d2d2cc093361917c7348c779df3fe92a7169bd2f9c5" +source = "git+https://github.com/n0-computer/iroh.git?branch=willow#9216fd64ec8f5d802bea25a9605f33af1c6cfc91" dependencies = [ "anyhow", "erased_set", @@ -3066,8 +3121,7 @@ dependencies = [ [[package]] name = "iroh-net" version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b18cd649ce3a342d8480038abee8170e483bc3bec8f111bf57ca9679c5075ce" +source = "git+https://github.com/n0-computer/iroh.git?branch=willow#9216fd64ec8f5d802bea25a9605f33af1c6cfc91" dependencies = [ "anyhow", "backoff", @@ -3084,7 +3138,7 @@ dependencies = [ "genawaiter", "governor", "hex", - "hickory-proto", + "hickory-proto 0.25.0-alpha.2", "hickory-resolver", "hostname", "http 1.1.0", @@ -3114,8 +3168,8 @@ dependencies = [ "reqwest 0.12.5", "ring", "rtnetlink", - "rustls 0.21.12", - "rustls-webpki 0.101.7", + "rustls", + "rustls-webpki", "serde", "smallvec", "socket2", @@ -3126,7 +3180,7 @@ dependencies = [ "thiserror", "time", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls", "tokio-stream", "tokio-tungstenite", "tokio-tungstenite-wasm", @@ -3135,7 +3189,7 @@ dependencies = [ "tungstenite", "url", "watchable", - "webpki-roots 0.25.4", + "webpki-roots", "windows 0.51.1", "wmi", "x509-parser", @@ -3144,16 +3198,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", @@ -3161,16 +3216,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", @@ -3179,15 +3234,56 @@ 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.23.0" +source = "git+https://github.com/n0-computer/iroh.git?branch=willow#9216fd64ec8f5d802bea25a9605f33af1c6cfc91" +dependencies = [ + "anyhow", + "bytes", + "curve25519-dalek", + "derive_more 1.0.0-beta.7", + "ed25519-dalek", + "futures-buffered", + "futures-concurrency", + "futures-lite 2.3.0", + "futures-util", + "genawaiter", + "hex", + "iroh-base", + "iroh-blobs", + "iroh-io", + "iroh-metrics", + "iroh-net", + "meadowcap", + "postcard", + "rand 0.8.5", + "rand_core 0.6.4", + "redb 2.1.1", + "serde", + "sha2", + "strum 0.26.3", + "syncify", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "ufotofu", + "willow-data-model", + "willow-encoding", + "zerocopy 0.8.0-alpha.17", ] [[package]] @@ -3225,6 +3321,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" @@ -3475,12 +3585,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" @@ -4057,9 +4186,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", ] @@ -4699,22 +4828,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", @@ -4733,7 +4863,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -4909,9 +5039,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", @@ -4925,6 +5055,7 @@ dependencies = [ "iroh-quinn", "pin-project", "serde", + "slab", "tokio", "tokio-serde", "tokio-util", @@ -4933,9 +5064,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", @@ -4986,8 +5117,8 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.0.0", - "rustls 0.23.12", + "rustc-hash", + "rustls", "socket2", "thiserror", "tokio", @@ -5003,8 +5134,8 @@ dependencies = [ "bytes", "rand 0.8.5", "ring", - "rustc-hash 2.0.0", - "rustls 0.23.12", + "rustc-hash", + "rustls", "slab", "thiserror", "tinyvec", @@ -5363,7 +5494,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.12", + "rustls", "rustls-pemfile 2.1.3", "rustls-pki-types", "serde", @@ -5371,13 +5502,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", ] @@ -5485,12 +5616,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" @@ -5528,18 +5653,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" @@ -5550,19 +5663,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", ] @@ -5593,15 +5707,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" @@ -5664,16 +5795,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" @@ -5698,6 +5819,7 @@ dependencies = [ "core-foundation", "core-foundation-sys", "libc", + "num-bigint", "security-framework-sys", ] @@ -6310,7 +6432,7 @@ checksum = "39769914108ae68e261d85ceac7bce7095947130f79c29d4535e9b31fc702a40" dependencies = [ "acto", "anyhow", - "hickory-proto", + "hickory-proto 0.24.1", "rand 0.8.5", "socket2", "tokio", @@ -6362,16 +6484,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]] @@ -6480,7 +6612,7 @@ dependencies = [ "gtk", "image 0.24.9", "instant", - "jni", + "jni 0.20.0", "lazy_static", "libc", "log", @@ -6902,23 +7034,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", ] @@ -7252,6 +7374,23 @@ dependencies = [ "winapi", ] +[[package]] +name = "ufotofu" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c773ca845a07603d8ebe7292a3cefa20bf41305ce91246332c7fc1e3029eecaa" +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" @@ -7310,10 +7449,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]] @@ -7651,12 +7790,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" @@ -7716,6 +7849,28 @@ 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 = "winapi" version = "0.3.9" @@ -8364,6 +8519,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" @@ -8451,9 +8615,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", @@ -8586,7 +8750,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.0-alpha.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da056c7307048e30bce8d625c6f0633366d31f1086b3c87ed9b1f18fa1081cb1" +dependencies = [ + "zerocopy-derive 0.8.0-alpha.17", ] [[package]] @@ -8600,6 +8773,17 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.0-alpha.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eb22123403bf9c05af423e2ced336a5fc2853df9179b42bea8144d6bf497a57" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "zeroize" version = "1.8.1" diff --git a/tauri-todos/src-tauri/Cargo.toml b/tauri-todos/src-tauri/Cargo.toml index b0529988..605ecc54 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,12 +18,13 @@ 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 = "willow" } 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" [features] # by default Tauri runs in production mode @@ -32,3 +33,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/todos.rs b/tauri-todos/src-tauri/src/todos.rs index 73ab784e..08582a2e 100644 --- a/tauri-todos/src-tauri/src/todos.rs +++ b/tauri-todos/src-tauri/src/todos.rs @@ -98,7 +98,7 @@ impl ProtocolHandler for CapExchangeProtocol { let ticket = (self.get_ticket)().await?; send.write_all(&ticket.to_bytes()).await?; - send.finish().await?; + send.finish()?; let close = conn.closed().await; println!("conn closed: {close:?}"); @@ -125,10 +125,10 @@ impl Todos { .await?; let (mut send, mut recv) = conn.open_bi().await?; send.write_all(node.net().node_id().await?.as_ref()).await?; - send.finish().await?; + send.finish()?; let ticket_bytes = recv.read_to_end(1024 * 10).await?; let ticket = DocTicket::from_bytes(&ticket_bytes)?; - conn.close(0u32.into(), b"bye!"); + conn.close(0u32.into(), b"thanks for the ticket!"); node.docs().import(ticket).await? } }; From 3db9f9a6b82464ba7c432410e3a49055e9c8a09b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Fri, 30 Aug 2024 15:09:19 +0200 Subject: [PATCH 06/17] crappy version that doesn't sync yet --- tauri-todos/src-tauri/Cargo.lock | 1 + tauri-todos/src-tauri/Cargo.toml | 1 + tauri-todos/src-tauri/src/main.rs | 64 ++++++++--------- tauri-todos/src-tauri/src/todos.rs | 111 ++++++++++++++++++----------- 4 files changed, 105 insertions(+), 72 deletions(-) diff --git a/tauri-todos/src-tauri/Cargo.lock b/tauri-todos/src-tauri/Cargo.lock index 61331b20..09826c0f 100644 --- a/tauri-todos/src-tauri/Cargo.lock +++ b/tauri-todos/src-tauri/Cargo.lock @@ -121,6 +121,7 @@ dependencies = [ "tauri", "tauri-build", "tokio", + "tokio-stream", "tokio-util", ] diff --git a/tauri-todos/src-tauri/Cargo.toml b/tauri-todos/src-tauri/Cargo.toml index 605ecc54..c12643bf 100644 --- a/tauri-todos/src-tauri/Cargo.toml +++ b/tauri-todos/src-tauri/Cargo.toml @@ -25,6 +25,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" [features] # by default Tauri runs in production mode diff --git a/tauri-todos/src-tauri/src/main.rs b/tauri-todos/src-tauri/src/main.rs index 2da15804..1bbf9779 100644 --- a/tauri-todos/src-tauri/src/main.rs +++ b/tauri-todos/src-tauri/src/main.rs @@ -4,7 +4,7 @@ )] mod todos; -use anyhow::{anyhow, Result}; +use anyhow::Result; use futures_lite::StreamExt; use iroh::client::docs::LiveEvent; use iroh::client::Iroh; @@ -16,35 +16,34 @@ use tokio::sync::Mutex; use self::todos::{Todo, Todos}; // this example uses a persistend iroh node stored in the application data directory -type IrohNode = iroh::node::Node; +type IrohNode = iroh::node::Node; // setup an iroh node async fn setup(handle: tauri::AppHandle) -> Result<()> { - let iroh_data_dir = std::env::var("IROH_DATA_DIR") - .ok() - .map(std::path::PathBuf::from); - - // get the applicaiton 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!("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 applicaiton 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? + let node = iroh::node::Node::memory() .build() .await? .accept( CapExchangeProtocol::ALPN, CapExchangeProtocol::new({ let handle = handle.clone(); - move || { + move |user_id| { Box::pin({ let handle = handle.clone(); async move { @@ -53,7 +52,7 @@ async fn setup(handle: tauri::AppHandle) -> Result<()> { let Some((todos, _)) = &mut *state.todos.lock().await else { anyhow::bail!("Not initialized"); }; - let ticket = todos.share_ticket().await?; + let ticket = todos.share_ticket(user_id).await?; Ok(ticket) } }) @@ -96,18 +95,19 @@ 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(); - } - _ => {} - } + app_handle.emit_all("update-all", ()).ok(); + // 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(); + // } + // _ => {} + // } } }); diff --git a/tauri-todos/src-tauri/src/todos.rs b/tauri-todos/src-tauri/src/todos.rs index 08582a2e..3fac7fc6 100644 --- a/tauri-todos/src-tauri/src/todos.rs +++ b/tauri-todos/src-tauri/src/todos.rs @@ -2,17 +2,19 @@ use anyhow::{bail, ensure, Context, Result}; use bytes::Bytes; use futures_lite::future::Boxed; use futures_lite::{Stream, StreamExt}; -use iroh::base::ticket::Ticket; -use iroh::client::docs::{Entry, LiveEvent, ShareMode}; -use iroh::client::{docs::Doc, Iroh}; -use iroh::docs::{AuthorId, DocTicket}; +use iroh::client::docs::LiveEvent; +use iroh::client::spaces::{EntryForm, Space, SpaceTicket}; +use iroh::client::Iroh; use iroh::net::ticket::NodeTicket; -use iroh::net::NodeId; use iroh::node::ProtocolHandler; +use iroh::spaces::interest::RestrictArea; +use iroh::spaces::proto::data_model::{AuthorisedEntry, Component, Path}; +use iroh::spaces::proto::grouping::{Range, Range3d}; +use iroh::spaces::proto::keys::{NamespaceKind, UserId}; +use iroh::spaces::proto::meadowcap::AccessMode; +use serde::{Deserialize, Serialize}; use std::str::FromStr; use std::sync::Arc; -// use iroh::ticket::DocTicket; -use serde::{Deserialize, Serialize}; /// Todo in a list of todos. #[derive(Clone, Debug, Serialize, Deserialize)] @@ -60,11 +62,11 @@ 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, - author: AuthorId, + space: Space, + user: UserId, } -type GetTicketFn = Arc Boxed> + Send + Sync + 'static>; +type GetTicketFn = Arc Boxed> + Send + Sync + 'static>; #[derive(derive_more::Debug)] pub struct CapExchangeProtocol { @@ -76,7 +78,7 @@ impl CapExchangeProtocol { pub const ALPN: &'static [u8] = b"iroh-tauri-todos/cap-request/0"; pub fn new( - get_ticket: impl Fn() -> Boxed> + Send + Sync + 'static, + get_ticket: impl Fn(UserId) -> Boxed> + Send + Sync + 'static, ) -> Arc { Arc::new(Self { get_ticket: Arc::new(get_ticket), @@ -91,13 +93,17 @@ impl ProtocolHandler for CapExchangeProtocol { let (mut send, mut recv) = conn.accept_bi().await?; - let node_id = NodeId::from_bytes(&recv.read_to_end(1000).await?.try_into().map_err( - |v: Vec| anyhow::anyhow!("Expected 32 bytes, but got {}", v.len()), - )?)?; - println!("Got incoming cap request from {node_id}"); - - let ticket = (self.get_ticket)().await?; - send.write_all(&ticket.to_bytes()).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.get_ticket)(user_id).await?; + let ticket_bytes = postcard::to_allocvec(&ticket)?; + send.write_all(&ticket_bytes).await?; send.finish()?; let close = conn.closed().await; @@ -110,42 +116,51 @@ impl ProtocolHandler for CapExchangeProtocol { impl Todos { pub async fn new( - ticket: Option, + node_ticket: Option, node: Iroh, endpoint: iroh::net::Endpoint, ) -> anyhow::Result { - let author = node.authors().create().await?; + let user = node.spaces().create_user().await?; - let doc = match ticket { - None => node.docs().create().await?, + let space = match node_ticket { + None => node.spaces().create(NamespaceKind::Owned, user).await?, Some(node_ticket) => { let node_addr = NodeTicket::from_str(&node_ticket)?.node_addr().clone(); let conn = endpoint .connect(node_addr, CapExchangeProtocol::ALPN) .await?; let (mut send, mut recv) = conn.open_bi().await?; - send.write_all(node.net().node_id().await?.as_ref()).await?; + send.write_all(user.as_bytes()).await?; send.finish()?; let ticket_bytes = recv.read_to_end(1024 * 10).await?; - let ticket = DocTicket::from_bytes(&ticket_bytes)?; + let ticket: SpaceTicket = postcard::from_bytes(&ticket_bytes)?; conn.close(0u32.into(), b"thanks for the ticket!"); - node.docs().import(ticket).await? + let (space, sync) = node.spaces().import_and_sync(ticket).await?; + let completion = sync.complete_all().await; + println!("Completed sync: {completion:#?}"); + space } }; - Ok(Todos { node, author, doc }) + Ok(Todos { node, user, space }) } pub async fn ticket(&self) -> Result { Ok(NodeTicket::new(self.node.net().node_addr().await?)?.to_string()) } - pub async fn share_ticket(&self) -> Result { - self.doc.share(ShareMode::Write, Default::default()).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>> { - self.doc.subscribe().await + // self.doc.subscribe().await + // TODO: We need a working .subscribe() fn + let interval = tokio::time::interval(std::time::Duration::from_millis(100)); + let intervals = tokio_stream::wrappers::IntervalStream::new(interval); + Ok(intervals.map(|_| Ok(LiveEvent::PendingContentReady))) } pub async fn add(&mut self, id: String, label: String) -> anyhow::Result<()> { @@ -188,10 +203,7 @@ 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 mut entries = self.space.get_many(Range3d::new_full()).await?; let mut todos = Vec::new(); while let Some(entry) = entries.next().await { @@ -206,8 +218,11 @@ impl 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::new(self.user, Self::to_willow_path(key)?), + content, + ) .await?; Ok(()) } @@ -219,8 +234,12 @@ 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)) + .space + .get_many(Range3d::new( + Range::full(), + Range::new_open(Self::to_willow_path(&id)?), + Range::full(), + )) .await? .next() .await @@ -229,11 +248,23 @@ impl Todos { self.todo_from_entry(&entry).await } - 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 { + async fn todo_from_entry(&self, entry: &AuthorisedEntry) -> anyhow::Result { + let entry = entry.entry(); + let key_component = entry + .path() + .get_component(0) + .ok_or_else(|| anyhow::anyhow!("path component missing"))?; + let id = String::from_utf8(key_component.to_vec()).context("invalid key")?; + 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::new_singleton( + Component::new(key.as_ref()).ok_or_else(|| anyhow::anyhow!("invalid component"))?, + )?) + } } From 42a86376bc3c667a076d590532ebad7e1dad230b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Fri, 30 Aug 2024 15:21:59 +0200 Subject: [PATCH 07/17] refactor `CapExchangeProtocol` a bit --- tauri-todos/src-tauri/src/main.rs | 2 +- tauri-todos/src-tauri/src/todos.rs | 72 +++++++++++++++++------------- 2 files changed, 43 insertions(+), 31 deletions(-) diff --git a/tauri-todos/src-tauri/src/main.rs b/tauri-todos/src-tauri/src/main.rs index 1bbf9779..eb1e7b80 100644 --- a/tauri-todos/src-tauri/src/main.rs +++ b/tauri-todos/src-tauri/src/main.rs @@ -24,7 +24,7 @@ async fn setup(handle: tauri::AppHandle) -> Result<()> { // .ok() // .map(std::path::PathBuf::from); - // // get the applicaiton data root, join with "iroh_data" to get the data root for the iroh node + // // 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 diff --git a/tauri-todos/src-tauri/src/todos.rs b/tauri-todos/src-tauri/src/todos.rs index 3fac7fc6..461e0d7e 100644 --- a/tauri-todos/src-tauri/src/todos.rs +++ b/tauri-todos/src-tauri/src/todos.rs @@ -6,6 +6,7 @@ use iroh::client::docs::LiveEvent; 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::interest::RestrictArea; use iroh::spaces::proto::data_model::{AuthorisedEntry, Component, Path}; @@ -84,33 +85,52 @@ impl CapExchangeProtocol { get_ticket: Arc::new(get_ticket), }) } -} -impl ProtocolHandler for CapExchangeProtocol { - fn accept(self: Arc, conn: iroh::net::endpoint::Connecting) -> Boxed> { - Box::pin(async move { - let conn = conn.await?; + pub async fn request( + endpoint: &iroh::net::Endpoint, + node_addr: NodeAddr, + user: UserId, + ) -> Result { + 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()?; + let ticket_bytes = recv.read_to_end(1024 * 10).await?; + let ticket: SpaceTicket = postcard::from_bytes(&ticket_bytes)?; + conn.close(0u32.into(), b"thanks for the ticket!"); + Ok(ticket) + } - let (mut send, mut recv) = conn.accept_bi().await?; + pub async fn respond(self: Arc, conn: iroh::net::endpoint::Connecting) -> Result<()> { + let conn = conn.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 (mut send, mut recv) = conn.accept_bi().await?; - let ticket = (self.get_ticket)(user_id).await?; - let ticket_bytes = postcard::to_allocvec(&ticket)?; - send.write_all(&ticket_bytes).await?; - send.finish()?; + 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 close = conn.closed().await; - println!("conn closed: {close:?}"); + let ticket = (self.get_ticket)(user_id).await?; + let ticket_bytes = postcard::to_allocvec(&ticket)?; + send.write_all(&ticket_bytes).await?; + send.finish()?; - Ok(()) - }) + 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)) } } @@ -126,15 +146,7 @@ impl Todos { None => node.spaces().create(NamespaceKind::Owned, user).await?, Some(node_ticket) => { let node_addr = NodeTicket::from_str(&node_ticket)?.node_addr().clone(); - 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()?; - let ticket_bytes = recv.read_to_end(1024 * 10).await?; - let ticket: SpaceTicket = postcard::from_bytes(&ticket_bytes)?; - conn.close(0u32.into(), b"thanks for the ticket!"); + let ticket = CapExchangeProtocol::request(&endpoint, node_addr, user).await?; let (space, sync) = node.spaces().import_and_sync(ticket).await?; let completion = sync.complete_all().await; println!("Completed sync: {completion:#?}"); From e3deb4ccd96675eb24759351e4e6256da6f964b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Fri, 30 Aug 2024 15:31:23 +0200 Subject: [PATCH 08/17] Actually sync continuously --- tauri-todos/src-tauri/src/main.rs | 4 +- tauri-todos/src-tauri/src/todos.rs | 165 ++++++++++++++++------------- 2 files changed, 94 insertions(+), 75 deletions(-) diff --git a/tauri-todos/src-tauri/src/main.rs b/tauri-todos/src-tauri/src/main.rs index eb1e7b80..e2e79a3b 100644 --- a/tauri-todos/src-tauri/src/main.rs +++ b/tauri-todos/src-tauri/src/main.rs @@ -6,9 +6,7 @@ mod todos; 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; @@ -94,7 +92,7 @@ impl AppState { ) -> Result<()> { let mut events = todos.doc_subscribe().await?; let events_handle = tokio::spawn(async move { - while let Some(Ok(event)) = events.next().await { + while let Some(Ok(_event)) = events.next().await { app_handle.emit_all("update-all", ()).ok(); // match event { // LiveEvent::InsertRemote { content_status, .. } => { diff --git a/tauri-todos/src-tauri/src/todos.rs b/tauri-todos/src-tauri/src/todos.rs index 461e0d7e..d091e46c 100644 --- a/tauri-todos/src-tauri/src/todos.rs +++ b/tauri-todos/src-tauri/src/todos.rs @@ -8,7 +8,7 @@ use iroh::client::Iroh; use iroh::net::ticket::NodeTicket; use iroh::net::NodeAddr; use iroh::node::ProtocolHandler; -use iroh::spaces::interest::RestrictArea; +use iroh::spaces::interest::{AreaOfInterestSelector, RestrictArea}; use iroh::spaces::proto::data_model::{AuthorisedEntry, Component, Path}; use iroh::spaces::proto::grouping::{Range, Range3d}; use iroh::spaces::proto::keys::{NamespaceKind, UserId}; @@ -16,6 +16,7 @@ use iroh::spaces::proto::meadowcap::AccessMode; use serde::{Deserialize, Serialize}; use std::str::FromStr; use std::sync::Arc; +use tokio::task::JoinHandle; /// Todo in a list of todos. #[derive(Clone, Debug, Serialize, Deserialize)] @@ -65,73 +66,7 @@ pub struct Todos { node: Iroh, space: Space, user: UserId, -} - -type GetTicketFn = Arc Boxed> + Send + Sync + 'static>; - -#[derive(derive_more::Debug)] -pub struct CapExchangeProtocol { - #[debug(skip)] - get_ticket: GetTicketFn, -} - -impl CapExchangeProtocol { - pub const ALPN: &'static [u8] = b"iroh-tauri-todos/cap-request/0"; - - pub fn new( - get_ticket: impl Fn(UserId) -> Boxed> + Send + Sync + 'static, - ) -> Arc { - Arc::new(Self { - get_ticket: Arc::new(get_ticket), - }) - } - - pub async fn request( - endpoint: &iroh::net::Endpoint, - node_addr: NodeAddr, - user: UserId, - ) -> Result { - 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()?; - let ticket_bytes = recv.read_to_end(1024 * 10).await?; - let ticket: SpaceTicket = postcard::from_bytes(&ticket_bytes)?; - 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.get_ticket)(user_id).await?; - let ticket_bytes = postcard::to_allocvec(&ticket)?; - send.write_all(&ticket_bytes).await?; - send.finish()?; - - 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)) - } + _sync: Option>>, } impl Todos { @@ -142,19 +77,38 @@ impl Todos { ) -> anyhow::Result { let user = node.spaces().create_user().await?; - let space = match node_ticket { - None => node.spaces().create(NamespaceKind::Owned, user).await?, + let (space, sync) = match node_ticket { + None => ( + node.spaces().create(NamespaceKind::Owned, user).await?, + None, + ), Some(node_ticket) => { let node_addr = NodeTicket::from_str(&node_ticket)?.node_addr().clone(); + let node_id = node_addr.node_id; let ticket = CapExchangeProtocol::request(&endpoint, node_addr, user).await?; let (space, sync) = node.spaces().import_and_sync(ticket).await?; let completion = sync.complete_all().await; println!("Completed sync: {completion:#?}"); - space + let mut sync = space + .sync_continuously(node_id, AreaOfInterestSelector::Widest) + .await?; + let handle = tokio::spawn(async move { + while let Some(ev) = sync.next().await { + println!("Got sync event: {ev:?}"); + } + + anyhow::Ok(()) + }); + (space, Some(handle)) } }; - Ok(Todos { node, user, space }) + Ok(Todos { + node, + user, + space, + _sync: sync, + }) } pub async fn ticket(&self) -> Result { @@ -280,3 +234,70 @@ impl Todos { )?) } } + +type GetTicketFn = Arc Boxed> + Send + Sync + 'static>; + +#[derive(derive_more::Debug)] +pub struct CapExchangeProtocol { + #[debug(skip)] + get_ticket: GetTicketFn, +} + +impl CapExchangeProtocol { + pub const ALPN: &'static [u8] = b"iroh-tauri-todos/cap-request/0"; + + pub fn new( + get_ticket: impl Fn(UserId) -> Boxed> + Send + Sync + 'static, + ) -> Arc { + Arc::new(Self { + get_ticket: Arc::new(get_ticket), + }) + } + + pub async fn request( + endpoint: &iroh::net::Endpoint, + node_addr: NodeAddr, + user: UserId, + ) -> Result { + 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()?; + let ticket_bytes = recv.read_to_end(1024 * 10).await?; + let ticket: SpaceTicket = postcard::from_bytes(&ticket_bytes)?; + 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.get_ticket)(user_id).await?; + let ticket_bytes = postcard::to_allocvec(&ticket)?; + send.write_all(&ticket_bytes).await?; + send.finish()?; + + 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)) + } +} From 519f893c43692e0f577a601e1849375a0f4d5a9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Tue, 3 Sep 2024 14:49:22 +0200 Subject: [PATCH 09/17] Checkpoint: Non-working version with `willow` branch --- tauri-todos/src-tauri/Cargo.lock | 5 ++- tauri-todos/src-tauri/Cargo.toml | 1 + tauri-todos/src-tauri/src/main.rs | 23 ++++------ tauri-todos/src-tauri/src/todos.rs | 62 ++++++++++++++++---------- tauri-todos/src/App.tsx | 36 +++++++-------- tauri-todos/src/component/OpenList.tsx | 4 +- tauri-todos/src/component/TodoList.tsx | 3 ++ 7 files changed, 71 insertions(+), 63 deletions(-) diff --git a/tauri-todos/src-tauri/Cargo.lock b/tauri-todos/src-tauri/Cargo.lock index 09826c0f..e12055bc 100644 --- a/tauri-todos/src-tauri/Cargo.lock +++ b/tauri-todos/src-tauri/Cargo.lock @@ -123,6 +123,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-util", + "tracing-subscriber", ] [[package]] @@ -7377,9 +7378,9 @@ dependencies = [ [[package]] name = "ufotofu" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c773ca845a07603d8ebe7292a3cefa20bf41305ce91246332c7fc1e3029eecaa" +checksum = "530b5dd047cf0fbc98b5138a17419d9f914de9b98971b6ac8a1b9e2050b711ef" dependencies = [ "either", "ufotofu_queues", diff --git a/tauri-todos/src-tauri/Cargo.toml b/tauri-todos/src-tauri/Cargo.toml index c12643bf..3d8740f2 100644 --- a/tauri-todos/src-tauri/Cargo.toml +++ b/tauri-todos/src-tauri/Cargo.toml @@ -26,6 +26,7 @@ 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"] } [features] # by default Tauri runs in production mode diff --git a/tauri-todos/src-tauri/src/main.rs b/tauri-todos/src-tauri/src/main.rs index e2e79a3b..fe7e73bb 100644 --- a/tauri-todos/src-tauri/src/main.rs +++ b/tauri-todos/src-tauri/src/main.rs @@ -92,27 +92,18 @@ impl AppState { ) -> Result<()> { let mut events = todos.doc_subscribe().await?; let events_handle = tokio::spawn(async move { - while let Some(Ok(_event)) = events.next().await { + while let Some(Ok(event)) = events.next().await { + println!("Got an event {event:?}"); app_handle.emit_all("update-all", ()).ok(); - // 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(); - // } - // _ => {} - // } } }); 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(()) @@ -120,6 +111,8 @@ impl AppState { } fn main() { + tracing_subscriber::fmt::init(); + tauri::Builder::default() .setup(|app| { let handle = app.handle(); @@ -146,7 +139,7 @@ fn main() { toggle_done, update_todo, delete, - set_ticket, + join_list, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); @@ -221,7 +214,7 @@ async fn delete(id: String, state: tauri::State<'_, AppState>) -> Result, diff --git a/tauri-todos/src-tauri/src/todos.rs b/tauri-todos/src-tauri/src/todos.rs index d091e46c..eaa84d77 100644 --- a/tauri-todos/src-tauri/src/todos.rs +++ b/tauri-todos/src-tauri/src/todos.rs @@ -2,7 +2,6 @@ use anyhow::{bail, ensure, Context, Result}; use bytes::Bytes; use futures_lite::future::Boxed; use futures_lite::{Stream, StreamExt}; -use iroh::client::docs::LiveEvent; use iroh::client::spaces::{EntryForm, Space, SpaceTicket}; use iroh::client::Iroh; use iroh::net::ticket::NodeTicket; @@ -10,13 +9,14 @@ use iroh::net::NodeAddr; use iroh::node::ProtocolHandler; use iroh::spaces::interest::{AreaOfInterestSelector, RestrictArea}; use iroh::spaces::proto::data_model::{AuthorisedEntry, Component, Path}; -use iroh::spaces::proto::grouping::{Range, Range3d}; +use iroh::spaces::proto::grouping::{Area, Range, Range3d}; use iroh::spaces::proto::keys::{NamespaceKind, UserId}; use iroh::spaces::proto::meadowcap::AccessMode; use serde::{Deserialize, Serialize}; use std::str::FromStr; use std::sync::Arc; -use tokio::task::JoinHandle; +use tokio::task::JoinSet; +use tokio_stream::wrappers::IntervalStream; /// Todo in a list of todos. #[derive(Clone, Debug, Serialize, Deserialize)] @@ -61,12 +61,15 @@ impl Todo { const MAX_TODO_SIZE: usize = 2 * 1024; const MAX_LABEL_LEN: usize = 2 * 1000; +#[derive(Debug, Clone)] +pub struct StoreEvent; + /// List of todos, including completed todos that have not been archived pub struct Todos { node: Iroh, space: Space, user: UserId, - _sync: Option>>, + tasks: JoinSet>, } impl Todos { @@ -77,29 +80,29 @@ impl Todos { ) -> anyhow::Result { let user = node.spaces().create_user().await?; - let (space, sync) = match node_ticket { + let (space, tasks) = match node_ticket { None => ( node.spaces().create(NamespaceKind::Owned, user).await?, - None, + JoinSet::new(), ), Some(node_ticket) => { let node_addr = NodeTicket::from_str(&node_ticket)?.node_addr().clone(); let node_id = node_addr.node_id; let ticket = CapExchangeProtocol::request(&endpoint, node_addr, user).await?; + println!("Importing & Syncing"); let (space, sync) = node.spaces().import_and_sync(ticket).await?; - let completion = sync.complete_all().await; - println!("Completed sync: {completion:#?}"); + sync.complete_all().await; + let mut tasks = JoinSet::new(); let mut sync = space .sync_continuously(node_id, AreaOfInterestSelector::Widest) .await?; - let handle = tokio::spawn(async move { + tasks.spawn(async move { while let Some(ev) = sync.next().await { - println!("Got sync event: {ev:?}"); + println!("=== SYNC EVENT === {ev:?}"); } - anyhow::Ok(()) }); - (space, Some(handle)) + (space, tasks) } }; @@ -107,7 +110,7 @@ impl Todos { node, user, space, - _sync: sync, + tasks, }) } @@ -117,16 +120,23 @@ impl Todos { pub async fn share_ticket(&self, user: UserId) -> Result { self.space - .share(user, AccessMode::Write, RestrictArea::None) + .share( + user, + AccessMode::Write, + RestrictArea::Restrict(Area::new_subspace(user)), + ) .await } - pub async fn doc_subscribe(&self) -> Result>> { - // self.doc.subscribe().await - // TODO: We need a working .subscribe() fn - let interval = tokio::time::interval(std::time::Duration::from_millis(100)); - let intervals = tokio_stream::wrappers::IntervalStream::new(interval); - Ok(intervals.map(|_| Ok(LiveEvent::PendingContentReady))) + pub async fn doc_subscribe(&self) -> Result>> { + // let stream = self + // .space + // .subscribe_area(Area::new_full(), Default::default()) + // .await?; + let stream = + IntervalStream::new(tokio::time::interval(std::time::Duration::from_millis(50))) + .map(|_| Ok(StoreEvent)); + Ok(stream) } pub async fn add(&mut self, id: String, label: String) -> anyhow::Result<()> { @@ -240,17 +250,17 @@ type GetTicketFn = Arc Boxed> + Send + Syn #[derive(derive_more::Debug)] pub struct CapExchangeProtocol { #[debug(skip)] - get_ticket: GetTicketFn, + build_ticket: GetTicketFn, } impl CapExchangeProtocol { pub const ALPN: &'static [u8] = b"iroh-tauri-todos/cap-request/0"; pub fn new( - get_ticket: impl Fn(UserId) -> Boxed> + Send + Sync + 'static, + build_ticket: impl Fn(UserId) -> Boxed> + Send + Sync + 'static, ) -> Arc { Arc::new(Self { - get_ticket: Arc::new(get_ticket), + build_ticket: Arc::new(build_ticket), }) } @@ -259,14 +269,17 @@ impl CapExchangeProtocol { 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) } @@ -284,10 +297,11 @@ impl CapExchangeProtocol { let user_id = UserId::from(bytes); println!("Got incoming cap request from {user_id}"); - let ticket = (self.get_ticket)(user_id).await?; + 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:?}"); diff --git a/tauri-todos/src/App.tsx b/tauri-todos/src/App.tsx index 626f66ca..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); }) }, []) From 8bd0728f798e482dc572198c0245c56e534a866a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Tue, 3 Sep 2024 14:50:41 +0200 Subject: [PATCH 10/17] Working version with `willow` branch --- tauri-todos/src-tauri/src/todos.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tauri-todos/src-tauri/src/todos.rs b/tauri-todos/src-tauri/src/todos.rs index eaa84d77..1c60468c 100644 --- a/tauri-todos/src-tauri/src/todos.rs +++ b/tauri-todos/src-tauri/src/todos.rs @@ -120,11 +120,7 @@ impl Todos { pub async fn share_ticket(&self, user: UserId) -> Result { self.space - .share( - user, - AccessMode::Write, - RestrictArea::Restrict(Area::new_subspace(user)), - ) + .share(user, AccessMode::Write, RestrictArea::None) .await } From a9f7277173fd5ae329a92b38e4140dbd87b63fcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Tue, 3 Sep 2024 14:54:11 +0200 Subject: [PATCH 11/17] Actually syncing version with subscriptions branch --- tauri-todos/src-tauri/Cargo.lock | 16 ++++++++-------- tauri-todos/src-tauri/Cargo.toml | 2 +- tauri-todos/src-tauri/src/todos.rs | 29 +++++++++++------------------ 3 files changed, 20 insertions(+), 27 deletions(-) diff --git a/tauri-todos/src-tauri/Cargo.lock b/tauri-todos/src-tauri/Cargo.lock index e12055bc..f8deb305 100644 --- a/tauri-todos/src-tauri/Cargo.lock +++ b/tauri-todos/src-tauri/Cargo.lock @@ -2897,7 +2897,7 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "iroh" version = "0.23.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=willow#9216fd64ec8f5d802bea25a9605f33af1c6cfc91" +source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-event-subscriptions#a2d8316cc5cb4de45ba64bd7a98fe98d870765c9" dependencies = [ "anyhow", "async-channel", @@ -2942,7 +2942,7 @@ dependencies = [ [[package]] name = "iroh-base" version = "0.23.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=willow#9216fd64ec8f5d802bea25a9605f33af1c6cfc91" +source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-event-subscriptions#a2d8316cc5cb4de45ba64bd7a98fe98d870765c9" dependencies = [ "aead", "anyhow", @@ -2983,7 +2983,7 @@ dependencies = [ [[package]] name = "iroh-blobs" version = "0.23.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=willow#9216fd64ec8f5d802bea25a9605f33af1c6cfc91" +source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-event-subscriptions#a2d8316cc5cb4de45ba64bd7a98fe98d870765c9" dependencies = [ "anyhow", "async-channel", @@ -3025,7 +3025,7 @@ dependencies = [ [[package]] name = "iroh-docs" version = "0.23.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=willow#9216fd64ec8f5d802bea25a9605f33af1c6cfc91" +source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-event-subscriptions#a2d8316cc5cb4de45ba64bd7a98fe98d870765c9" dependencies = [ "anyhow", "async-channel", @@ -3063,7 +3063,7 @@ dependencies = [ [[package]] name = "iroh-gossip" version = "0.23.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=willow#9216fd64ec8f5d802bea25a9605f33af1c6cfc91" +source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-event-subscriptions#a2d8316cc5cb4de45ba64bd7a98fe98d870765c9" dependencies = [ "anyhow", "async-channel", @@ -3103,7 +3103,7 @@ dependencies = [ [[package]] name = "iroh-metrics" version = "0.23.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=willow#9216fd64ec8f5d802bea25a9605f33af1c6cfc91" +source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-event-subscriptions#a2d8316cc5cb4de45ba64bd7a98fe98d870765c9" dependencies = [ "anyhow", "erased_set", @@ -3123,7 +3123,7 @@ dependencies = [ [[package]] name = "iroh-net" version = "0.23.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=willow#9216fd64ec8f5d802bea25a9605f33af1c6cfc91" +source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-event-subscriptions#a2d8316cc5cb4de45ba64bd7a98fe98d870765c9" dependencies = [ "anyhow", "backoff", @@ -3250,7 +3250,7 @@ dependencies = [ [[package]] name = "iroh-willow" version = "0.23.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=willow#9216fd64ec8f5d802bea25a9605f33af1c6cfc91" +source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-event-subscriptions#a2d8316cc5cb4de45ba64bd7a98fe98d870765c9" dependencies = [ "anyhow", "bytes", diff --git a/tauri-todos/src-tauri/Cargo.toml b/tauri-todos/src-tauri/Cargo.toml index 3d8740f2..409f49a4 100644 --- a/tauri-todos/src-tauri/Cargo.toml +++ b/tauri-todos/src-tauri/Cargo.toml @@ -18,7 +18,7 @@ serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } tauri = { version = "1.6.1", features = ["api-all"] } tokio = { version = "1" } -iroh = { git = "https://github.com/n0-computer/iroh.git", branch = "willow" } +iroh = { git = "https://github.com/n0-computer/iroh.git", branch = "Frando/willow-event-subscriptions" } bytes = "1" tokio-util = { version = "0.7" } futures-lite = "2.3.0" diff --git a/tauri-todos/src-tauri/src/todos.rs b/tauri-todos/src-tauri/src/todos.rs index 1c60468c..a670cd3c 100644 --- a/tauri-todos/src-tauri/src/todos.rs +++ b/tauri-todos/src-tauri/src/todos.rs @@ -7,16 +7,17 @@ use iroh::client::Iroh; use iroh::net::ticket::NodeTicket; use iroh::net::NodeAddr; use iroh::node::ProtocolHandler; -use iroh::spaces::interest::{AreaOfInterestSelector, RestrictArea}; +use iroh::spaces::interest::RestrictArea; use iroh::spaces::proto::data_model::{AuthorisedEntry, Component, Path}; 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::str::FromStr; use std::sync::Arc; use tokio::task::JoinSet; -use tokio_stream::wrappers::IntervalStream; /// Todo in a list of todos. #[derive(Clone, Debug, Serialize, Deserialize)] @@ -61,9 +62,6 @@ impl Todo { const MAX_TODO_SIZE: usize = 2 * 1024; const MAX_LABEL_LEN: usize = 2 * 1000; -#[derive(Debug, Clone)] -pub struct StoreEvent; - /// List of todos, including completed todos that have not been archived pub struct Todos { node: Iroh, @@ -87,15 +85,13 @@ impl Todos { ), Some(node_ticket) => { let node_addr = NodeTicket::from_str(&node_ticket)?.node_addr().clone(); - let node_id = node_addr.node_id; let ticket = CapExchangeProtocol::request(&endpoint, node_addr, user).await?; println!("Importing & Syncing"); - let (space, sync) = node.spaces().import_and_sync(ticket).await?; - sync.complete_all().await; - let mut tasks = JoinSet::new(); - let mut sync = space - .sync_continuously(node_id, AreaOfInterestSelector::Widest) + 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:?}"); @@ -125,13 +121,10 @@ impl Todos { } pub async fn doc_subscribe(&self) -> Result>> { - // let stream = self - // .space - // .subscribe_area(Area::new_full(), Default::default()) - // .await?; - let stream = - IntervalStream::new(tokio::time::interval(std::time::Duration::from_millis(50))) - .map(|_| Ok(StoreEvent)); + let stream = self + .space + .subscribe_area(Area::new_full(), Default::default()) + .await?; Ok(stream) } From 3314b52a8f753bb26502d9fe62db5ff8ea77c7fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Tue, 3 Sep 2024 16:57:59 +0200 Subject: [PATCH 12/17] Only show most recent entry by key (not duplicate writes from multiple peers) --- tauri-todos/src-tauri/src/todos.rs | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/tauri-todos/src-tauri/src/todos.rs b/tauri-todos/src-tauri/src/todos.rs index a670cd3c..bc249dbf 100644 --- a/tauri-todos/src-tauri/src/todos.rs +++ b/tauri-todos/src-tauri/src/todos.rs @@ -8,13 +8,14 @@ use iroh::net::ticket::NodeTicket; use iroh::net::NodeAddr; use iroh::node::ProtocolHandler; use iroh::spaces::interest::RestrictArea; -use iroh::spaces::proto::data_model::{AuthorisedEntry, Component, Path}; +use iroh::spaces::proto::data_model::{AuthorisedEntry, Component, Entry, Path}; 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; @@ -170,15 +171,29 @@ impl Todos { pub async fn get_todos(&self) -> anyhow::Result> { let mut entries = self.space.get_many(Range3d::new_full()).await?; - let mut todos = Vec::new(); + let mut todos = BTreeMap::::new(); while let Some(entry) = entries.next().await { - let entry = entry?; + let (entry, _) = entry?.into_parts(); let todo = self.todo_from_entry(&entry).await?; if !todo.is_delete { - todos.push(todo); + match todos.entry(todo.id.clone()) { + btree_map::Entry::Occupied(mut curr) => { + if !curr.get().0.is_newer_than(&entry) { + *curr.get_mut() = (entry, todo); + } + } + btree_map::Entry::Vacant(space) => { + space.insert((entry, todo)); + } + } } } - todos.sort_by_key(|t| t.created); + + let mut todos = todos + .into_values() + .map(|(_, todo)| todo) + .collect::>(); + todos.sort_by_key(|todo| todo.created); Ok(todos) } @@ -210,11 +225,10 @@ impl Todos { .await .ok_or_else(|| anyhow::anyhow!("no todo found"))??; - self.todo_from_entry(&entry).await + self.todo_from_entry(&entry.into_parts().0).await } - async fn todo_from_entry(&self, entry: &AuthorisedEntry) -> anyhow::Result { - let entry = entry.entry(); + async fn todo_from_entry(&self, entry: &Entry) -> anyhow::Result { let key_component = entry .path() .get_component(0) From bcf85d2813c725477a40e40e329ab2f0562b3ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Wed, 4 Sep 2024 13:00:08 +0200 Subject: [PATCH 13/17] cleanup logic --- tauri-todos/src-tauri/Cargo.lock | 1 + tauri-todos/src-tauri/Cargo.toml | 1 + tauri-todos/src-tauri/src/todos.rs | 89 +++++++++++++++++------------- 3 files changed, 53 insertions(+), 38 deletions(-) diff --git a/tauri-todos/src-tauri/Cargo.lock b/tauri-todos/src-tauri/Cargo.lock index f8deb305..73e6419c 100644 --- a/tauri-todos/src-tauri/Cargo.lock +++ b/tauri-todos/src-tauri/Cargo.lock @@ -114,6 +114,7 @@ dependencies = [ "data-encoding", "derive_more 1.0.0-beta.7", "futures-lite 2.3.0", + "futures-util", "iroh", "postcard", "serde", diff --git a/tauri-todos/src-tauri/Cargo.toml b/tauri-todos/src-tauri/Cargo.toml index 409f49a4..40f517da 100644 --- a/tauri-todos/src-tauri/Cargo.toml +++ b/tauri-todos/src-tauri/Cargo.toml @@ -27,6 +27,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 diff --git a/tauri-todos/src-tauri/src/todos.rs b/tauri-todos/src-tauri/src/todos.rs index bc249dbf..eaa9f59a 100644 --- a/tauri-todos/src-tauri/src/todos.rs +++ b/tauri-todos/src-tauri/src/todos.rs @@ -1,14 +1,15 @@ -use anyhow::{bail, ensure, Context, Result}; +use anyhow::{anyhow, bail, ensure, Context, Result}; use bytes::Bytes; use futures_lite::future::Boxed; -use futures_lite::{Stream, StreamExt}; +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::interest::RestrictArea; -use iroh::spaces::proto::data_model::{AuthorisedEntry, Component, Entry, Path}; +use iroh::spaces::proto::data_model::{Component, Entry, Path}; use iroh::spaces::proto::grouping::{Area, Range, Range3d}; use iroh::spaces::proto::keys::{NamespaceKind, UserId}; use iroh::spaces::proto::meadowcap::AccessMode; @@ -68,6 +69,7 @@ pub struct Todos { node: Iroh, space: Space, user: UserId, + #[allow(unused)] tasks: JoinSet>, } @@ -169,31 +171,14 @@ impl Todos { } pub async fn get_todos(&self) -> anyhow::Result> { - let mut entries = self.space.get_many(Range3d::new_full()).await?; - - let mut todos = BTreeMap::::new(); - while let Some(entry) = entries.next().await { - let (entry, _) = entry?.into_parts(); - let todo = self.todo_from_entry(&entry).await?; - if !todo.is_delete { - match todos.entry(todo.id.clone()) { - btree_map::Entry::Occupied(mut curr) => { - if !curr.get().0.is_newer_than(&entry) { - *curr.get_mut() = (entry, todo); - } - } - btree_map::Entry::Vacant(space) => { - space.insert((entry, todo)); - } - } - } - } + let entries = self.get_latest(Range3d::new_full()).await?; - let mut todos = todos - .into_values() - .map(|(_, todo)| todo) - .collect::>(); + 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); + println!("get_todos:\n{todos:#?}"); Ok(todos) } @@ -213,27 +198,47 @@ impl Todos { } async fn get_todo(&self, id: String) -> anyhow::Result { - let entry = self - .space - .get_many(Range3d::new( + let entries = self + .get_latest(Range3d::new( Range::full(), Range::new_open(Self::to_willow_path(&id)?), 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? - .next() - .await - .ok_or_else(|| anyhow::anyhow!("no todo found"))??; + .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); + } + } + } - self.todo_from_entry(&entry.into_parts().0).await + 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 key_component = entry - .path() - .get_component(0) - .ok_or_else(|| anyhow::anyhow!("path component missing"))?; - let id = String::from_utf8(key_component.to_vec()).context("invalid key")?; + 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), @@ -246,6 +251,14 @@ impl Todos { Component::new(key.as_ref()).ok_or_else(|| anyhow::anyhow!("invalid component"))?, )?) } + + 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>; From dcde62e1514c7ccb33eb990c6681a1ebbb6e7419 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Wed, 4 Sep 2024 15:52:05 +0200 Subject: [PATCH 14/17] Try inserting entries on the "foreign" subspac --- tauri-todos/src-tauri/src/todos.rs | 34 +++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/tauri-todos/src-tauri/src/todos.rs b/tauri-todos/src-tauri/src/todos.rs index eaa9f59a..588cba65 100644 --- a/tauri-todos/src-tauri/src/todos.rs +++ b/tauri-todos/src-tauri/src/todos.rs @@ -8,8 +8,9 @@ use iroh::client::Iroh; use iroh::net::ticket::NodeTicket; use iroh::net::NodeAddr; use iroh::node::ProtocolHandler; -use iroh::spaces::interest::RestrictArea; -use iroh::spaces::proto::data_model::{Component, Entry, Path}; +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; @@ -68,6 +69,7 @@ const MAX_LABEL_LEN: usize = 2 * 1000; pub struct Todos { node: Iroh, space: Space, + subspace_id: SubspaceId, user: UserId, #[allow(unused)] tasks: JoinSet>, @@ -81,14 +83,17 @@ impl Todos { ) -> anyhow::Result { let user = node.spaces().create_user().await?; - let (space, tasks) = match node_ticket { + 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() @@ -101,13 +106,16 @@ impl Todos { } anyhow::Ok(()) }); - (space, tasks) + (space, subspace_id, tasks) } }; + println!("Using subspace ID {subspace_id}"); + Ok(Todos { node, user, + subspace_id, space, tasks, }) @@ -185,7 +193,12 @@ impl Todos { async fn insert_bytes(&self, key: impl AsRef<[u8]>, content: Bytes) -> anyhow::Result<()> { self.space .insert_bytes( - EntryForm::new(self.user, Self::to_willow_path(key)?), + EntryForm { + auth: AuthForm::Any(self.user), + subspace_id: SubspaceForm::Exact(self.subspace_id), + path: Self::to_willow_path(key)?, + timestamp: TimestampForm::Now, + }, content, ) .await?; @@ -247,9 +260,7 @@ impl Todos { } fn to_willow_path(key: impl AsRef<[u8]>) -> anyhow::Result { - Ok(Path::new_singleton( - Component::new(key.as_ref()).ok_or_else(|| anyhow::anyhow!("invalid component"))?, - )?) + Ok(Path::from_bytes(&[key.as_ref()])?) } fn from_willow_path(path: &Path) -> anyhow::Result { @@ -331,3 +342,10 @@ impl ProtocolHandler for CapExchangeProtocol { Box::pin(self.respond(conn)) } } + +fn progenitor(cap: &CapabilityPack) -> &UserId { + match cap { + CapabilityPack::Read(read) => read.read_cap().progenitor(), + CapabilityPack::Write(write) => write.progenitor(), + } +} From c8a2cfa16b57579289058d97a0ca337333d1ef26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Tue, 24 Sep 2024 15:16:49 +0200 Subject: [PATCH 15/17] fix: Return the correct todo in `get_todo` --- flake.lock | 12 ++++----- tauri-todos/src-tauri/Cargo.lock | 39 +++++++++++++++--------------- tauri-todos/src-tauri/Cargo.toml | 2 +- tauri-todos/src-tauri/src/todos.rs | 7 ++++-- 4 files changed, 32 insertions(+), 28 deletions(-) diff --git a/flake.lock b/flake.lock index cc92af7c..a3763810 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "lastModified": 1726560853, + "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", "type": "github" }, "original": { @@ -47,11 +47,11 @@ ] }, "locked": { - "lastModified": 1724898214, - "narHash": "sha256-4yMO9+Lsr3zqTf4clAGGag/bfNTmc/ITOXbJQcOEok4=", + "lastModified": 1727144949, + "narHash": "sha256-uMZMjoCS2nf40TAE1686SJl3OXWfdfM+BDEfRdr+uLc=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "0bc2c784e3a6ce30a2ab1b9f47325ccbed13039f", + "rev": "2e19799819104b46019d339e78d21c14372d3666", "type": "github" }, "original": { diff --git a/tauri-todos/src-tauri/Cargo.lock b/tauri-todos/src-tauri/Cargo.lock index 73e6419c..03620dda 100644 --- a/tauri-todos/src-tauri/Cargo.lock +++ b/tauri-todos/src-tauri/Cargo.lock @@ -2897,8 +2897,8 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "iroh" -version = "0.23.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-event-subscriptions#a2d8316cc5cb4de45ba64bd7a98fe98d870765c9" +version = "0.24.0" +source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-proptest#6b4f2bf893e974377eee2a96d12ec2c0981dc340" dependencies = [ "anyhow", "async-channel", @@ -2942,8 +2942,8 @@ dependencies = [ [[package]] name = "iroh-base" -version = "0.23.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-event-subscriptions#a2d8316cc5cb4de45ba64bd7a98fe98d870765c9" +version = "0.24.0" +source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-proptest#6b4f2bf893e974377eee2a96d12ec2c0981dc340" dependencies = [ "aead", "anyhow", @@ -2983,8 +2983,8 @@ dependencies = [ [[package]] name = "iroh-blobs" -version = "0.23.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-event-subscriptions#a2d8316cc5cb4de45ba64bd7a98fe98d870765c9" +version = "0.24.0" +source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-proptest#6b4f2bf893e974377eee2a96d12ec2c0981dc340" dependencies = [ "anyhow", "async-channel", @@ -3025,8 +3025,8 @@ dependencies = [ [[package]] name = "iroh-docs" -version = "0.23.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-event-subscriptions#a2d8316cc5cb4de45ba64bd7a98fe98d870765c9" +version = "0.24.0" +source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-proptest#6b4f2bf893e974377eee2a96d12ec2c0981dc340" dependencies = [ "anyhow", "async-channel", @@ -3063,8 +3063,8 @@ dependencies = [ [[package]] name = "iroh-gossip" -version = "0.23.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-event-subscriptions#a2d8316cc5cb4de45ba64bd7a98fe98d870765c9" +version = "0.24.0" +source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-proptest#6b4f2bf893e974377eee2a96d12ec2c0981dc340" dependencies = [ "anyhow", "async-channel", @@ -3103,8 +3103,8 @@ dependencies = [ [[package]] name = "iroh-metrics" -version = "0.23.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-event-subscriptions#a2d8316cc5cb4de45ba64bd7a98fe98d870765c9" +version = "0.24.0" +source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-proptest#6b4f2bf893e974377eee2a96d12ec2c0981dc340" dependencies = [ "anyhow", "erased_set", @@ -3123,8 +3123,8 @@ dependencies = [ [[package]] name = "iroh-net" -version = "0.23.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-event-subscriptions#a2d8316cc5cb4de45ba64bd7a98fe98d870765c9" +version = "0.24.0" +source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-proptest#6b4f2bf893e974377eee2a96d12ec2c0981dc340" dependencies = [ "anyhow", "backoff", @@ -3166,7 +3166,6 @@ dependencies = [ "pkarr", "postcard", "rand 0.8.5", - "rand_core 0.6.4", "rcgen", "reqwest 0.12.5", "ring", @@ -3250,8 +3249,8 @@ dependencies = [ [[package]] name = "iroh-willow" -version = "0.23.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-event-subscriptions#a2d8316cc5cb4de45ba64bd7a98fe98d870765c9" +version = "0.24.0" +source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-proptest#6b4f2bf893e974377eee2a96d12ec2c0981dc340" dependencies = [ "anyhow", "bytes", @@ -7107,13 +7106,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", diff --git a/tauri-todos/src-tauri/Cargo.toml b/tauri-todos/src-tauri/Cargo.toml index 40f517da..f8b34d14 100644 --- a/tauri-todos/src-tauri/Cargo.toml +++ b/tauri-todos/src-tauri/Cargo.toml @@ -18,7 +18,7 @@ serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } tauri = { version = "1.6.1", features = ["api-all"] } tokio = { version = "1" } -iroh = { git = "https://github.com/n0-computer/iroh.git", branch = "Frando/willow-event-subscriptions" } +iroh = { git = "https://github.com/n0-computer/iroh.git", branch = "Frando/willow-proptest" } bytes = "1" tokio-util = { version = "0.7" } futures-lite = "2.3.0" diff --git a/tauri-todos/src-tauri/src/todos.rs b/tauri-todos/src-tauri/src/todos.rs index 588cba65..16df7ce2 100644 --- a/tauri-todos/src-tauri/src/todos.rs +++ b/tauri-todos/src-tauri/src/todos.rs @@ -186,7 +186,6 @@ impl Todos { .try_collect::>() .await?; todos.sort_by_key(|todo| todo.created); - println!("get_todos:\n{todos:#?}"); Ok(todos) } @@ -211,10 +210,14 @@ impl Todos { } async fn get_todo(&self, id: String) -> anyhow::Result { + 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(), - Range::new_open(Self::to_willow_path(&id)?), + // 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?; From eedc5599f6a20cbf75cc19f9a7bb1d99045a2931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Fri, 18 Oct 2024 13:15:20 +0200 Subject: [PATCH 16/17] Test latest version --- flake.nix | 3 ++ tauri-todos/src-tauri/Cargo.lock | 79 +++++++++++++++++++------------- tauri-todos/src-tauri/Cargo.toml | 3 +- 3 files changed, 52 insertions(+), 33 deletions(-) diff --git a/flake.nix b/flake.nix index fcd7587b..abe896f9 100644 --- a/flake.nix +++ b/flake.nix @@ -80,6 +80,9 @@ # 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/src-tauri/Cargo.lock b/tauri-todos/src-tauri/Cargo.lock index 03620dda..e5c7d5e5 100644 --- a/tauri-todos/src-tauri/Cargo.lock +++ b/tauri-todos/src-tauri/Cargo.lock @@ -51,7 +51,7 @@ dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -509,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" @@ -2898,7 +2911,6 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "iroh" version = "0.24.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-proptest#6b4f2bf893e974377eee2a96d12ec2c0981dc340" dependencies = [ "anyhow", "async-channel", @@ -2943,7 +2955,6 @@ dependencies = [ [[package]] name = "iroh-base" version = "0.24.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-proptest#6b4f2bf893e974377eee2a96d12ec2c0981dc340" dependencies = [ "aead", "anyhow", @@ -2984,7 +2995,6 @@ dependencies = [ [[package]] name = "iroh-blobs" version = "0.24.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-proptest#6b4f2bf893e974377eee2a96d12ec2c0981dc340" dependencies = [ "anyhow", "async-channel", @@ -3026,7 +3036,6 @@ dependencies = [ [[package]] name = "iroh-docs" version = "0.24.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-proptest#6b4f2bf893e974377eee2a96d12ec2c0981dc340" dependencies = [ "anyhow", "async-channel", @@ -3064,7 +3073,6 @@ dependencies = [ [[package]] name = "iroh-gossip" version = "0.24.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-proptest#6b4f2bf893e974377eee2a96d12ec2c0981dc340" dependencies = [ "anyhow", "async-channel", @@ -3104,7 +3112,6 @@ dependencies = [ [[package]] name = "iroh-metrics" version = "0.24.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-proptest#6b4f2bf893e974377eee2a96d12ec2c0981dc340" dependencies = [ "anyhow", "erased_set", @@ -3124,7 +3131,6 @@ dependencies = [ [[package]] name = "iroh-net" version = "0.24.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-proptest#6b4f2bf893e974377eee2a96d12ec2c0981dc340" dependencies = [ "anyhow", "backoff", @@ -3250,13 +3256,13 @@ dependencies = [ [[package]] name = "iroh-willow" version = "0.24.0" -source = "git+https://github.com/n0-computer/iroh.git?branch=Frando/willow-proptest#6b4f2bf893e974377eee2a96d12ec2c0981dc340" dependencies = [ "anyhow", "bytes", "curve25519-dalek", "derive_more 1.0.0-beta.7", "ed25519-dalek", + "either", "futures-buffered", "futures-concurrency", "futures-lite 2.3.0", @@ -3285,7 +3291,18 @@ dependencies = [ "ufotofu", "willow-data-model", "willow-encoding", - "zerocopy 0.8.0-alpha.17", + "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]] @@ -4865,7 +4882,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -7875,6 +7892,24 @@ dependencies = [ "ufotofu", ] +[[package]] +name = "willow-store" +version = "0.1.0" +source = "git+https://github.com/n0-computer/willow-store.git?branch=matheus23/minor-adjustments#286389c1fd318b228d3833435b66d1920aa69d41" +dependencies = [ + "anyhow", + "blake3", + "genawaiter", + "hex", + "itertools", + "redb 2.1.1", + "ref-cast", + "self_cell", + "smallvec", + "tracing", + "zerocopy", +] + [[package]] name = "winapi" version = "0.3.9" @@ -8754,16 +8789,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive 0.7.35", -] - -[[package]] -name = "zerocopy" -version = "0.8.0-alpha.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da056c7307048e30bce8d625c6f0633366d31f1086b3c87ed9b1f18fa1081cb1" -dependencies = [ - "zerocopy-derive 0.8.0-alpha.17", + "zerocopy-derive", ] [[package]] @@ -8777,17 +8803,6 @@ dependencies = [ "syn 2.0.72", ] -[[package]] -name = "zerocopy-derive" -version = "0.8.0-alpha.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eb22123403bf9c05af423e2ced336a5fc2853df9179b42bea8144d6bf497a57" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.72", -] - [[package]] name = "zeroize" version = "1.8.1" diff --git a/tauri-todos/src-tauri/Cargo.toml b/tauri-todos/src-tauri/Cargo.toml index f8b34d14..c11c3c08 100644 --- a/tauri-todos/src-tauri/Cargo.toml +++ b/tauri-todos/src-tauri/Cargo.toml @@ -18,7 +18,8 @@ serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } tauri = { version = "1.6.1", features = ["api-all"] } tokio = { version = "1" } -iroh = { git = "https://github.com/n0-computer/iroh.git", branch = "Frando/willow-proptest" } +# iroh = { git = "https://github.com/n0-computer/iroh.git", branch = "Frando/willow-proptest" } +iroh = { path = "../../../iroh-willow/iroh" } bytes = "1" tokio-util = { version = "0.7" } futures-lite = "2.3.0" From 63f1c327affe767d21f9421f459bc3e0a9000200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Mon, 30 Jun 2025 10:10:23 +0200 Subject: [PATCH 17/17] WIP - persistent willow store --- .../src-tauri/.test-sth/blobs/blobs.db | Bin 0 -> 1589248 bytes .../src-tauri/.test-sth/default-author | 1 + tauri-todos/src-tauri/.test-sth/docs.redb | Bin 0 -> 1589248 bytes tauri-todos/src-tauri/.test-sth/keypair | 7 ++++ .../src-tauri/.test-sth/peers.postcard | Bin 0 -> 104 bytes tauri-todos/src-tauri/.test-sth/spaces.redb | Bin 0 -> 1589248 bytes tauri-todos/src-tauri/Cargo.lock | 4 ++- tauri-todos/src-tauri/src/main.rs | 34 +++++++++--------- 8 files changed, 29 insertions(+), 17 deletions(-) create mode 100644 tauri-todos/src-tauri/.test-sth/blobs/blobs.db create mode 100644 tauri-todos/src-tauri/.test-sth/default-author create mode 100644 tauri-todos/src-tauri/.test-sth/docs.redb create mode 100644 tauri-todos/src-tauri/.test-sth/keypair create mode 100644 tauri-todos/src-tauri/.test-sth/peers.postcard create mode 100644 tauri-todos/src-tauri/.test-sth/spaces.redb 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 0000000000000000000000000000000000000000..0b9e386d2a1fad636b833baf3c5f72a8f9cdb7bc GIT binary patch literal 1589248 zcmeI*3z!tuoha}cW&$G!vf_idsO^aOn%3@~p6Ld?iZ6V8kmUL{z~}Z8Cg6=af9hBB zc8dD$B)_=tiW|Q3iFu!$yJYMy_P@E~`nv6Jy3JP>E$E0Jd&gPX-%h;fnM-bcwkN4TlAT!|9wr(KeZiKKk@SK>}>wE z-P_N(_Sx}ElHdRAl6C!-|MRPlJXy{E;O^J|u;H9T2R(DlfOBRny1Dht7w2`aHX}fQ z009C72oNAZfB*pk?`wfd|90`P`skoAuk`ods_z$N2iHcEZmNyuK2;ly?5d4!sHuyd z*e~>?htx$QhSo*dKdFnJ36VIkE}9e_! z+z?`6h)Q`Y{rXslvqPL0EY^x$g-D0UhX0&eR}=LM%j>)MKexwnHOtBd)z_=*%n1I`e%?Ad2cXwTFGH=4wlT009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0Rja6SfDnFq7fltHBmJ3j4%(&)f7?v=H=?TeQh2^{Wf{*Yjsy&0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBly@O~1gj*+l9jE%4)43IE1%-44pFsj?U{9ctgifW=iyjPjamjD3*1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oTt10@Y^}{wzG3Pz(VH@coh1&qQpmN>SL* z=M=W5V?6-^1PBoL;0jdVLnOj`hje%Y5pGRvdfza&$8uGm@`cpiGgLLq3G90Um2<@K z5F^6E*f6gOR6dAZT{ho01va&f(Ep6}Y#X6}x^IeXu65Ps_TW+(v!MD)rQ=>*ey`@$ zvP}x*sywSuEl1@IL3O#W^Xk4&4g0NJqp0pD43n^Fxw>x4d3D>yuw7-j`uf}+%hh#Z zchNy1#`buvx-Pbjbv=(kHC@lTSJ(9{b9G(MvQ*bq=GC%Pj_?adh7~)QD(OuG2oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF>?;EG`$|RC zlK=q%1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C7dKIYaHH8EO2oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkKc-w^1xZ&Xt)2@oJafB*pk1PBlyK!5-N0t5&UAV7cs0RjXF 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 z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF^hx00u+{A$`h^(Yy*xC`*Kc@xT2s4uxqHT(%!iBO`vaEX>Z*6CeXL7v^Q>j6X@Gk+FRe;Lifc)fWS@&{C_{ufB*mh literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..693308babd8abf23766a77c7c9258c4b5276af26 GIT binary patch literal 1589248 zcmeI*QHWeu835pOXGV8g*>RmT%o*w*WJpkpNOn?9Z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB=CN1?nlKT@kHXO1mFPsU7*%PRlf~ zoUhnfWzP$w)JU)O{34xRsai}flEB9WzHq9lUF<6^P3jzcP z5FkK+009C72oNB!=>_t84DEPt;b6SM5dZqH{2oL6eq{dL$ki5_qNCqZTunzk0RjXF z5a?Zj{2F3!ymlCk7Z7o2V(E3mTqVx}HMkJvd^ zZcTxuV`E_5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV6Rp0(0wVqX_{51PBlyK!5-N0t5&UAh7ub+M9m~RzQFN0RjXFbRp2{qK_^F2=tD? zK<_NYO1czicG<6&x;1Wq#nj$_IjrN~`{!2JO}kRps^rzDhw5Qc<*H759lNiLE+s&K z009C72oNAZfWYeu6!U%QSYu#wSKjOdUIVWOiiVvGJM7>6wv-W=9s=jO;r;{-w$36XQoF zr$_EQIeBF2A3t~cEC2016)Pe@fB*pk1PBlyK!5-N0=*@0<6i$SFVmJ=k&}t< z96h&x@bU-W{mdWl?f=i|&*b`|Rkm8VDU*n6uIzTMLa+ZK^ukav$|_fiUH|Why8ho= zPX$&(fB*pk1PBlyK!8Ax3zXOYKN9E0IynIXS)j+C7%Yzf0RjXF5FkK+ z0D+zp$Y(Q30t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!Ctz6}T-X{&YkwVrM5Gi1O4mv=blV6dFw GXgC05%O=wR literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..bb459c05f39e3fe32e67b9c592cc53a3c9f044a1 GIT binary patch literal 1589248 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 z zMK;ury8Y58O08P|`PUm}wcgt5K>D#UGBJDW5e|S{78q_h%o-}CSAt)4X4h2q3ODWPrDv7Y4D=TTbK{n>zu$(J;Ech}`5H_Tf!ywQr{ z_n%kg<-Ox$l2h7Dzq)SwQ?8oWt5%C*NpXMrsOgxrmh)QQIwWs{*F5#%fViYJuRq(N z#HLp!ZFxVVYxm||p1!GSqpB-!DYvcf%>060+<43lIj47eMp}AGT6|J$rKY>oHBQ3vz?#6F}X~M(!00bxUy#Cvf9?GXCJ@x(AkHUB$vH8Z_D|m%P;7G$?J22PEF1ZzRmH8 zu_kHQYQht9z8Tr{nl3H=QElA?m+gIf(~3$1 z+fT1GtJI=}$Cf*9OUvvhhSr~0ctefu%{on}(YV3ln$_P~FUU2BI ziBHa}xovFy$GhD7cBJT(zMJnjTrn~&WBl#S*H_(qaehH>ziQ{VL1(9@rzAB_3%{5DY2<>DJii@Sy}1nO%gJbg3DT7Xg{8WN4j054%Zbw=K0E>U%2U>E}wn5I{wY& zefAbR*!=BTcT}9Q@sbv`HlI7N_2vT;s;rngJo@G$Yqs4q_VgB&I&6OR+FtcutJI@Z zn>*Xxc4D3B`$oOFx!!ON%GjU1F1lydwY63iA2IXUDvcI(9@Vhn!7Hz< z{*Smq)%NbcqW=E0%QttsvC~tPC!Tgjw_&aJE?rS-`Mu5l)H`d#hSOWs8JAhR{krRK zeErnUhx%0-Jg#)<0S}gH7+$?=wI1>DbM0$&m{la{{r%Uk8&>1SijVjBV)N`_`=hIM z>UG`h{DMx{QvJrG{y!ZC8UCmVUxyd6!wrh`T-dY1v`g;oAN%5^HJ%vO`My_LJy>zu zhW#;*RNvlcK+2rQT32Z}@3XACA8I=E#G#{~8(*Q{F;ljtcmK|1t z|J-ehYqnjnYwA0@Pb*z|R-1_{R+U?Mpy%_gmftryZ-aBTp1FKx{e81Hoq61a)8~zS zaz%&z2;@AurDIcU_hIeVf`zGTa-%@^$G zwQud09q%q)vi_9b>lzKHy`%s2=eL~Ob70w3S5LTXLrkL; +type IrohNode = iroh::node::Node; // setup an iroh node async fn setup(handle: tauri::AppHandle) -> Result<()> { - // 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"), - // ) - // })?; + 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::memory() + let node = iroh::node::Node::persistent(data_root) + .await? .build() .await? .accept( @@ -60,6 +61,7 @@ async fn setup(handle: tauri::AppHandle) -> Result<()> { .spawn() .await?; handle.manage(AppState::new(node)); + println!("CALLED MANAGE"); Ok(()) }